SendMessage、PostMessage原理

SendMessagePostMessage原理windows

本文解說SendMessagePostMessage兩個函數的實現原理,分爲三個步驟進行解說,分別適合0基礎、中級、高級程序猿進行理解,三個步驟分別爲:函數

一、SendMessagePostMessage的執行機制。工具

二、SendMessagePostMessage的執行內幕。post

三、SendMessagePostMessage的內部實現。ui

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

 

1SendMessagePostMessage的執行機制.net

咱們先來看最簡單的。線程

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提供的工具本身研究了。

首先,在基本Win32project代碼中,咱們可以直接看到消息處理函數、消息循環,因此創建一個基本Win32project(本篇文章使用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

相關文章
相關標籤/搜索