今天跟同事聯調一段代碼,被一個問題鬱悶了很久。
調用過程其實並不複雜,就是他提供一個Dll,並輸出一個函數(姑且叫做Foo()吧),我調用他的函數Foo,其內部產生一個視窗。但是,我每次調用,視窗總是一閃而過!我們於是懷疑,是不是因為主程式是console,不支援MFC的緣故?要不然一定是因為不是在主線程裡調用的緣故?!又是多線程,真是麻煩!
反覆測試,結果如下
MFC對話方塊Win32 MFC Console
多線程 失敗 失敗
單線程 成功 失敗
為什嗎?難道就不能在普通線程裡建立視窗嗎?什麼是主線程(UI線程),什麼又是背景工作執行緒?以前認為理所當然的事情,現在突然變得很模糊!
開始把目光鎖定到訊息佇列上!是不是因為主線程有訊息佇列?比如MFC對話方塊程式就有訊息佇列,而Console沒有。但是,同樣是線程,為什麼你有而我沒有?難道是WinMain函數和普通的main函數不一樣?到底訊息佇列是什麼時候產生的?
草草瀏覽了CWinApp的代碼,也沒有找到答案。這時候,隱約想起了《Windows核心編程》裡說的,系統在你第一次調用相關函數的時候,自動為線程建立訊息佇列!但是,到底是什麼函數呢?
帶著這個鬱悶回到了家,開啟那本Windows編程寶典,翻到訊息佇列一章。原來,每個視窗歸屬於建立它的線程。一旦線程退出,視窗也將自行銷毀!另外,線程還要進行訊息迴圈,以分發視窗的訊息!注意,訊息迴圈是由線程負責的!線程剛開始建立的時候是不帶訊息佇列的,當線程第一次調用視窗相關的函數時,作業系統自動為其建立訊息佇列!原來,視窗的訊息佇列是歸屬於線程的!
問題終於搞清楚了,在普通的背景工作執行緒裡,我們沒有訊息迴圈,所以建立出來的視窗還沒有沒有機會顯示出來,線程就結束退出了!即使在console程式裡,通過cin.get()讓程式等待,視窗還是沒有機會重新整理(依賴訊息迴圈分發並調用訊息處理函數)而一直處於忙碌等待狀態!而前面的Foo函數,恰恰要依賴於訊息迴圈!
知道問題的真正原因後,就很容易解決了!在調用同事的函數產生視窗後(函數返回了),在同一個線程裡進行訊息迴圈!如下
...in a working thread...
Foo(); //call some function
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
...working thread exit...
問題迎刃而解!你看不是MFC的問題,也不是不能在背景工作執行緒裡建立視窗。本來,我一開始就懷疑,這麼依賴MFC,總不能讓主程式都是MFC程式吧!作為一個DLL組件,不能對調用者有太多的限制!而多線程,太尋常了,如果都在主線程裡做,那也太難模組化了吧!當然了,現在說這些都是馬後炮,當時鬱悶的時候,什麼古怪的理由都用上了!
其實,這個問題本來可以解決得更快點的。如果同事事先說明他的Foo函數何時返回(我一直以為它要一直阻塞到其產生的視窗關閉才返回的,結果並不是這樣的,視窗一產生它就返回了),以及是否需要依賴外部的訊息迴圈(他是有提過,但也很模糊,搞不清具體為什麼)。當然,最大的原因還是因為大家對Windows的訊息迴圈機制理解得不是很深入!
現在你搞明白Windows的訊息迴圈到底是如何進行的了嗎?