消息隊列
windows系統是通過消息驅動的,每移動一下鼠標,點擊一下屏幕都會產生一個消息。這些消息會先被放在windows的一個系統消息隊列(先進先出)中,windows系統會為每一個GUI線程創建一個線程消息隊列,然后系統會從系統消息隊列中取出一個消息放到對應的線程消息隊列中。之后通過消息循環從線程消息隊列中取出消息分發派遣到對應的窗口的窗口過程中。
線程消息隊列是如何創建的
系統消息隊列是由windows操作系統默認創建的,而對於普通的線程而言是沒有線程消息隊列的。線程剛創建時為普通線程,其KTHREAD.ServiceTable字段指向的是KerServiceDescriptorTable(SSDT)表,這個表包含的是ntoskrnl.exe服務函數,都是一些非GUI函數。如果我們在線程中調用第一個Windows的GUI函數時,線程的KTHREAD.ServiceTable字段會指向KerServiceDescriptorTable(Shadow SSDT)表,這個表不光包含ntoskrnl.exe服務函數還包含win32k.sys服務函數,后者包含了GUI函數。而且會分配空間為KTHREAD.win32Thread指針分配空間,由此指針就可以找到相應的線程消息隊列。
利用消息隊列進行IPC
因為線程消息隊列是在內核中的,所以可以通過消息隊列進行進程間通信(IPC)。當然進行進程間通訊的兩個進程必須有GUI線程才行,因為只有GUI線程才有線程消息隊列。
消息類型
系統保留從0x0000---0x03ff(WM_USER-1)的值用於系統定義的消息,而用戶自定義消息的范圍為0x0400(WM_USER)---0x7fff。
發送消息
消息隊列中的消息對應一個消息結構MSG,包含了消息的各個信息。我們在利用消息進行進程間通訊時是通過WPARAM和LPARAM傳遞訊息的。
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
DWORD lPrivate;
} MSG, *PMSG, *NPMSG, *LPMSG;
我們可以通過SendMessage或PostMessage發送消息,但是SendMessage直接將消息發送到對應的窗口過程中並不放在消息隊列里,所以我們要想通過消息隊列實現IPC應該使用PostMessage。
PostMessageA可以將消息發送到窗口句柄hWnd對應的線程消息隊列中,PostThreadMessageA可以將消息發送到線程句柄hThread對應的消息隊列中。(這里注意hWnd窗口句柄是屬於用戶對象的句柄一般通過FindWindow函數獲取,而hThread是內核對象的句柄一般通過跨進程共享內核對象完成)
BOOL PostMessageA(
HWND hWnd,
UINT Msg,
WPARAM wParam,
LPARAM lParam
);
獲取消息
獲取消息有一般使用PeekMessage或GetMessage。
PeekMessage可以檢索當前調用線程的線程消息隊列中指定的消息,並不指定檢索后如何處理線程隊列中的消息(刪除還是保留)。
BOOL PeekMessageA(
LPMSG lpMsg,
HWND hWnd,
UINT wMsgFilterMin,
UINT wMsgFilterMax,
UINT wRemoveMsg
);
GetMessage和PeekMessage一樣,只不過其會在檢索到消息后直接將消息從對應的線程消息隊列中刪除。
BOOL GetMessage(
LPMSG lpMsg,
HWND hWnd,
UINT wMsgFilterMin,
UINT wMsgFilterMax
);