SendMessage、PostMessage原理

SendMessagePostMessage原理

本文講解SendMessagePostMessage兩個函數的實現原理,分爲三個步驟進行講解,分別適合初級、中級、高級程序員進行理解,三個步驟分別爲:

1、SendMessagePostMessage的運行機制。

2、SendMessagePostMessage的運行內幕。

3、SendMessagePostMessage的內部實現。

注:理解這篇文章之前,必須先了解Windows的消息循環機制。

 

1SendMessagePostMessage的運行機制

我們先來看最簡單的。

SendMessage可以理解爲,SendMessage函數發送消息,等待消息處理完成後,SendMessage才返回。稍微深入一點,是等待窗口處理函數返回後,SendMessage就返回了。

PostMessage可以理解爲,PostMessage函數發送消息,不等待消息處理完成,立刻返回。稍微深入一點,PostMessage只管發送消息,消息有沒有被送到則並不關心,只要發送了消息,便立刻返回。

對於寫一般Windows程序的程序員來說,能夠這樣理解也就足夠了。但SendMessagePostMessage真的是一個發送消息等待、一個發送消息不等待嗎?具體細節,下面第2點將會講到。

 

2SendMessagePostMessage的運行內幕

在寫一般Windows程序時,如上第1點講到的足以應付,其實我們可以看看MSDN來確定SendMessagePostMessage的運行內幕。

MSDN中,SendMessage解釋如爲:The SendMessage function sends the specified message to a window or windows. It calls the window procedure for the specified window and does not return until the window procedure has processed the message.

翻譯成中文爲:SendMessage函數將指定的消息發到窗口。它調用特定窗口的窗口處理函數,並且不會立即返回,直到窗口處理函數處理了這個消息。

再看看PostMessage的解釋:The PostMessage function places (posts) a message in the message queue associated with the thread that created the specified window and returns without waiting for the thread to process the message.

    翻譯成中文爲:PostMessage函數將一個消息放入與創建這個窗口的消息隊列相關的線程中,並立刻返回不等待線程處理消息。

仔細看完MSDN解釋,我們瞭解到,SendMessage的確是發送消息,然後等待處理完成返回,但發送消息的方法爲直接調用消息處理函數(即WndProc函數),按照函數調用規則,肯定會等消息處理函數返回之後,SendMessage才返回。而PostMessage卻沒有發送消息,PostMessage是將消息放入消息隊列中,然後立刻返回,至於消息何時被處理,PostMessage完全不知道,此時只有消息循環知道被PostMessage的消息何時被處理了。

至此我們撥開了一層疑雲,原來SendMessage只是調用我們的消息處理函數,PostMessage只是將消息放到消息隊列中。下一節將會更深入這兩個函數,看看Microsoft究竟是如何實現這兩個函數的。

 

3SendMessagePostMessage的內部實現

Windows內部運行原理、機制往往是我們感興趣的東西,而這些東西又沒有被文檔化,所以我們只能使用Microsoft提供的工具自己研究了。

首先,在基本Win32工程代碼中,我們可以直接看到消息處理函數、消息循環,所以建立一個基本Win32工程(本篇文章使用VS2005),爲了看到更多信息,我們需要進行設置,讓VS2005載入MicrosoftSymbolpdb)文件[1]。爲了方便,去除了一些多餘的代碼,加入了兩個菜單,ID分別爲:IDM_SENDMESSAGEIDM_POSTMESSAGE。如下列出經過簡化後的必要的代碼。

消息循環:

Ln000while (GetMessage(&msg, NULL, 0, 0))

Ln001{

Ln002    TranslateMessage(&msg);

Ln003    DispatchMessage(&msg);

Ln004}

 

消息處理函數:

Ln100LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

Ln101{

Ln102    int wmId, wmEvent;

Ln103    switch (message)

Ln104    {

Ln105    case WM_COMMAND:

Ln106        wmId = LOWORD(wParam);

Ln107        wmEvent = HIWORD(wParam);

Ln108        switch (wmId)

Ln109        {

Ln110        case IDM_EXIT:

Ln111            DestroyWindow(hWnd);

Ln112            break;

Ln113        case IDM_SENDMESSAGE:

Ln114            SendMessage(hWnd, WM_SENDMESSAGE, 0, 0);

Ln115            break;

Ln116        case IDM_POSTMESSAGE:

Ln117            PostMessage(hWnd, WM_POSTMESSAGE, 0, 0);

Ln118            break;

Ln119        default:

Ln120            return DefWindowProc(hWnd, message, wParam, lParam);

Ln121        }

Ln122        break;

Ln123

Ln124   case WM_SENDMESSAGE:

Ln125        MessageBox(hWnd, L"SendMessage", L"Prompt", MB_OK);

Ln126        break;

Ln127

Ln128    case WM_POSTMESSAGE:

Ln129        MessageBox(hWnd, L"PostMessage", L"Prompt", MB_OK);

Ln130        break;

Ln131

Ln132    case WM_DESTROY:

Ln133        PostQuitMessage(0);

Ln134

Ln135    default:

Ln136        return DefWindowProc(hWnd, message, wParam, lParam);

Ln137    }

Ln138    return 0;

Ln139}

 

    下面一步步分析這兩個函數的內部情況,先討論 SendMessage

