Windows消息機制和多線程

MFC篇
Windows消息處理
單位線程是如何處理消息的
Windows的消息處理機制是用如下代碼進行消息處理的:
MSG message;
While(::GetMessage(&message,NULL,0,0)){
 ::TranslateMessage(&message);
 ::DispatchMessage(&message);
}
當消息到達時,由TranslateMessage進行必要的轉換,例如:將WM_KEYDOWN消息轉換爲包含有ASCII字符的WM_CHAR消息,然後由DispatchMessage進行發送,當處理完成後,DispatchMessage返回.
放棄控制
如果在等待方式下,DispatchMessage必須等待處理完成後才能返回,在此之前將不能處理任何消息,而下面的代碼可以做到即使沒有消息到達程序的情況下也立即返回
MSG message;
While(::PeekMessage(&message,NULL,0,0,PM_REMOVE)){
 ::TranslateMessage(&message);
 ::DispatchMessage(&message);
}
計時器
計時器是不依賴CPU的時鐘速度的. 注意的是因爲Windows並不是實時的操作系統,所以,如果你指定的週期小於100毫秒的話,計時器事件之間的週期可能不精確.
有了計時器,有時可以替代多線程情況, 例如下面的代碼就允許在循環內仍然接收處理消息. 這是一個進度條, 在OnTimer裏面改動進度條的顯示, 同時可以自定義CANCEL消息, 在OnCancel中將程序終止.
Void CDlg::OnStart()
{
 MSG message;
 SetTimer(0,100,NULL);
 GetDlgItem(IDC_START)->EnableWindow(FALSE); // 使按鈕無效
 Volatile int nTemp; //使變更不保存在寄存器中, 因爲變量如果保存在寄存器中, 在線程的切換過程中可能會出現值的錯誤.
 For (m_nCount=0;m_nCount
  For (nTemp=0;nTemp<10000;nTemp++){
   .........
  }
  if (::PeekMessage(&message,NULL,0,0,PM_REMOVE)){
   ::TranslateMessage(&message);
   ::DispatchMessage(&message);
  }
 }
 CDlg::OnOK(); // 線程結束後關閉對話框
}

多線程編程
進程是擁有自己的內存,文件句柄和其他系統資源的運行程序, 單個進程可以包含獨立的執行,叫線程.
Windows提供了兩種線程, 工作者(worker)線程和用戶界面線程, 用戶界面線程通常有窗口,且具有自己的消息循環.工作者線程沒有窗口,因此它不需要處理消息.
編寫工作者線程函數並啓動線程
線程體一般是如何形式:
UINT ThreadProc(LPVOID pParam)
{
return 0;
}
啓動線程使用:
CwinThread* pThread =
AfxBeginThread(ThreadProc,GetSafeHwnd(),THREAD_PRIORITY_NORMAL);
主線程如何與工作線程使用全局變量通訊
全局變量通訊是最簡單而有效的辦法.
例如下面的代碼:
UINT ThreadProc(LPVOID pParam)
{
 g_nCount = 0;
while(g_nCount<100)
 ::InterlockedIncrement((long*)&g_nCount);
return 0;
}
InterLockIncrement函數在變量加1期間阻塞其他線程訪問該變量. 如果不使用此函數而直接使用:g_nCount++的話, 可能會出現錯誤.
工作者線程與主線程通訊發送消息進行聯絡
 下面的代碼: 當線程完成後發送給父進程消息
 UINT ThreadProc(LPVOID pParam)
{
 .........
 ::PostMessage((HWND)pParam,WM_THREADFINISHED,0,0);
 return 0;
}
使用事件進行線程同步
Cevent類是一個事件類,剛定義時處於"非信號"狀態, 可以調用SetEvent()成員函數置它爲"信號"狀態.
下面的代碼: 線程首先等待開始信號, 如果沒有置開始信號會一直掛起等待, 同時在運行的過程中等待結束信號, 如果結束信號發生就終止線程.
Cevent g_eventStart,g_eventKill;
UINT ThreadProc(LPVOID pParam)
{
//INFINITE表示等待無限時間
 ::WaitForSingleObject(g_eventtart,INFINITE);
 for (.........) 
{
..........
If(::WaitForSingleObject(g_eventKill,0)==WAIT_OBJECT_0))
Break;
}
return 0;
}
當啓動線程時: g_eventStart.SetEvent();
當終止線程時: g_eventKill.SetEvent();
但在終止線程時如果還沒有啓動線程,則應該先啓動線程再終止它.
注意: 線程如果不正常終止會引起內存泄漏, 例如用關閉進程的方法來強制終止線程,或使用Win32 TerminateThread函數.
臨界段
使用CcriticalSection類可以將臨界段句柄包裝起來, 例如:
 CcriticalSection g_cs;
 G_cs.Lock();
 G_nCount++;
 G_cs.Unlock();
用戶接口線程
 一 般使用用戶接口線程是想要得到多個頂級窗口,例如你想允許用戶運行程序的多個實例,但是你想讓所有的實例都共享內存,IE就是這樣的. 你可以從CwinThread來派生一個類,使用AfxBeginThread的重載版本來啓動該線程,派生的這個類具有它自己的 InitInstance函數,並且具有自己的消息循環.

