<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    treenode

    在路上。

    BlogJava 首頁 新隨筆 聯系 聚合 管理
      5 Posts :: 1 Stories :: 53 Comments :: 0 Trackbacks

    前言

    ?
    作為一個專注于
    C/S 方面開發的程序員,我一直對“面向對象的編程框架如何與 Windows 操作系統的消息機制打交道”這個問題有著相當大的興趣。讀者想必知道,象 MFC 、 VCL SWT 這樣的類庫在實現界面處理的時候,有幾個主要問題是不得不考慮的。首先是如何為窗口和控件這樣的界面以面向對象方式進行包裝——這一方面可以說沒多少技術上的難題;從一般意義上講,不過是把 HWND 作為第一個參數的函數分類整理一下而已。當然,具體作起來還是有不少東西需要認真考慮,只是這些問題多半是在設計的層面,考慮包裝是否完善、維護和擴展起來是否方便等等;在實現上基本上就沒什么需要克服的技術障礙了。而另一方面——即如何處理系統消息機制,則是一個頗費腦筋的問題了。其中最大的難點之一,就是 Windows 的消息系統依賴于窗口過程(術語叫做 Window Procedure ),而這個窗口過程卻是一個非面向對象的、普通的全局函數,它完全不理解對象是什么;而為了讓整個程序 OO 起來,你還非得讓它去操縱對象不可。因此,如何將窗口過程用面向對象的方法完美的封裝起來,就成為各種類庫面臨的最大挑戰之一。當然,這也理所當然的成為各個開發小組展示自身功力的絕好舞臺。

    ?
    據我所知,在此一問題上,不同的類庫采納了不同的做法。較早的
    MFC 使用了窗口查找表的技術,即為每個窗口和對應的窗口過程建立一個映射;需要處理消息的時候,則是映射表中找到窗口所對應的過程,并調用之。這樣會帶來幾個問題。首先是每次進行查表勢必浪費時間,為此 MFC 不惜在關鍵處使用 Cache 映射和內聯匯編的方法以提高效率。第二個問題:映射表是和線程相關聯的,如果你將窗口傳遞給另外一個線程, MFC 無法在該線程中找到窗口的映射項,也就不知該如何是好,于是只能出錯。我已經在很多地方看到有人問跨線程傳遞窗口指針的疑問,多半都是因為不理解 MFC 的消息處理機制。正因為如此, MFC 的使用者必須強制遵守一些調用方面的約定,否則會出現很多莫名其妙的錯誤,這無疑是框架不夠友好的表現。而稍晚出現的 VCL ATL 則使用了一種比較巧妙的 Thunk 技術,利用函數調用過程中使用堆棧的原理,巧妙的將對象指針“暗度陳倉”地偷偷傳遞進去,并通過一些內存中的“小動作”越過了通常的處理機制。這樣做的好處是節省了額外維護映射表的開銷,速度相當快,同時也不存在線程傳遞的問題。當然,這個過程因為大量使用匯編,而且需要對函數調用的底層機制有深刻的理解,所以很難為一般程序員所理解和運用。(相應的維護起來也難度也比較高——還記得 Anders 離開 Borland 以后相當長時間沒有人敢改動 Delphi 底層代碼的往事嗎?)

    ?

    在眾多框架中, SWT 算是比較年輕的一個,也是頗為獨特的一個。之所以說它特殊,因為它是用 Java 編寫的。我們知道,和 Windows 平臺上的本地開發工具不同, Java 程序是生活在自己的虛擬機中的,除非通過 JNI 這個后門,否則它對底下的操作系統根本一無所知。這顯然為設計者提出了更高的挑戰。那么, SWT 又是如何實現這一點的呢?非常幸運, SWT 是完全開放源代碼的(當然, MFC VCL 也是開放的,不過這種開放就比較小家子氣——許多時候只有你購買昂貴的企業版以后才能看到這些寶貴的源碼, D 版且不論)。開放源代碼為我們研究其實現掃清了障礙。

    ?

    準備工作

    ?

    在上路之前,我們應當準備好足夠的武器。當然, Eclipse 是必不可少的——我使用的是最新的 Eclipse 3.2 RC6 版本,不過只要是 3.x 的版本,在核心代碼方面應該不會有很大差別,所以對本文的目的而言, Eclipse 3.0 以上的任何版本都是夠用的。此外,如果你還沒有安裝任何界面開發方面的插件的話,我強烈建議你安裝一個 Eclipse.org 官方的 Visual Editor 。這倒不是說我認為該插件對界面開發有多大的助力——事實上從功能上來說它要比 SWT Designer 等同類產品遜色;但是該插件最大的好處在于可以非常簡單的設定好 SWT 程序所運行的環境,還包括源代碼支持,這樣你就可以很輕松的跟蹤到 SWT 源代碼內部去了。并且這個工具是沒有使用限制的,也不需要注冊激活,這一點要比 SWT Designer 來得方便。

    ?

    安裝 Visual Editor 以后,你可以在創建項目的過程中使用 Java Settings 頁面,或者在項目創建以后再選擇項目屬性,從 Java Build Path 分支下的 Libraries 頁面訪問同樣的界面:

    然后按下 Add Library 按鈕。如果 Visual Editor 安裝正確,這里會多出一個 Standard Widget Toolkit 項。選擇它然后 Next 。


    默認選中的 IDE Platform 不用變,不過最好也勾選上 Include support for JFace library



    然后按 Finish 。這樣準備工作就完成了。

    ?

    ?上路吧!

    ?

    現在我們可以對 SWT 的源代碼著手進行分析了。不過,應當從哪里開始下手呢?答案取決于對消息機制的理解。我們知道,任何 Windows 程序(嚴格地說,應當是有用戶界面的程序,而不包括控制臺應用和系統服務程序)都是從 WinMain 開始的;而 WinMain 中最重要的部分則是消息循環,這也是任何 Windows 程序得以持續運行的生命之源,所以有人稱之為“消息泵”,就是因為它象心臟一樣為應用程序的生命源源不斷的輸送動力。通常,在用 SDK 編寫的程序中會有如下的調用:

    while ?(?GetMessage( & msg,?NULL,? 0 ,? 0 )?)

    {

    ???TranslateMessage(?
    & msg?);

    ???DispatchMessage(?
    & msg?);
    }

    ?
    SWT 應用程序,盡管實現方法不同,但是看起來非常相似:

    while ?(? ! shell.isDisposed()?)

    {

    ????
    if ?(? ! display.readAndDispatch()?)

    ???????display.sleep();

    }

    僅從文字上推斷,也很容易猜想:Display.readAndDispatch()方法所作的和SDK程序中Translate/Dispatch兩行所作的事情應該是類似的;而sleep方法,則在SDK程序中沒有直接的對應物。接下來,我們可以按住Ctrl鍵然后點擊readAndDispatch方法,去探查一下它內部是如何實現的。

    public ? boolean ?readAndDispatch?()?{

    ????checkDevice?();

    ????drawMenuBars?();

    ????runPopups?();

    ????
    if ?(OS.PeekMessage?(msg,? 0 ,? 0 ,? 0 ,?OS.PM_REMOVE))?{

    ???????
    if ?( ! filterMessage?(msg))?{

    ???????????OS.TranslateMessage?(msg);

    ???????????OS.DispatchMessage?(msg);

    ???????}

    ???????runDeferredEvents?();

    ???????
    return ? true ;

    ????}

    ????
    return ?runMessages? && ?runAsyncMessages?( false );


    雖然這里有一些新鮮的東西,不過總體上來說沒有太大意外。我們如預想的那樣看到了對Translate/DispatchMessage方法的調用,這證明SWT的消息循環和一般的本地程序是沒有本質差別的。不過和SDK程序有所不同的是,這里使用了PeekMessage,而非傳統SDK程序中所使用的GetMessage。(事實上,現代的大多數UI框架也傾向于采用PeekMessage而非GetMessage,不信的話你可以自己去查查看。)

    ?

    為什么是 PeekMessage 而非 GetMessage 呢?這是因為:除了操作系統通過正常途徑發送來的消息以外,應用程序通常還要額外使用一些內部的消息,這些消息需要通過“非常規”的途徑進行處理。如果使用 GetMessage 的話,它只有在應用程序消息隊列中存在消息的時候才會被喚醒,那些“非常”消息就失去了獲得及時處理的機會。例如, SWT 就創建了一些用于線程通信的內部消息,這些消息是 Display.syncExec Display.asyncExec 得以正常運作的基礎。上面 filterMessage runDeferredEvents 方法就對此有所涉及。不過因為這些輔助方法和本文的主題沒有直接關系,所以我不打算對它們作什么說明;如果你有興趣的話,可以自己去研究一下這些函數內部究竟做了些什么。

    ?

    接下來我們看看 SWT 消息循環中另外一個意義不明的方法: sleep

    public ? boolean ?sleep?()?{

    ????checkDevice?();

    ????
    if ?(runMessages? && ?getMessageCount?()? != ? 0 )? return ? true ;

    ????
    if ?(OS.IsWinCE)?{

    ???????OS.MsgWaitForMultipleObjectsEx?(
    0 ,? 0 ,?OS.INFINITE,?OS.QS_ALLINPUT,?OS.MWMO_INPUTAVAILABLE);

    ???????
    return ? true ;

    ????}

    ????
    return ?OS.WaitMessage?();

    }

    中間的代碼明顯是針對WinCE系統的,可以不去管它。有點意外的是這里出現了WaitMessage,這是一般程序中比較少見的一個函數調用。不過認真想想,原因大概也可以理解。PeekMessageGetMessage的不同之處在于:如果消息隊列中沒有消息可抓,那么GetMessage會釋放控制權讓其他程序運行,而PeekMessage卻不會。即使是在搶占式多任務操作系統中,一個程序總是攥著控制權不放也不是好事。因此,如果真的沒有任何消息需要處理,那么WaitMessage將使線程處于睡眠狀態,直到下個消息到來才再次喚醒——這也是SWT為什么把該方法定名為sleep的原因。

    ?

    通過上面的研究我們看到:拋開無關的細節,消息循環的處理本身是非常簡單的。然而,這些研究尚不足以解決我們的疑惑。最關鍵的窗口過程究竟是在哪定義的呢?很顯然,我們需要追蹤窗口的創建過程,來找到定義窗口過程的地方。所以接下來的研究對象就是 Shell 。

    ?

    Shell 類并沒有類似 create 這樣的方法,因此我們可以合理的猜想:創建窗口的過程大概就放在構造函數中。

    ?

    接下來我們跟蹤 Shell 的實現代碼來證實此猜想。不過有一點值得先作個說明:你可能已經知道, Shell 對象具有一個很深的繼承層次——它的直接父類是 Decoration ,而這個類的父類又是 Canvas , Canvas 的父類是 Composite ,依此類推。你必須知道這個層次的原因是: Shell 創建過程中經常會用到祖先類中的一些方法,同時也會重載祖先類中的部分方法,因此在跟蹤代碼的時候,你也得根據方法的調用者實際所在的類,在這個類層次中上下移動。 Eclipse 提供的 Hierarchy 視圖是個不錯的工具,可以讓它來幫助你,如下圖所示。小心不要迷路!


    ?

    經過一番跟蹤,我們有了如下的發現:

    l????????通常,我們調用的是型如Shell(Display)或者Shell(Display, style)這樣的構造函數。這兩個構造函數都會調用內部的其他一些形式的構造函數,最終調用如下的形式:

    Shell(Display, Shell parent, int style, int handle);

    l???????? 上述方法的最后一步調用了createWidget()。這個方法的名字應該讓你馬上有一種“我找到了”的感覺;

    l???????? Shell本身并沒有定義createWidget()方法,實際上它調用的是Decorations.createWidget

    l???????? Decorations.createWidget其實并沒有做什么事,只是簡單的調用上級(Canvas)的實現,然后修改一些內部狀態。不過,Canvas并沒有重載createWidget,因此控制繼續向上,來到Scrollable;

    l????????同樣,Scrollable.createWidget也是簡單的向上調用。Control類才是完成真正工作的地方。我們可以從代碼中看到,這個類作了相當多的工作:

    void?createWidget?()?{

    ????foreground?
    =?background?=?-1;

    ????checkOrientation?(parent);

    ????createHandle?();

    ????checkBackground?();

    ????checkBuffered?();

    ????register?();

    ????subclass?();

    ????setDefaultFont?();

    ????checkMirrored?();

    ????checkBorder?();

    ????
    if?((state?&?PARENT_BACKGROUND)?!=?0)?{

    ???????setBackground?();

    ????}

    }


    有經驗的讀者從名字應當能夠猜到,上面這么多方法中,createHandle才應當是真正值得我們關心的。

    void?createHandle?()?{

    ????
    int?hwndParent?=?widgetParent?();

    ????handle?
    =?OS.CreateWindowEx?(

    ???????widgetExtStyle?(),

    ???????windowClass?(),

    ???????
    null,

    ???????widgetStyle?(),

    ???????OS.CW_USEDEFAULT,?
    0,?OS.CW_USEDEFAULT,?0,

    ???????hwndParent,

    ???????
    0,

    ???????OS.GetModuleHandle?(
    null),

    ???????widgetCreateStruct?());

    ????….

    }


    我沒有把完整的代碼列出來;因為,既然已經看到了CreateWindowEx,就知道我們想找的東西已經就在眼前,沒有必要再找下去了。

    ?

    createWindowEx方法必須指定要創建的窗口類名字,也就是上面代碼中windowClass()方法所作的事情。我們接著看看這個類名應當是什么。然而,我們發現windowClass()Control類中定義為抽象方法:

    abstract TCHAR windowClass ();

    這意味著實際上類的名字是由具體的子類來指定的。所以我們還要繼續跟蹤下去。因為繼承層次上每個類都能夠改寫這個方法,所以我們不應該從現在的位置回頭向下,而是應當從最底層的Shell開始向上找——這樣,你找到的第一個被重載的地方就是最終的實現。

    ?

    Shell的確實現了windowClass()方法,方法如下:

    TCHAR?windowClass?()?{

    ????
    if?(OS.IsSP)?return?DialogClass;

    ????
    if?((style?&?SWT.TOOL)?!=?0)?{

    ???????
    int?trim?=?SWT.TITLE?|?SWT.CLOSE?|?SWT.MIN?|?SWT.MAX?|?SWT.BORDER?|?SWT.RESIZE;

    ???????
    if?((style?&?trim)?==?0)?return?display.windowShadowClass;

    ????}

    ????
    return?parent?!=?null???DialogClass?:?super.windowClass?();

    }


    因為這里涉及到其他一些變量,所以其意圖最初看上去可能不是很明確。總體的邏輯大概是這樣的:如果Shell發現用戶要創建的是一個對話框,那么將返回Dialog的內部類名。否則,調用上級類的實現(shadowClass則是SWT內部維護的一個需要特殊處理的類)。

    ?

    因為Shell的實現調用了基類,所以我們還是要往上走。Decorations、CanvasComposite都沒有重載windowClass()方法。繼續來到Scrollable類中,這個方法具有如下的實現:

    TCHAR?windowClass?()?{

    ????
    return?display.windowClass;

    }


    現在線索轉到了Display類。然而,windowClass只是Display類的一個字段,而非方法,這個字段一定是在哪個地方得到了初始化。問題就是:究竟在哪初始化的呢?

    ?

    好在,我們只需要在Display類查找哪里修改了windowClass字段就可以了。很快可以發現如下的方法:

    protected?void?init?()?{

    ????
    super.init?();

    ???????

    ????
    /*?Create?the?callbacks?*/

    ????windowCallback?
    =?new?Callback?(this,?"windowProc",?4);?//$NON-NLS-1$

    ????windowProc?
    =?windowCallback.getAddress?();

    ????
    if?(windowProc?==?0)?error?(SWT.ERROR_NO_MORE_CALLBACKS);

    ????…

    ????
    /*?Use?the?character?encoding?for?the?default?locale?*/

    ????windowClass?
    =?new?TCHAR?(0,?WindowName?+?WindowClassCount,?true);

    ????windowShadowClass?
    =?new?TCHAR?(0,?WindowShadowName?+?WindowClassCount,?true);

    ????WindowClassCount
    ++;

    上面代碼中用到了兩個相關字段:windowName是一個實例變量,其值為“SWT_Window”;而windowClassCount則是一個靜態變量,沒有說明初始值,那么就是默認值0

    ?

    稍稍分析一下就能明白:當init()方法第一次被調用的時候,windowClass將被設置為字符串“SWT_Window0”(你可以將TCHAR對象視為和字符串等同的東西),然后windowClassCount遞增。如果init()方法第二次被調用,那么下一個類名將會是SWT_Window1。不過,通常情況下我們的SWT程序僅有一個Display對象,也僅會初始化一次。也因此,所有頂層窗口的類名都應當是“SWT_Window0”。

    ?

    你可以用SPY++或者Winsight32之類的工具來證實這一點(如下圖)。

    知道了類名以后怎么辦呢?還是要從消息機制的原理上找到線索。而在Windows中將一個窗口類和窗口過程連接起來的關鍵是:調用RegisterClass或者RegisterClassEx,并將類名和窗口過程的地址作為參數一并傳入。所以,下面我們的目標是查找在哪里調用了RegisterClass。

    ?

    因為windowClass是定義在Display類中的,按照就近的原則,我們就從這里找起。果然不出所料,在init()方法接下來的部分就有這樣的代碼:

    /*?Register?the?SWT?window?class?*/

    ????
    int?hHeap?=?OS.GetProcessHeap?();

    ????
    int?hInstance?=?OS.GetModuleHandle?(null);

    ????WNDCLASS?lpWndClass?
    =?new?WNDCLASS?();

    ????lpWndClass.hInstance?
    =?hInstance;

    ????lpWndClass.lpfnWndProc?
    =?windowProc;

    ????lpWndClass.style?
    =?OS.CS_BYTEALIGNWINDOW?|?OS.CS_DBLCLKS;

    ????lpWndClass.hCursor?
    =?OS.LoadCursor?(0,?OS.IDC_ARROW);

    ????
    int?byteCount?=?windowClass.length?()?*?TCHAR.sizeof;

    ????lpWndClass.lpszClassName?
    =?OS.HeapAlloc?(hHeap,?OS.HEAP_ZERO_MEMORY,?byteCount);

    ????OS.MoveMemory?(lpWndClass.lpszClassName,?windowClass,?byteCount);

    ????OS.RegisterClass?(lpWndClass);


    init()方法的其他部分還注冊了另外一些輔助窗口,比如陰影窗口等;此外還注冊了一個全局鉤子。這些部分和消息機制的核心沒有直接關系,可以不去管它。關鍵在于這一行:

    ????lpWndClass.lpfnWndProc?=?windowProc;

    ?

    回頭看看,在init()方法的開頭部分,windowProc成員是這樣初始化的:

    ????/*?Create?the?callbacks?*/

    ????windowCallback?
    =?new?Callback?(this,?"windowProc",?4);?//$NON-NLS-1$

    ????windowProc?
    =?windowCallback.getAddress?();

    ????
    if?(windowProc?==?0)?error?(SWT.ERROR_NO_MORE_CALLBACKS);


    這里出現了一個神秘的類:Callback。有Windows 編程經驗的讀者大概會回想起,在Windows消息機制中,Callback是一個非常核心的概念。雖然Java程序員或許不熟悉它,不過事實上它可謂是Windows中的“控制反轉”或曰“依賴注入”——早在Java和模式大行其道之前很久,Windows中的一些手法已經暗合了最新的編程范式,只是當時沒有人給它起一個聽上去比較嚇人的名字而已。

    ?

    跑題了,回到正文上來。先不看Callback的實現,從這段代碼我們大概可以猜到:

    l???????? Callback類就是將OO的世界和非OO的世界連接起來的橋梁;

    l???????? Callback的構造函數中,提供了處理消息的目標對象和處理消息的方法名稱。最后那個參數4你不妨先猜猜看是什么意思;

    l???????? CallbackgetAddress()返回的應該是一個地址,也就是——你應當猜到了——正是回調函數的地址;

    l???????? Callback背后一定有某種魔法,把傳入的對象方法和getAddress返回的回調函數巧妙的連接起來。

    ?

    接下來,我們要進行的是這個歷程中最艱苦的部分:揭示Callback類背后的神秘魔法。

    (未完待續)?

    posted on 2006-06-03 12:46 TreeNode 閱讀(3480) 評論(10)  編輯  收藏 所屬分類: SWT,JFace和RCP 、Java技術

    Feedback

    # re: SWT: 深入內幕之消息機制探秘(上篇) 2006-06-04 09:01 Jet Geng
    強人。期待下一篇。  回復  更多評論
      

    # re: SWT: 深入內幕之消息機制探秘(上篇) 2006-06-04 16:41 foxcai
    樓主.
    你后面的圖片都沒有貼上來啊

    能不能處理一下啊.
      回復  更多評論
      

    # re: SWT: 深入內幕之消息機制探秘(上篇) 2006-06-04 17:32 TreeNode
    答樓上,我已經努力了一整天,不知是否因為文章太長格式復雜,這個HTML編輯器速度難以忍受而且頻頻出現腳本錯誤,上傳文件也失敗。我覺得很失望。或許我會想其他辦法解決。  回復  更多評論
      

    # re: SWT: 深入內幕之消息機制探秘(上篇) 2006-06-05 21:57 foxcai
    樓主表著急.
    慢慢來.
    期待你的下篇.  回復  更多評論
      

    # re: SWT: 深入內幕之消息機制探秘(上篇) 2006-06-05 22:43 foxcai
    看來樓主的Win編程也很厲害啊.
    看了樓主的文章感覺霍然開朗,有了些感覺.
    強烈期待下篇.

    樓主能不能寫一些關于SWT線程的文章呢?  回復  更多評論
      

    # re: SWT: 深入內幕之消息機制探秘(上篇) 2006-06-05 23:51 TreeNode
    今天上傳文件仍然失敗,我放棄了。全文做成PDF格式,有興趣的可以到這里下載:

    http://www.yousendit.com/transfer.php?action=download&ufid=19BF243E3E7F9D9C


    或者如果有Eclipse中文社區帳號的話,這里也可以:

    http://www.eclipseworld.org/bbs/read.php?tid=5132


    SWT的線程,只要了解Display對象提供的幾個同步方法,其他方面和一般的Java線程沒有什么差別了。Eclipse.org上面的文章也說得很明白,似乎沒有什么東西可寫的。  回復  更多評論
      

    # re: SWT: 深入內幕之消息機制探秘(上篇) 2006-06-09 03:01 foxcai
    已經下載了.

    希望樓主能有更多好的文章.  回復  更多評論
      

    # re: SWT: 深入內幕之消息機制探秘(上篇) 2006-06-19 11:15 hhh
    能否談談swing呢!  回復  更多評論
      

    # re: SWT: 深入內幕之消息機制探秘(上篇) 2006-06-19 19:13 TreeNode
    Swing我不熟悉,不評論。
      回復  更多評論
      

    # re: SWT: 深入內幕之消息機制探秘(上篇) 2007-06-29 12:39 SWT
    具體兩個線程間是如何通信的 ?能否給舉個實例!謝謝 !現在急用
    有的話!通知我一聲!非常感激!我的QQ是84750858  回復  更多評論
      

    主站蜘蛛池模板: 日本黄色动图免费在线观看| 美女被暴羞羞免费视频| 无码人妻AV免费一区二区三区| 男人的天堂亚洲一区二区三区 | 精品亚洲成A人无码成A在线观看 | 国产特黄一级一片免费| 国产亚洲精品成人a v小说| a级成人毛片免费视频高清| 亚洲电影一区二区三区| 久视频精品免费观看99| 亚洲一级毛片免观看| 嫩草视频在线免费观看| 国产精品亚洲专区一区| 亚洲色一色噜一噜噜噜| 大地资源中文在线观看免费版| 亚洲AV无码成人精品区蜜桃| 曰批全过程免费视频网址| 亚洲中文字幕久在线| 暖暖免费高清日本一区二区三区| 国产亚洲午夜精品| 亚洲综合无码精品一区二区三区| 日韩免费高清播放器| 亚洲欧洲尹人香蕉综合| 青青草国产免费久久久下载| 深夜特黄a级毛片免费播放| 亚洲婷婷五月综合狠狠爱| 在线观看的免费网站无遮挡| 亚洲色大情网站www| 亚洲午夜AV无码专区在线播放| 人人玩人人添人人澡免费| 亚洲av产在线精品亚洲第一站 | 在线亚洲精品自拍| 真实国产乱子伦精品免费| 亚洲av无一区二区三区| 亚洲午夜久久久久久噜噜噜| 97免费人妻无码视频| 免费在线观看一区| 亚洲欧洲在线播放| 中文字幕精品亚洲无线码二区| 在线a级毛片免费视频| 一级一级一片免费高清|