Windows消息传递机制详解
? ? ? Windows是一個消息(Message)驅動系統。Windows的消息提供了應用程序之間、應用程序與Windows系統之間進行通信的手段。應用程序想要實現的功能由消息來觸發,并且靠對消息的響應和處理來完成。必須注意的是,消息并非是搶占性的,無論事件的緩急,總是按照到達的先后派對,依次處理(一些系統消息除外),這樣可能使一些實時外部事件得不到及時處理。
????? Windows的應用程序一般包含窗口(Window),它主要為用戶提供一種可視化的交互方式,窗口是總是在某個線程(Thread)內創建的。Windows系統通過消息機制來管理交互,消息(Message)被發送,保存,處理,一個線程會維護自己的一套消息隊列(Message Queue),以保持線程間的獨占性。隊列的特點無非是先進先出,這種機制可以實現一種異步的需求響應過程。
?
目錄:
1、消息
2? 、消息類型
3 、消息隊列(Message Queues)
4 、隊列消息(Queued Messages)和非隊列消息(Non-Queued Messages)
5 、PostMessage(PostThreadMessage), SendMessage
6 、GetMessage, PeekMessage
7 、TranslateMessage, TranslateAccelerator
8、(消息死鎖( Message Deadlocks)
9、BroadcastSystemMessage
10、消息的處理
11、MFC的消息映射
12、消息反射機制
?
1、消息
??? 消息系統對于一個win32程序來說十分重要,它是一個程序運行的動力源泉。一個消息,是系統定義的一個32位的值,他唯一的定義了一個事件,向Windows發出一個通知,告訴應用程序某個事情發生了。例如,單擊鼠標、改變窗口尺寸、按下鍵盤上的一個鍵
都會使Windows發送一個消息給應用程序。
??? 消息本身是作為一個記錄傳遞給應用程序的,這個記錄中包含了消息的類型以及其他信息。例如,對于單擊鼠標所產生的消息來
說,這個記錄中包含了單擊鼠標時的坐標。這個記錄類型叫做MSG,MSG含有來自windows應用程序消息隊列的消息信息,它在
Windows中聲明如下:??
typedef struct tagMsg??
{??
HWND hwnd;????????? // 接受該消息的窗口句柄??
UINT message;???????? // 消息常量標識符,也就是我們通常所說的消息號??
WPARAM wParam;???? // 32位消息的特定附加信息,確切含義依賴于消息值??
LPARAM lParam;?????? // 32位消息的特定附加信息,確切含義依賴于消息值??
DWORD time;???????? // 消息創建時的時間??
POINT pt;???????????? // 消息創建時的鼠標/光標在屏幕坐標系中的位置??
}MSG;??
? ? 消息可以由系統或者應用程序產生。系統在發生輸入事件時產生消息。舉個例子, 當用戶敲鍵, 移動鼠標或者單擊控件。系統也產生消息以響應由應用程序帶來的變化, 比如應用程序改變系統字體,改變窗體大小。應用程序可以產生消息使窗體執行任務,或者與其他應用程序中的窗口通訊。
2、消息類型
1) 系統定義消息(System-Defined Messages)?
? ? ? ? 在SDK中事先定義好的消息,非用戶定義的,其范圍在[0x0000, 0x03ff]之間, 可以分為以下三類:??
1> 窗口消息(Windows Message)?
? ? ? ?與窗口的內部運作有關,如創建窗口,繪制窗口,銷毀窗口等。可以是一般的窗口,也可以是Dialog,控件等。??
如:WM_CREATE, WM_PAINT, WM_MOUSEMOVE, WM_CTLCOLOR, WM_HSCROLL...??
2> 命令消息(Command Message)?
? ? ? ? 與處理用戶請求有關, 如單擊菜單項或工具欄或控件時, 就會產生命令消息。??
WM_COMMAND, LOWORD(wParam)表示菜單項,工具欄按鈕或控件的ID。如果是控件, HIWORD(wParam)表示控件消息類型??
3> 控件通知(Notify Message)?
? ? ? ? 控件通知消息, 這是最靈活的消息格式, 其Message, wParam, lParam分別為:WM_NOTIFY, 控件ID,指向NMHDR的指針。NMHDR包含控件通知的內容, 可以任意擴展。??
2) 程序定義消息(Application-Defined Messages)?
? ? ? ? 用戶自定義的消息, 對于其范圍有如下規定:??
WM_USER: 0x0400-0x7FFF????? (ex. WM_USER+10)??
WM_APP(winver> 4.0): 0x8000-0xBFFF (ex.WM_APP+4)??
RegisterWindowMessage: 0xC000-0xFFFF
3、消息隊列(Message Queues)
?Windows中有兩種類型的消息隊列??
1) 系統消息隊列(System Message Queue)?
? ? ? ? 這是一個系統唯一的Queue,設備驅動(mouse, keyboard)會把操作輸入轉化成消息存在系統隊列中,然后系統會把此消息放到目標窗口所在的線程的消息隊列(thread-specific message queue)中等待處理??
2) 線程消息隊列(Thread-specific Message Queue)?
? ? ? ? 每一個GUI線程都會維護這樣一個線程消息隊列。(這個隊列只有在線程調用GDI函數時才會創建,默認不創建)。然后線程消息隊列中的消息會被送到相應的窗口過程(WndProc)處理.??
注意: 線程消息隊列中WM_PAINT,WM_TIMER只有在Queue中沒有其他消息的時候才會被處理,WM_PAINT消息還會被合并以提高效率。其他所有消息以先進先出(FIFO)的方式被處理。
4、隊列消息(Queued Messages)和非隊列消息(Non-Queued Messages)
1)隊列消息(Queued Messages)?
? ? ? ? 消息會先保存在消息隊列中,消息循環會從此隊列中取消息并分發到各窗口處理 ?、如鼠標,鍵盤消息。??
2) 非隊列消息(NonQueued Messages)?
? ? ? ? 消息會繞過系統消息隊列和線程消息隊列直接發送到窗口過程被處理? 如: WM_ACTIVATE, WM_SETFOCUS, WM_SETCURSOR, WM_WINDOWPOSCHANGED??
注意: postMessage發送的消息是隊列消息,它會把消息Post到消息隊列中; SendMessage發送的消息是非隊列消息, 被直接送到窗口過程處理.
隊列消息和非隊列消息的區別?
? ? ? ? 從消息的發送途徑來看,消息可以分成2種:隊列消息和非隊列消息。消息隊列由可以分成系統消息隊列和線程消息隊列。系統消息隊列由Windows維護,線程消息隊列則由每個GUI線程自己進行維護,為避免給non-GUI現成創建消息隊列,所有線程產生時并沒有消息隊列,僅當線程第一次調用GDI函數時系統才給線程創建一個消息隊列。隊列消息送到系統消息隊列,然后到線程消息隊列;非隊列消息直接送給目的窗口過程。?
???? 對于隊列消息,最常見的是鼠標和鍵盤觸發的消息,例如WM_MOUSERMOVE,WM_CHAR等消息,還有一些其它的消息,例如:WM_PAINT、 WM_TIMER和WM_QUIT。當鼠標、鍵盤事件被觸發后,相應的鼠標或鍵盤驅動程序就會把這些事件轉換成相應的消息,然后輸送到系統消息隊列,由 Windows系統去進行處理。Windows系統則在適當的時機,從系統消息隊列中取出一個消息,根據前面我們所說的MSG消息結構確定消息是要被送往那個窗口,然后把取出的消息送往創建窗口的線程的相應隊列,下面的事情就該由線程消息隊列操心了,Windows開始忙自己的事情去了。線程看到自己的消息隊列中有消息,就從隊列中取出來,通過操作系統發送到合適的窗口過程去處理。?
???? 一般來講,系統總是將消息Post在消息隊列的末尾。這樣保證窗口以先進先出的順序接受消息。然而,WM_PAINT是一個例外,同一個窗口的多個 WM_PAINT被合并成一個 WM_PAINT 消息, 合并所有的無效區域到一個無效區域。合并WM_PAIN的目的是為了減少刷新窗口的次數。
? ? ? 非隊列消息將會繞過系統隊列和消息隊列,直接將消息發送到窗口過程,。系統發送非隊列消息通知窗口,系統發送消息通知窗口。例如,當用戶激活一個窗口系統發送WM_ACTIVATE, WM_SETFOCUS, and WM_SETCURSOR。這些消息通知窗口它被激活了。非隊列消息也可以由當應用程序調用系統函數產生。例如,當程序調用SetWindowPos系統發送WM_WINDOWPOSCHANGED消息。一些函數也發送非隊列消息,例如下面我們要談到的函數。
5 、PostMessage(PostThreadMessage), SendMessage?
? ? ? ? PostMessage:把消息放到指定窗口所在的線程消息隊列中后立即返回。 PostThreadMessage:把消息放到指定線程的消息隊列中后立即返回。??
? ? ? ? SendMessage:直接把消息送到窗口過程處理, 處理完了才返回。
PostMessage(異步)和SendMessage(同步)的區別
a、 PostMessage 是異步的,SendMessage 是同步的。
? ? ? ? ?PostMessage 只把消息放到隊列,不管消息是不是被處理就返回,消息可能不被處理;
? ? ? ? SendMessage等待消息被處理完了才返回,如果消息不被處理,發送消息的線程將一直處于阻塞狀態,等待消息的返回。
b、 同一個線程內:
? ? ? ? ? SendMessage 發送消息時,由USER32.DLL模塊調用目標窗口的消息處理程序,并將結果返回,SendMessage 在同一個線程里面發送消息不進入線程消息隊列;PostMessage 發送的消息要先放到消息隊列,然后通過消息循環分派到目標窗口(DispatchMessage)。
c、不同線程:
???????????? SendMessage 發送消息到目標窗口的消息隊列,然后發送消息的線程在USER32。DLL模塊內監視和等待消息的處理結果,直到目標窗口的才處理返回,SendMessage在返回之前還需要做許多工作,如響應別的線程向它發送的SendMessage().PostMessge() 到別的線程的時候最好使用PostThreadMessage?? 代替。PostMessage()的HWND 參數可以為NULL,相當于PostThreadMessage() + GetCrrentThreadId.
d、系統處理消息。
? ? ? ?系統只處理(marshal)系統消息(0--WM_USER),發送用戶消息(用戶自己定義)時需要用戶自己處理。
? ? ? ? 使用PostMessage,SendNotifyMessage,SendMessageCallback等異步函數發送系統消息時,參數不可以使用指針,因為發送者不等待消息的處理就返回,接收者還沒有處理,指針就有可能被釋放了,或則內容變化了。
e、在Windows 2000/XP,每個消息隊列最多只能存放一定數量的消息,超過的將不會被處理就丟掉。系統默認是10000;:[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows] USERPostMessageLimit
6 、GetMessage, PeekMessage?
PeekMessage會立即返回??? 可以保留消息??
GetMessage在有消息時返回? 會刪除消息
PeekMessage和GetMessage函數的主要區別有:?
? ? ? ? a. GetMessage的主要功能是從消息隊列中“取出”消息,消息被取出以后,就從消息隊列中將其刪除;而PeekMessage的主要功能是“窺視”消息,如果有消息,就返回true,否則返回false。也可以使用PeekMessage從消息隊列中取出消息,這要用到它的一個參數(UINT wRemoveMsg),如果設置為PM_REMOVE,消息則被取出并從消息隊列中刪除;如果設置為PM_NOREMOVE,消息就不會從消息隊列中取出。?
? ? ? ? b. 如果GetMessage從消息隊列中取不到消息,則線程就會被操作系統掛起,等到OS重新調度該線程時,兩者的性質不同:使用GetMessage線程仍會被掛起,使用PeekMessage線程會得到CPU的控制權,運行一段時間。?
? ? ? ? c、GetMessage每次都會等待消息,直到取到消息才返回;而PeekMessage只是查詢消息隊列,沒有消息就立即返回,從返回值判斷是否取到了消息。?
我們也可以說,PeekMessage是一個具有線程異步行為的函數,不管消息隊列中是否有消息,函數都會立即返回。而GetMessage則是一個具有線程同步行為的函數,如果消息隊列中沒有消息的話,函數就會一直等待,直到消息隊列中至少有一條消息時才返回。?
如果消息隊列中沒有消息,PeekMessage總是能返回,這就相當于在執行一個循環,如果消息隊列一直為空, 它就進入了一個死循環。GetMessage則不可能因為消息隊列為空而進入死循環。
聯系:
? ? ? ? 在Windows的內部,GetMessage和PeekMessage執行著相同的代碼,Peekmessage和Getmessage都是向系統的消息隊列中取得消息,并將其放置在指定的結構。
區別:
PeekMessage:有消息時返回TRUE,沒有消息返回FALSE?
GetMessage:有消息時且消息不為WM_QUIT時返回TRUE,如果有消息且為WM_QUIT則返回FALSE,沒有消息時不返回。
GetMessage:取得消息后,刪除除WM_PAINT消息以外的消息。
PeekMessage:取得消息后,根據wRemoveMsg參數判斷是否刪除消息。PM_REMOVE則刪除,PM_NOREMOVE不刪除。
The PeekMessage function normally does not remove WM_PAINT messages from the queue. WM_PAINT messages remain in the queue until they are processed. However, if a WM_PAINT message has a null update region, PeekMessage does remove it from the queue.
不能用PeekMessage從消息隊列中刪除WM_PAINT消息,從隊列中刪除WM_PAINT消息可以令窗口顯示區域的失效區域變得有效(刷新窗口),如果隊列中包含WM_PAINT消息程序就會一直while循環了。
7 、TranslateMessage, TranslateAccelerator?
? ? ? ? ?TranslateMessage: 把一個virtual-key消息轉化成字符消息(character message),并放到當前線程的消息隊列中,消息循環下一次取出處理。??
? ? ? ? TranslateAccelerator: 將快捷鍵對應到相應的菜單命令。它會把WM_KEYDOWN 或 WM_SYSKEYDOWN轉化成快捷鍵表中相應的WM_COMMAND 或WM_SYSCOMMAND消息, 然后把轉化后的 WM_COMMAND或WM_SYSCOMMAND直接發送到窗口過程處理, 處理完后才會返回。
8、(消息死鎖( Message Deadlocks)?
假設有線程A和B, 現在有以下下步驟??
? ? ? ? 1) 線程A SendMessage給線程B, A等待消息在線程B中處理后返回??
? ? ? ? 2) 線程B收到了線程A發來的消息,并進行處理, 在處理過程中,B也向線程A SendMessgae,然后等待從A返回。? 因為此時, 線程A正等待從線程B返回, 無法處理B發來的消息, 從而導致了線程A,B相互等待, 形成死鎖。多個線程也可以形成環形死鎖。??
可以使用 SendNotifyMessage或SendMessageTimeout來避免出現死鎖。
9、BroadcastSystemMessage?
? ? ? ? 我們一般所接觸到的消息都是發送給窗口的, 其實, 消息的接收者可以是多種多樣的,它可以是應用程序(applications), 可安裝驅動(installable drivers), 網絡設備(network drivers), 系統級設備驅動(system-level device drivers)等,??
BroadcastSystemMessage這個API可以對以上系統組件發送消息。
10、消息的處理?
? ? ? ? 接下來我們談一下消息的處理,首先我們來看一下VC中的消息泵:
while(GetMessage(&msg, NULL, 0, 0))?
{?
?????? if(!TranslateAccelerator(msg.hWnd, hAccelTable, &msg))?
?{??
??????????? TranslateMessage(&msg);?
??????????? DispatchMessage(&msg);?
?????? }?
}
TranslateMessage(轉換消息):
? ? ? ? 用來把虛擬鍵消息轉換為字符消息。由于Windows對所有鍵盤編碼都是采用虛擬鍵的定義,這樣當按鍵按下時,并不得字符消息,需要鍵盤映射轉換為字符的消息。
TranslateMessage函數
? ? ? ? 用于將虛擬鍵消息轉換為字符消息。字符消息被投遞到調用線程的消息隊列中,當下一次調用GetMessage函數時被取出。當我們敲擊鍵盤上的某個字符鍵時,系統將產生WM_KEYDOWN和WM_KEYUP消息。這兩個消息的附加參數(wParam和lParam)包含的是虛擬鍵代碼和掃描碼等信息,而我們在程序中往往需要得到某個字符的ASCII碼,TranslateMessage這個函數就可以將WM_KEYDOWN和WM_ KEYUP消息的組合轉換為一條WM_CHAR消息(該消息的wParam附加參數包含了字符的ASCII碼),并將轉換后的新消息投遞到調用線程的消息隊列中。注意,TranslateMessage函數并不會修改原有的消息,它只是產生新的消息并投遞到消息隊列中。
也就是說TranslateMessage會發現消息里是否有字符鍵的消息,如果有字符鍵的消息,就會產生WM_CHAR消息,如果沒有就會產生什么消息。
DispatchMessage(分派消息):
把 TranslateMessage轉換的消息發送到窗口的消息處理函數,此函數在窗口注冊時已經指定。
? ? ? ? 首先,GetMessage從進程的主線程的消息隊列中獲取一個消息并將它復制到MSG結構,如果隊列中沒有消息,則GetMessage函數將等待一個消息的到來以后才返回。如果你將一個窗口句柄作為第二個參數傳入GetMessage,那么只有指定窗口的的消息可以從隊列中獲得。GetMessage也可以從消息隊列中過濾消息只接受消息隊列中落在范圍內的消息。這時候就要利用GetMessage/PeekMessage指定一個消息過濾器。這個過濾器是一個消息標識符的范圍或者是一個窗體句柄,或者兩者同時指定。當應用程序要查找一個后入消息隊列的消息是很有用。WM_KEYFIRST 和 WM_KEYLAST 常量用于接受所有的鍵盤消息。 WM_MOUSEFIRST 和 WM_MOUSELAST 常量用于接受所有的鼠標消息。?
然后TranslateAccelerator判斷該消息是不是一個按鍵消息并且是一個加速鍵消息,如果是,則該函數將把幾個按鍵消息轉換成一個加速鍵消息傳遞給窗口的回調函數。處理了加速鍵之后,函數TranslateMessage將把兩個按鍵消息WM_KEYDOWN和WM_KEYUP轉換成一個 WM_CHAR,不過需要注意的是,消息WM_KEYDOWN,WM_KEYUP仍然將傳遞給窗口的回調函數。?????
處理完之后,DispatchMessage函數將把此消息發送給該消息指定的窗口中已設定的回調函數。如果消息是WM_QUIT,則 GetMessage返回0,從而退出循環體。應用程序可以使用PostQuitMessage來結束自己的消息循環。通常在主窗口的 WM_DESTROY消息中調用。
11、MFC的消息映射?
? ? ? ? ?使用MFC編程時,消息發送和處理的本質和Win32相同,但是,它對消息處理進行了封裝,簡化了程序員編程時消息處理的復雜性,它通過消息映射機制來處理消息,程序員不必去設計和實現自己的窗口過程。?
說白了,MFC中的消息映射機制實質是一張巨大的消息及其處理函數對應表。消息映射基本上分為兩大部分:?
在頭文件(.h)中有一個宏DECLARE_MESSAGE_MAP(),它放在類的末尾,是一個public屬性的;與之對應的是在實現部分(.cpp)增加了一個消息映射表,內容如下:?
BEGIN_MASSAGE_MAP(當前類,當前類的基類)?
//{{AFX_MSG_MAP(CMainFrame)?
消息的入口項?
//}}AFX_MSG_MAP?
END_MESSAGE_MAP()?
但是僅是這兩項還不足以完成一條消息,要是一個消息工作,必須還有以下3個部分去協作:?
1、在類的定義中加入相應的函數聲明;?
2、在類的消息映射表中加入相應的消息映射入口項;?
3、在類的實現中加入相應的函數體;?
消息的添加?
(1)、利用Class Wizard實現自動添加?
? ? ? ? 在菜單中選擇View -> Class Wizard激活Class Wizard,選擇Message Map標簽,從Class name組合框中選取我們想要添加消息的類。在Object IDs列表框中,選取類的名稱。此時,Messages列表框顯示該類的可重載成員函數和窗口消息。可重載成員函數顯示在列表的上部,以實際虛構成員函數的大小寫字母來表示。其他為窗口消息,以大寫字母出現。選中我們要添加的消息,單擊Add Funtion按鈕,Class Wizard自動將該消息添加進來。?
有時候,我們想要添加的消息在Message列表中找不到,我們可以利用Class Wizard上Class Info標簽以擴展消息列表。在該頁中,找到Message Filter組合框,通過它可以改變首頁中Messages列表框中的選項。?
(2)、手動添加消息?
?如果Messages列表框中確實沒有我們想要的消息,就需要我們手工添加:?
? ? ? ? 1)在類的.h文件中添加處理函數的聲明,緊接著在//}}AFX_MSG行之后加入聲明,注意,一定要以afx_msg開頭。?
通常,添加處理函數聲明的最好的地方是源代碼中Class Wizard維護的表的下面,在它標記其領域的{{ }}括弧外面。這些括弧中的任何東西都有可能會被Class Wizard銷毀。?
? ? ? ? 2)接著,在用戶類的.cpp文件中找到//}}AFX_MSG_MAP行,緊接在它之后加入消息入口項。同樣,也放在{{ }}外面。?
? ? ? ? 3)最后,在該文件中添加消息處理函數的實體。?
對于能夠使用Class Wizard添加的消息,盡量使用Class Wizard添加,以減少我們的工作量;對于不能使用Class Wizard添加的消息和自定義消息,需要手動添加。總體說來,MFC的消息編程對用戶來說,相對比較簡單,在此不再使用實例演示。?
??????? 12、消息反射機制?
什么叫消息反射??
? ? ? ? ?父窗口將控件發給它的通知消息,反射回控件進行處理(即讓控件處理這個消息),這種通知消息讓控件自己處理的機制叫做消息反射機制。?
? ? ? ? 通過前面的學習我們知道,一般情況下,控件向父窗口發送通知消息,由父窗口處理這些通知消息。這樣,父窗口(通常是一個對話框)會對這些消息進行處理,換句話說,控件的這些消息處理必須在父窗口類體內,每當我們添加子控件的時候,就要在父窗口類中復制這些代碼。很明顯,這對代碼的維護和移植帶來了不便,而且,明顯背離C++的對象編程原則。?
? ? ? ? 從4.0版開始,MFC提供了一種消息反射機制(Message Reflection),可以把控件通知消息反射回控件。具體地講,對于反射消息,如果控件有該消息的處理函數,那么就由控件自己處理該消息,如果控件不處理該消息,則框架會把該消息繼續送給父窗口,這樣父窗口繼續處理該消息。可見,新的消息反射機制并不破壞原來的通知消息處理機制。?
消息反射機制為控件提供了處理通知消息的機會,這是很有用的。如果按傳統的方法,由父窗口來處理這個消息,則加重了控件對象對父窗口的依賴程度,這顯然違背了面向對象的原則。若由控件自己處理消息,則使得控件對象具有更大的獨立性,大大方便了代碼的維護和移植。?
? ? ? ? 實例M8:簡單地演示MFC的消息反射機制。(見附帶源碼 工程M8)?
? ? ?開VC++ 6.0,新建一個基于對話框的工程M8。?
在該工程中,新建一個CMyEdit類,基類是CEdit。接著,在該類中添加三個變量,如下:?
private:?
CBrush m_brBkgnd;?
COLORREF m_clrBkgnd;?
COLORREF m_clrText;?
在CMyEdit::CMyEdit()中,給這三個變量賦初值:?
{?
m_clrBkgnd = RGB( 255, 255, 0 );?
m_clrText = RGB( 0, 0, 0 );?
m_brBkgnd.CreateSolidBrush(RGB( 150, 150, 150) );?
}?
打開ClassWizard,類名為CMyEdit,Messages處選中“=WM_CTLCOLOR”,您是否發現,WM_CTLCOLOR消息前面有一個等號,它表示該消息是反射消息,也就是說,前面有等號的消息是可以反射的消息。?
消息反射函數代碼如下:?
HBRUSH CMyEdit::CtlColor(CDC* pDC, UINT nCtlColor)?
{?
??? // TODO: Change any attributes of the DC here?
??? pDC->SetTextColor( m_clrText );//設置文本顏色?
??? pDC->SetBkColor( m_clrBkgnd );//設置背景顏色?
???? //請注意,在我們改寫該函數的內容前,函數返回NULL,即return NULL;?
??? //函數返回NULL將會執行父窗口的CtlColor函數,而不執行控件的CtlColor函數?
??? //所以,我們讓函數返回背景刷,而不返回NULL,目的就是為了實現消息反射?
??? return m_brBkgnd; //返回背景刷?
}?
在IDD_M8_DIALOG對話框中添加一個Edit控件,使用ClassWizard給該Edit控件添加一個CMyEdit類型的變量m_edit1,把Edit控件和CMyEdit關聯起來。
林炳文Evankaka原創作品。轉載請注明出處http://blog.csdn.net/evankaka
轉載于:https://www.cnblogs.com/icooper/p/4574916.html
總結
以上是生活随笔為你收集整理的Windows消息传递机制详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: EF – 8.多对多关联
- 下一篇: 第二次冲刺阶段 站立会议09