Win32 SDK篇
事件的使用方法
HANDLE g_hCloseEvent = NULL;
g_hCloseEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (g_hCloseEvent == NULL)
        return FALSE;
 設置信號: SetEvent(g_hCloseEvent);
線程的創建方法
 線程體的一般形式:
 DWORD WINAPI ThreadProc(LPVOID pParam)
 {
  return 0;
}
創建時:
HANDLE hReceiveThread = NULL;
UINT ThreadID;
hReceiveThread =
CreateThread(NULL,0,ThreadProc,hWnd,0, &ThreadID);
if ( hReceiveThread == NULL )
  return FALSE;
// 優先級爲普通
SetThreadPriority(hReceiveThread,THREAD_PRIORITY_NORMAL);
臨界區的使用方法
 CRITICAL_SECTION csRecvRead  = {0}; 
 InitializeCriticalSection(&csRecvRead); // 臨界區初始化
 EnterCriticalSection(&csRecvRead); // 使用臨界區變量
 pRightBuffer = pRightBuffer + len;
 LeaveCriticalSection(&csRecvRead);

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

VC++消息映射的思考

作者:郝慶欣

在學習VC++的時候,大家都不可避免的用到消息映射。我們都知道C++是一種面向對象的編程語言,VC++中爲什麼這樣來實現消息映射呢?
  首先要明白一個包含了消息處理的Windows程序是如何工作的。
  一般來說一個包含了消息處理的Windows程序至少要包含兩個函數
   第一個:
    int WINAPI WinMain(
      HINSTANCE hInstance,   // handle to current instance
      HINSTANCE hPrevInstance,  // handle to previous instance
      LPSTR lpCmdLine,     // command line
      int nCmdShow     // show state
    );
   第二個:
   long FAR PASCAL WndProc(HWND hWnd,WORD message,WORD wParam,LONG lParam);
    我們不必糾纏程序實現的細節,只要明白在第一個函數WinMain中要註冊WndProc函數,通俗一些的理解就是WinMain告訴Windows 系統,聽着,我知道你要產生很多消息,我這裏有一個WndProc函數負責處理你傳遞來的各種消息。當然消息的格式都是系統規定好的。

其次要明白C++中是如何實現多態性的。

我們知道多態性實現的關鍵是晚綁定(或者稱爲後期綁定),其實質就是編譯器並沒有在編譯期間指定調用函數的絕對地址,而是指定了某個類內部該函數的偏移地址。

爲了實現上面的功能,編譯器爲我們作了手腳

1、  在每個帶有虛函數的類中,編譯器祕密放置了一個指針,稱爲Vpointer

2、  當系統運行時,爲每個類創建一個VTABLE,其中包含了可以調用虛函數地址。

3、  Vpointer出始化,指向VTABLE,通過在Vtable中偏移,來找到正確的需要調用的函數地址。

然後是MFC對Window API進行的封裝

當 我們利用MFC框架開發程序的時候,尤其是開發界面應用程序的時候,必定要用到CWnd或者派生於CWnd的類。根據面向對象的設計原則,對於CWnd的 一些通用函數,例如窗口大學改變(OnSize),窗口移動(OnMove),最好是在CWnd中聲明爲虛函數,然後在繼承的類裏面重載他們。但是,這樣 以來,每個相關的派生類都要有一個Vpointer和一套記錄Vtable,而CWnd中通用函數是如此至多,CWnd的派生類也很多,必然會導致系統在 運行是佔用過多的資源(內存),這樣顯然是不合適的。

 

那麼MFC是如何實現的呢?

答案就是在CWnd基類中儘可能的少用虛函數,採用消息映射機制來代替。

大家可以看一下CWnd的類中的函數,就會發現這一點。

CWnd::OnMove 
afx_msg void OnMove( int x, int y );

上面這個函數就不是虛函數。

 最後的問題消息映射是如何實現的呢?

 用一句話說,就是利用宏定義來實現面向過程的消息處理。

例如在VC中有如下的消息映射宏。

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)  //{{AFX_MSG_MAP(CMainFrame)

ON_WM_CREATE() 

//}}AFX_MSG_MAP

ON_COMMAND(ID_FONT_DROPDOWN, DoNothing)

END_MESSAGE_MAP()

經過編譯後,代碼被替換爲如下形式(這只是作講解,實際情況比這複雜得多):

//BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd) 

CMainFrame::newWndProc(...)

{

switch(...)

{

//{{AFX_MSG_MAP(CMainFrame)

// ON_WM_CREATE() 

case(WM_CREATE):

OnCreate(...);

break;

//}}AFX_MSG_MAP

// ON_COMMAND(ID_FONT_DROPDOWN, DoNothing)

case(WM_COMMAND):

if(HIWORD(wP)==ID_FONT_DROPDOWN)

{

DoNothing(...);

}

break;

//END_MESSAGE_MAP()

}

}

這樣,VC++就消除了對部分虛擬函數的需要,從而節省了內存空間。

 

參考資料:

  Thingking in C++,Bruce Eckel;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章