第一步,在DispatchMessageLn003)函數處下個斷點,F5進行調試,當程序運行到斷點後,查看 CallStack 窗口,可得如下結果:

#003MyProj.exe!wWinMain(HINSTANCE__ * hInstance=0x00400000, HINSTANCE__ * hPrevInstance=0x00000000, wchar_t * lpCmdLine=0x000208e0, int nCmdShow=0x00000001)  Line 49   C++

#002MyProj.exe!__tmainCRTStartup()  Line 589 + 0x35 bytes C

#001MyProj.exe!wWinMainCRTStartup()  Line 414 C

#000kernel32.dll!_BaseProcessStart@4()  + 0x23 bytes 

我們可以看到,進程先調用 kernel32.dll 中的 BaseProcessStart 函數,然後調用的 Startup Code 的函數 wWinMainCRTStartup,然後調用 _tmainCRTStartup 函數,最終調用我們的 wWinMain 函數,我們的程序就運行起來了。

 

第二步,去除第一步下的斷點,在 WndProcLn101) 函數入口處下個斷點,F5 繼續運行,運行到新下的斷點處,查看 CallStack 窗口,可得如下結果:

#008MyProj.exe!WndProc(HWND__ * hWnd=0x00050860, unsigned int message=0x00000101, unsigned int wParam=0x00000074, long lParam=0xc03f0001)  Line 122    C++

#007user32.dll!_InternalCallWinProc@20()  + 0x28 bytes   

#006user32.dll!_UserCallWinProcCheckWow@32()  + 0xb7 bytes   

#005user32.dll!_DispatchMessageWorker@8()  + 0xdc bytes  

#004user32.dll!_DispatchMessageW@4()  + 0xf bytes

#003MyProj.exe!wWinMain(HINSTANCE__ * hInstance=0x00400000, HINSTANCE__ * hPrevInstance=0x00000000, wchar_t * lpCmdLine=0x000208e0, #000int nCmdShow=0x00000001)  Line 49 + 0xc bytes C++

#002MyProj.exe!__tmainCRTStartup()  Line 589 + 0x35 bytes C

#001MyProj.exe!wWinMainCRTStartup()  Line 414 C

#000kernel32.dll!_BaseProcessStart@4()  + 0x23 bytes 

    #000~#003 跟第一步相同,不再解釋。在 #004#005,可以看到,函數運行到 DispatchMessage 的內部了,DispatchMessageWDispatchMessageWorker user32.dll 中到處的函數,而且函數前部字符串相等,在此猜想應該是 DispatchMessage 的內部處理。#008 爲我們消息處理函數,所以推想而知,#006#007 是爲了調用我們的消息處理函數而準備的代碼。

 

第三步,去除第二步下的斷點,在Ln003Ln114Ln115Ln125 處分別下一個斷點,在菜單中選擇對應項,使程序運行至 Ln114F10下一步,可以看到並沒有運行到 breakLn115),直接跳到了 Ln125 處,由此可知目前 SendMessage 已經在等待了,查看 CallStack 窗口,可得如下結果:

#013MyProj.exe!WndProc(HWND__ * hWnd=0x00050860, unsigned int message=0x00000500, unsigned int wParam=0x00000000, long lParam=0x00000000)  Line 147    C++

#012user32.dll!_InternalCallWinProc@20()  + 0x28 bytes   

#011user32.dll!_UserCallWinProcCheckWow@32()  + 0xb7 bytes   

#010user32.dll!_SendMessageWorker@20()  + 0xc8 bytes 

#009user32.dll!_SendMessageW@16()  + 0x49 bytes  

#008MyProj.exe!WndProc(HWND__ * hWnd=0x00050860, unsigned int message=0x00000111, unsigned int wParam=0x00008003, long lParam=0x00000000)  Line 136 + 0x15 bytes   C++

#007user32.dll!_InternalCallWinProc@20()  + 0x28 bytes   

#006user32.dll!_UserCallWinProcCheckWow@32()  + 0xb7 bytes   

#005user32.dll!_DispatchMessageWorker@8()  + 0xdc bytes  

#004user32.dll!_DispatchMessageW@4()  + 0xf bytes

#003MyProj.exe!wWinMain(HINSTANCE__ * hInstance=0x00400000, HINSTANCE__ * hPrevInstance=0x00000000, wchar_t * lpCmdLine=0x000208e0, #000int nCmdShow=0x00000001)  Line 49 + 0xc bytes C++

#002MyProj.exe!__tmainCRTStartup()  Line 589 + 0x35 bytes C

#001MyProj.exe!wWinMainCRTStartup()  Line 414 C

#000kernel32.dll!_BaseProcessStart@4()  + 0x23 bytes 

#000~#008 跟上面的相同,不再解釋。在 #009#010,可以看到,函數調用到 SendMessage 內部了,在此猜想應該是 SendMessage 的內部處理。#011#012 跟第二步中的 #006#007 一樣,在第二部中,這兩個函數是爲了調用消息處理函數而準備的代碼,#013 也是我們的消息處理函數,所以此兩行代碼的功能相等。

