Windows訊息機制和多線程

來源:互聯網
上載者:User

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;

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.