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;