windows API視窗訊息分流器

來源:互聯網
上載者:User

對於熟悉Win API編程的同志們來說,windowsx.h這個標頭檔應該不會太陌生吧,這次要講的內容就來自這個windowsx.h標頭檔。

經常能在msdn上查到這樣一些函數,明明是個函數,而且模樣長得和一般的api函數也一樣一樣的,可卻叫做macro,為什麼呢?留意一下函數使用的requirement,你會發現,它的聲明正是在windowsx.h這個標頭檔裡。

Windowsx.h包含了這樣一些內容:
宏API,視窗訊息分流器,控制項API;

所 有的這些宏定義,可以使你的程式更加安全,簡潔,結構更清晰,大大提高程式的可讀性;其中視窗訊息分流器(message cracker)是我們今天要討論的話題,它可以使我們的API程式變得更簡潔。下面就進入我們的主題:(有關windowsx.h的更多內容,可以參考 MS Knowledge Base Article #83456.)

訊息分流器是Windows提供的一組宏定義,它的兩個最大的作用,用MS的話來說,就是:

● 安全的資料類型,因為訊息分流器完成了大量的類型轉換的工作;
● 使程式向32位windows的轉化更簡單;

當然,使用訊息分流器會大大改變程式的面貌,你也可以選擇不使用它。

下面我們就以一個對話方塊視窗的訊息處理過程為例,看看訊息分流器到底是怎麼運作的。

1.訊息分流器的基本使用
先看一個普通的視窗訊息處理函數,它可能需要處理一些視窗的初始化,無效客戶區重繪等訊息:

LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, 
WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CREATE:
// ...
return 0;

case WM_PAINT:
// ...
return 0;

case WM_DESTROY:
//...
return 0;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}

而通過使用訊息分流器,我們可以把每個case都寫到相應的訊息處理函數中,就像下面這樣:

LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, 
WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CREATE:
return HANDLE_WM_CREATE(hwnd, wParam, lParam, Cls_OnCreate);

case WM_PAINT:
return HANDLE_WM_PAINT(hwnd, wParam, lParam, Cls_OnPaint);

case WM_DESTROY:
return HANDLE_WM_DESTROY(hwnd, wParam, lParam, Cls_OnDestroy);
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}

這裡用到了三個宏定義:HANDLE_WM_CREATE, HANDLE_WM_PAINT, HANDLE_WM_DESTROY;這三個宏定義就是我們的三個訊息分流器(別看叫什麼分流器,說穿了也不值幾個錢,呵呵),它們在windowsx.h中的定義如下:

#define HANDLE_WM_CREATE(hwnd, wParam, lParam, fn) 
((fn)((hwnd), (LPCREATESTRUCT)(lParam)) ? 0L : (LRESULT)-1L)
#define HANDLE_WM_PAINT(hwnd, wParam, lParam, fn) 
((fn)(hwnd), 0L)
#define HANDLE_WM_DESTROYCLIPBOARD(hwnd, wParam, lParam, fn) 
((fn)(hwnd), 0L)

把這三個宏定義替換回去,就變成:

LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, 
WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CREATE:
return Cls_OnCreate(hwnd, (LPCREATESTRUCT)(lParam) ? 0L : (LRESULT)-1L;
// 如果處理了訊息,則Cls_OnCreate應返回TRUE,導致WndProc返回0,否則Cls_OnCreate返回FALSE,導致WndProc返回-1;
case WM_PAINT:
return Cls_OnPaint(hwnd), 0L;
// 逗號運算式;Cls_OnPaint是void類型,這裡返回0; 
case WM_DESTROY:
return Cls_OnDestroy(hwnd), 0L; // 同Cls_OnPaint

return DefWindowProc(hwnd, msg, wParam, lParam);
}

之後我們就可以按照訊息分流器的定義編寫相應的訊息處理函數了:

BOOL Cls_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct){…};
void Cls_OnPaint(HWND hwnd){…};
void Cls_OnDestroyClipboard(HWND hwnd){…};

windowsx.h還提供了一個更加簡化的方法:使用HANDLE_MSG宏,這個宏是這樣定義的:

#define HANDLE_MSG(hwnd, message, fn) 
case (message): return HANDLE_##message((hwnd), (wParam), (lParam), (fn))

這個宏要做的就是根據不同的message(##用來串連前後的字串),把自己“變成”相應的HANDLE_XXXXMESSAGE形式的宏,再通過相應的宏來執行訊息處理代碼;
比如實際代碼中寫入:

HANDLE_MSG(hwnd, WM_CREATE, Cls_OnCreate)

則經過轉換就變成:

case (WM_CREATE): return HANDLE_WM_CREATE((hwnd), (wParam), (lParam), (Cls_OnCreate))

這樣,我們就可以直接把程式寫為:
LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, 
WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
HANDLE_MSG(hwnd, WM_CREATE, Cls_OnCreate);
HANDLE_MSG(hwnd, WM_PAINT, Cls_OnPaint);
HANDLE_MSG(hwnd, WM_DESTROY, Cls_OnDestroy);
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}

