SendMessage、PostMessage原理

SendMessage、PostMessage原理程序員

 

 

SendMessage、PostMessage原理windows

 

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

 

一、SendMessage、PostMessage的運行機制。工具

 

二、SendMessage、PostMessage的運行內幕。post

 

三、SendMessage、PostMessage的內部實現。ui

 

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

 

 

 

1、SendMessage、PostMessage的運行機制.net

 

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

 

SendMessage能夠理解爲,SendMessage函數發送消息,等待消息處理完成後,SendMessage才返回。稍微深刻一點,是等待窗口處理函數返回後,SendMessage就返回了。翻譯

 

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

 

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

 

 

 

2、SendMessage、PostMessage的運行內幕

 

在寫通常Windows程序時,如上第1點講到的足以應付,其實咱們能夠看看MSDN來肯定SendMessage、PostMessage的運行內幕。

 

在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到底是如何實現這兩個函數的。

 

 

 

3、SendMessage、PostMessage的內部實現

 

Windows內部運行原理、機制每每是咱們感興趣的東西,而這些東西又沒有被文檔化,因此咱們只能使用Microsoft提供的工具本身研究了。

 

首先,在基本Win32工程代碼中,咱們能夠直接看到消息處理函數、消息循環,因此創建一個基本Win32工程(本篇文章使用VS2005),爲了看到更多信息,咱們須要進行設置,讓VS2005載入Microsoft的Symbol(pdb)文件[1]。爲了方便,去除了一些多餘的代碼,加入了兩個菜單,ID分別爲:IDM_SENDMESSAGE、IDM_POSTMESSAGE。以下列出通過簡化後的必要的代碼。

 

消息循環:

 

Ln000:while (GetMessage(&msg, NULL, 0, 0))

 

Ln001:{

 

Ln002:    TranslateMessage(&msg);

 

Ln003:    DispatchMessage(&msg);

 

Ln004:}

 

 

 

消息處理函數:

 

Ln100:LRESULT 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。

 

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

 

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

 

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

 

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

 

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

 

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

 

 

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

    #000~#003 跟第一步相同,再也不解釋。在 #00四、#005,能夠看到,函數運行到 DispatchMessage 的內部了,DispatchMessageW、DispatchMessageWorker 是 user32.dll 中處處的函數,並且函數前部字符串相等,在此猜測應該是 DispatchMessage 的內部處理。#008 爲咱們消息處理函數,因此推想而知,#00六、#007 是爲了調用咱們的消息處理函數而準備的代碼。

 

 

 

第三步,去除第二步下的斷點,在Ln00三、Ln11四、Ln11五、Ln125 處分別下一個斷點,在菜單中選擇對應項,使程序運行至 Ln114,F10下一步,能夠看到並無運行到 break(Ln115),直接跳到了 Ln125 處,由此可知目前 SendMessage 已經在等待了,查看 CallStack 窗口,可得以下結果:

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

#000~#008 跟上面的相同,再也不解釋。在 #00九、#010,能夠看到,函數調用到 SendMessage 內部了,在此猜測應該是 SendMessage 的內部處理。#0十一、#012 跟第二步中的 #00六、#007 同樣,在第二部中,這兩個函數是爲了調用消息處理函數而準備的代碼,#013 也是咱們的消息處理函數,因此此兩行代碼的功能相等。

 

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

 

 

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

至此,咱們完全撥開了 SendMessage 的疑雲,瞭解了 SendMessage 函數的運行機制,綜述爲,SendMessage 內部調用 SendMessageW、SendMessageWorker 函數作內部處理,而後調用 UserCallWinProcCheckWow、InternalCallWinProc 來調用咱們代碼中的消息處理函數,消息處理函數完成以後,SendMessage 函數便返回了。

 

 

 

SendMessage 討論完以後,如今討論 PostMessage,將上面的全部斷點刪除,關閉調試。

 

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

 

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

 

第三步,去除第二步下的斷點,在 Ln00三、Ln11七、Ln11八、Ln129 處分別下一個斷點,在菜單中選擇對應項,使程序運行至 Ln117,F10 下一步,能夠看到已經運行到 break,PostMessage 函數返回了,此時 CallStack 沒有變化。

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

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

 

由此能夠看到,此調用是從消息循環中調用而來,DispatchMessageW、DispatchMessageWorker 是 DispatchMessage 的內部處理,UserCallWinProcCheckWow、InternalCallWinProc是爲了調用咱們的消息處理函數而準備的代碼。

 

至此,咱們再次完全撥開了 PostMessage 的疑雲,瞭解了 PostMessage 函數的運行機制,綜述爲,PostMessage 將消息放入消息隊列中,本身馬上返回,消息循環中的 GetMessage(PeekMessage 也可,本例中爲演示)處理到咱們發的消息以後,便按照普通消息處理方法進行處理。

 

 

 

 

 

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

 

[1]關於如何設置,讓VS2005載入Symbol,能夠查看我寫的另一篇文章:「讓Visual Studio載入Symbol(pdb)文件」,地址:http://blog.csdn.net/xt_xiaotian/archive/2010/03/16/5384111.aspx

相關文章
相關標籤/搜索