至此,我們證明了 SendMessage 的確是直接調用消息處理函數的,在消息處理函數返回前,SendMessage 等待。在所有的操作中,Ln003 斷點沒有去到,證明 SendMessage 不會將消息放入消息隊列中(在 PostMessage 分析中,此斷點將會跑到,接下來講述)。

 

第四步,F5繼續運行,此時彈出對話框,點擊對話框中的確定後,運行到斷點 Ln115 處。查看 CallStack 窗口,可得如下結果:

#008MyProj.exe!WndProc(HWND__ * hWnd=0x00050860, unsigned int message=0x00000111, unsigned int wParam=0x00008003, long lParam=0x00000000)  Line 137    C++

#007user32.dll!_InternalCallWinProc@20()  + 0x28 bytes   

#006user32.dll!_UserCallWinProcCheckWow@32()  + 0xb7 bytes   

#005user32.dll!_DispatchMessageWorker@8()  + 0xdc bytes  

#004user32.dll!_DispatchMessageW@4()  + 0xf bytes

#003MyProj.exe!wWinMain(HINSTANCE__ * hInstance=0x00400000, HINSTANCE__ * hPrevInstance=0x00000000, wchar_t * lpCmdLine=0x000208e0, int nCmdShow=0x00000001)  Line 49 + 0xc bytes   C++

#002MyProj.exe!__tmainCRTStartup()  Line 589 + 0x35 bytes C

#001MyProj.exe!wWinMainCRTStartup()  Line 414 C

#000kernel32.dll!_BaseProcessStart@4()  + 0x23 bytes 

#000~008 跟第二步的完全相同,此時 SendMessage 也已經返回,所調用的堆棧也清空了。

至此,我們徹底撥開了 SendMessage 的疑雲,瞭解了 SendMessage 函數的運行機制,綜述爲,SendMessage 內部調用 SendMessageWSendMessageWorker 函數做內部處理,然後調用 UserCallWinProcCheckWowInternalCallWinProc 來調用我們代碼中的消息處理函數,消息處理函數完成之後,SendMessage 函數便返回了。

 

SendMessage 討論完之後,現在討論 PostMessage,將上面的所有斷點刪除,關閉調試。

第一步,在DispatchMessageLn003)函數處下個斷點,F5進行調試,此處結果跟 SendMessage 一樣,不再說明。

第二步,去除第一步下的斷點,在 WndProcLn101) 函數入口處下個斷點,F5 繼續運行,此處結果跟 SendMessage 一樣,不再說明。

第三步,去除第二步下的斷點,在 Ln003Ln117Ln118Ln129 處分別下一個斷點,在菜單中選擇對應項,使程序運行至 Ln117F10 下一步,可以看到已經運行到 breakPostMessage 函數返回了,此時 CallStack 沒有變化。

第四步,F5 繼續運行,此時程序運行到 Ln003CallStack 跟第一步剛起來時一樣。

第五步,F5 繼續運行(由於有多個消息,可能要按多次),讓程序運行到 Ln129,此時 CallStack 跟第二步相同,爲了方便說明,再次列舉如下:

#008MyProj.exe!WndProc(HWND__ * hWnd=0x00070874, unsigned int message=0x00000501, unsigned int wParam=0x00000000, long lParam=0x00000000)  Line 151    C++

#007user32.dll!_InternalCallWinProc@20()  + 0x28 bytes   

#006user32.dll!_UserCallWinProcCheckWow@32()  + 0xb7 bytes   

#005user32.dll!_DispatchMessageWorker@8()  + 0xdc bytes  

#004user32.dll!_DispatchMessageW@4()  + 0xf bytes

#003MyProj.exe!wWinMain(HINSTANCE__ * hInstance=0x00400000, HINSTANCE__ * hPrevInstance=0x00000000, wchar_t * lpCmdLine=0x000208e0, int nCmdShow=0x00000001)  Line 49 + 0xc bytes   C++

#002MyProj.exe!__tmainCRTStartup()  Line 589 + 0x35 bytes C

#001MyProj.exe!wWinMainCRTStartup()  Line 414 C

#000kernel32.dll!_BaseProcessStart@4()  + 0x23 bytes 

由此可以看到,此調用是從消息循環中調用而來,DispatchMessageWDispatchMessageWorker DispatchMessage 的內部處理,UserCallWinProcCheckWowInternalCallWinProc是爲了調用我們的消息處理函數而準備的代碼。

至此,我們再次徹底撥開了 PostMessage 的疑雲,瞭解了 PostMessage 函數的運行機制,綜述爲,PostMessage 將消息放入消息隊列中,自己立刻返回,消息循環中的 GetMessagePeekMessage 也可,本例中爲演示)處理到我們發的消息之後,便按照普通消息處理方法進行處理。

 

 

------------------------------------

[1]關於如何設置,讓VS2005載入Symbol,可以查看我寫的另外一篇文章:“讓Visual Studio載入Symbolpdb)文件”,地址:http://blog.csdn.net/xt_xiaotian/archive/2010/03/16/5384111.aspx

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章