之 後直接編寫相應的訊息處理過程就可以了。是不是簡潔多了?而且把訊息處理封裝到函數裡面,就可以使用VS直接跳轉到這個函數,再也不用費勁去找那個 case了。要注意的一點是,雖然windowsx.h裡包括了所有訊息對應的分流器,但它們的參數是宏定義顯式說明的,在編寫訊息處理函數時,必須遵循 宏定義中的參數類型,否則會導致錯誤;這麼多訊息分流器,我們每次新寫一個訊息處理函數時就得看看是否把參數設定正確了,整個過程繁瑣冗長。好在已經有一 個工具叫Message Cracker Wizard,可以協助我們產生訊息分流器和相關的處理過程,具體見:http://www.codeproject.com/win32/msgcrackwizard.asp。

2.在對話方塊中使用訊息分流器
在對話方塊訊息處理中,視窗子類化是我們經常使用的手段,這也可以通過訊息分流器實現,但是有點小問題 :>
下面是一個使用了windowsx.h訊息分流器的對話方塊及其處理過程:
……
int WINAPI _tWinMain(HINSTANCE hinstExe, HINSTANCE, PTSTR pszCmdLine, int) 
{
DialogBoxParam(
hinstExe, MAKEINTRESOURCE(IDD_PASSTHRU), NULL, (DLGPROC)Dlg_Proc, 0);

return(0);
}
……

LRESULT CALLBACK Dlg_Proc (HWND hwnd, UINT msg, 
WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
HANDLE_MSG(hwnd, WM_INITDIALOG, Cls_OnInitDialog); // 不能直接使用HANDLE_MSG宏
HANDLE_MSG(hwnd, WM_COMMAND, Cls_OnCommand); // 不能直接使用HANDLE_MSG宏
}

return false;
}

以上程式中直接使用HANDLE_MSG可能導致錯誤;為什麼呢?問題出在子類化的訊息處理過程的傳回值上,msdn中對於對話方塊訊息處理過程的傳回值有如下說明:

一般情況下,對話方塊過程函數應該在處理了訊息的情況下返回TRUE,如果沒有處理,則返回FALSE。如果對話方塊過程返回了FALSE,那麼對話方塊管理器為這條訊息準備預設的對話操作。

如 果對話方塊處理了一個需要特定傳回值的訊息,則對話方塊的傳回值應該被設定為調用SetWindowLong(The SetWindowLong function changes an attribute of the specified window. The function also sets a 32-bit (long) value at the specified offset into the extra window memory of a window. )後的傳回值,並在返回TRUE之前立即返回這個 值。注意你必須立即調用SetWindowLong(這個函數用於調用視窗子類化的過程),這會導致DWL_MSGRESULT值被一個嵌套的對話方塊訊息 改寫。傳回值為特定值的訊息有:
• WM_CHARTOITEM 
• WM_COMPAREITEM 
• WM_CTLCOLORBTN 
• WM_CTLCOLORDLG 
• WM_CTLCOLOREDIT 
• WM_CTLCOLORLISTBOX 
• WM_CTLCOLORSCROLLBAR 
• WM_CTLCOLORSTATIC 
• WM_INITDIALOG 
• WM_QUERYDRAGICON 
• WM_VKEYTOITEM
看到沒有? 我們的訊息WM_INITDIALOG也在其中,對這個訊息進行處理的過程不能簡單的返回TRUE表示對訊息進行了處理,而是另有其意;它將轉化為:

case (WM_INITDIALOG): return HANDLE_WM_INITDIALOG(hwnd, wParam, lParam, Cls_OnInitDialog);

宏HANDLE_WM_INITDIALOG定義如下:

#define HANDLE_WM_INITDIALOG(hwnd, wParam, lParam, fn) 
(LRESULT)(DWORD)(UINT)(BOOL)(fn)((hwnd), (HWND)(wParam), lParam)

對WM_INITDIALOG的處理,如果返回TRUE,則表示設定鍵盤焦點到對話方塊的預設控制項,否則返回FALSE;這時好像還看不出什麼問題,而對於我們的另外一個訊息WM_COMMAND,HANDLE_MSG簡單的把它變成:

case (WM_COMMAND): return HANDLE_WM_COMMAND(hwnd, wParam, lParam, Cls_OnCommand);

宏HANDLE_WM_COMMAND定義如下:

#define HANDLE_WM_COMMAND(hwnd, wParam, lParam, fn) 
((fn)((hwnd), (int)(LOWORD(wParam)), (HWND)(lParam), (UINT)HIWORD(wParam)), 0L)

問 題出來了,我們的Cls_OnCommand由於是個void型的函數,是沒有傳回值的,因此windows預設這種訊息處理過程必須返回一個0值,而返 回0值不就表示我們的訊息過程不處理這個訊息嗎?這個矛盾是HANDLE_MSG無法解決的。怎麼辦才能使訊息過程在處理完WM_COMMAND訊息之後 正確的返回一個TRUE呢? 答案是使用另一個windowsx.h中的宏:SetDlgMsgResult(hwnd, msg, result) 

這個宏定義如下:

#define SetDlgMsgResult(hwnd, msg, result) (( 
(msg) == WM_CTLCOLORMSGBOX || 
(msg) == WM_CTLCOLOREDIT || 
(msg) == WM_CTLCOLORLISTBOX || 
(msg) == WM_CTLCOLORBTN || 
(msg) == WM_CTLCOLORDLG || 
(msg) == WM_CTLCOLORSCROLLBAR || 
(msg) == WM_CTLCOLORSTATIC || 
(msg) == WM_COMPAREITEM || 
(msg) == WM_VKEYTOITEM || 
(msg) == WM_CHARTOITEM || 
(msg) == WM_QUERYDRAGICON || 
(msg) == WM_INITDIALOG 
) ? (BOOL)(result) : (SetWindowLongPtr((hwnd), DWLP_MSGRESULT, (LPARAM)(LRESULT)(result)), TRUE))

(有沒有注意到,裡面多了一個WM_CTLCOLORMSGBOX ? 這個訊息是16位WinAPI中的訊息,一度被轉換為Win32 API的一個訊息;現在在最新的32位API中已經被刪除了;保留它可能考慮到相容性的問題,這裡不做進一步討論)
現 在看到了,如果對話方塊過程處理的訊息恰巧為返回特定值中的一個,則如實返回result;不要被前面的BOOL蒙蔽,BOOL在標頭檔中的定義實際上是一 個int型,一旦需要返回非TRUE或FALSE的其他值,照樣可以;這樣,我們的Cls_OnInitDialog就能夠正確的返回它的BOOL值了, 而Cls_OnCommand在處理之後,也可以由後面的逗號運算式正確的返回一個TRUE表示訊息已處理。

在《Windows核心編程》一書中,大牛Jeffrey自己定義了一個宏,使SetDlgMsgResult宏的使用更加方便:

#define chHANDLE_DLGMSG(hwnd, message, fn) 
case (message): return (SetDlgMsgResult(hwnd, uMsg, 
HANDLE_##message((hwnd), (wParam), (lParam), (fn)))) 

可見這個宏只是簡單的對SetDlgMsgRseult宏進行了封裝。

這樣,我們最終的代碼可以寫成:

LRESULT CALLBACK Dlg_Proc (HWND hwnd, UINT msg, 
WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
chHANDLE_DLGMSG(hwnd, WM_INITDIALOG, Cls_OnInitDialog); // 使用大牛的chHANDLE_DLGMSG宏
chHANDLE_DLGMSG(hwnd, WM_COMMAND, Cls_OnCommand); 
}

return false;
}

下面把原來程式整個架構列出來:

LRESULT CALLBACK Dlg_Proc(HWND hwnd, UNIT umsg, WPARAM wparam, LPARAM lparam)
{
switch(msg) 
{
case WM_COMMAND: // 每個case都被一個message cracker代替,這裡使用大牛同志的
// do something; // chHANDLE_DLGMSG宏;這個宏負責對訊息篩選,處理並返回相應的值
return true;

case WM_INITDIALOG: 
// do something;
return xxxx;
}

return false; // 如果訊息不在我們的DlgProc過程中被處理,則告訴調用這個DlgProc的訊息,
} //告訴系統的對話方塊管理器,這個訊息我們不處理,交給你了

對比一下,訊息分流器的作用不言自明。

以上只是介紹了訊息分流器的部分應用,更多創造性的用法還等你自己在實踐中發掘。

下面列出一些有用的參考資料:

http://support.microsoft.com/default.aspx?scid=kb;en-us;83456 介紹了STRICT宏定義以及windowsx.h
http://www.codeproject.com/win32/msgcrackwizard.asp 提供message cracker wizard的下載,而且附有原始碼
《windows核心編程》windows系統編程,就跟定大牛了 :> 他在自己的sample中大量使用了message cracker

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.