1.Windows SDK程序開發流程
主要分爲程序代碼和UI資源兩部分。
2.以消息未基礎,以事件驅動
程序不斷的等待外圍的輸入,判斷在處理。操作系統通過捕捉外圍輸入,以消息的形式進入程序中,程序通過獲取的不同消息進行不同的處理。USER模塊掌管外圍的驅動程序。
程序通過一個循環來獲取消息。
MSG msg;
while (GetMessage(&msg, NULL, NULL, NULL)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
其中MSG是一個結構體如下:
typedef struct tagMSG
{
HWND hwnd;
UINT message; // WM_xxx ,例如WM_MOUSEMOVE ,WM_SIZE...
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;
獲取消息後必須將獲取的消息進行處理,這個過程就需要一個窗口處理函數函數,形式如下:
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CREATE:
//....
break;
case WM_CLOSE:
//....
break;
case WM_DESTROY:
//....
break;
//...
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
整個過程如下圖:
3.Windows程序
main 是一般C程序的進入點:
int main(int argc, char *argv[]);
{
...
}
WinMain 則是Windows程序的進入點:
int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
...
}
// 在Win32 中CALLBACK被定義爲__stdcall,是一種函數調用習慣,關係到
// 參數壓入到堆棧的次序,以及處理堆棧的責任歸屬。其它的函數調用習慣還有
// _pascal 和_cdecl
初始化後程序進入消息循環:
while (GetMessage(&msg,...)) {
TranslateMessage(&msg); // 轉換鍵盤消息
DispatchMessage(&msg); // 分派消息
}
其中TranslateMessage是爲了將鍵盤消息轉化,DispatchMessage將消息傳給窗口函數去處理,當消息發生之時,OS已根據當時狀態,爲他表明了所屬窗口。而窗口窗口類又已經標明瞭窗口函數(也就是wc.
lpfnWndProc所指定的函數),所以DispatchMessage可以將消息發送給窗口處理。
消息循環中的DispatchMessage通過USER,模塊將消息送到窗口函數,窗口函數通過Switch/case來判斷消息種類,由於窗口函數是右系統所調用,所以這就是一種回調函數,用於Windows系統調用,還不會由我們調用。
整個流程是消息經過輸入,再經由消息循環的抓取,再傳輸給窗口進而給窗口函數,
LRESULT CALLBACK WndProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
所有消息都要經過處理,所以Switch/case中的default必須調用DefWindwosProc,這個是windows內部預設的消息處理函數。
首先,定義一個MSGMAP_ENTRY 結構和一個di m 宏:
struct MSGMAP_ENTRY {
UINT nMessage;
LONG (*pfn)(HWND, UINT, WPARAM, LPARAM);
};
#define dim(x) (sizeof(x) / sizeof(x[0]))
其中pfn是一個函數指針,用出指針處理nMessgae消息,接下來定義兩個數組_messageEntries[]和_commandEntries[],將消息和處理方法關聯起來:
// 消息與處理方法之對照
struct MSGMAP_ENTRY _messageEntries[] =
{
WM_CREATE, OnCreate,
WM_PAINT, OnPaint,
WM_SIZE, OnSize,
WM_COMMAND, OnCommand,
WM_SETFOCUS, OnSetFocus,
WM_CLOSE, OnClose,
WM_DESTROY, OnDestroy,
} ;
// Command-ID 與處理方法對照
struct MSGMAP_ENTRY _commandEntries =
{
IDM_ABOUT, OnAbout,
IDM_FILEOPEN, OnFileOpen,
IDM_SAVEAS, OnSaveAs,
} ;
窗口函數的設計如下:
//----------------------------------------------------------------------
//窗口函數
//----------------------------------------------------------------------
LRESULT CALLBACK WndProc(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
int i;
for(i=0; i < dim(_messageEntries); i++) { // 消息對照表
if (message == _messageEntries[i].nMessage)
return((*_messageEntries[i].pfn)(hWnd, message, wParam, lParam));
}
return(DefWindowProc(hWnd, message, wParam, lParam));
}
//----------------------------------------------------------------------
// OnCommand -- 專門處理WM_COMMAND
//----------------------------------------------------------------------
LONG OnCommand(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
int i;
for(i=0; i < dim(_commandEntries); i++) { // 命令對照表
if (LOWORD(wParam) == _commandEntries[i].nMessage)
return((*_commandEntries[i].pfn)(hWnd, message, wParam, lParam));
}
return(DefWindowProc(hWnd, message, wParam, lParam));
}
//----------------------------------------------------------------------
LONG OnCreate(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam)
{
...
}
//----------------------------------------------------------------------
LONG OnAbout(HWND hWnd, UINT wMsg, UINT wParam, LONG lParam)
{
...
}
//----------------------------------------------------------------------
這樣一來WndProc和OnComand就不用改變了,每有新要處理的消息,只要添加到_messageEntries[]和_commandEntries[]兩個數組中就行了。
對話框有兩種:
- 模態對話框:當子對話框運行的時候父窗口不能使用
- 無模對話框:子對話框和父窗口共同運行
一張圖說明問題:
4.Windows程序生命週期
- 在初始化過程中調用CreateWindow創建窗口,同時CreateWindow發送WM_CREATE消息,用於初始化動作。
- 在程序運行過程中不斷的使用GetMessage獲取存儲的消息,如果獲取的消息是WM_QUIT,GetMessage就返回0同時While循環就結束,從而就結束運行。
- DispatchMessage通過USER模塊將消息發送到窗口函數。
- 不斷重複2和3。
- 當點擊關閉按鈕時,窗口發送WM_CLOSE消息,這個消息一般是DefWindwosProc進行處理。
- 當DefWindwosProc收到WM_CLOSE後嗲偶哦那個DestoryWindow把窗口清理,DestoryWindow本身會發送WM_DESTORY消息。
- 窗口函數對WM_DESTORY消息進行處理,通過調用PostQuitMessage處理。
- 通過PostQuitMessage發送WM_QUIT消息,讓GetMessage獲取消息讓2處理。
5.空閒時間處理
當消息隊列中沒有消息的時候,就存在空閒時間,獲取這個時間可以通過如下代碼:
while (TRUE) {
if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE) {
if (msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else {
OnIdle ();
}
}
PeekMessage 返回
TRUE
的條件是有消息,如果沒有消息返回
FALSE GetMessage 返回
TRUE
的條件是有消息且該消息不爲WM_QUIT返回
, FALSE 的條件是有消息且該消息 爲
WM_QUIT
相同點:
PeekMessage函數與GetMessage函數都用於查看應用程序消息隊列,有消息時將隊列中的消息派發出去。
不同點:
無論應用程序消息隊列是否有消息,PeekMessage函數都立即返回,程序得以繼續執行後面的語句(無消息則執行其它指令,有消息時一般要將消息派發出去,再執行其它指令)。GetMessage函數只有在消息對立中有消息時返回,隊列中無消息就會一直等,直至下一個消息出現時才返回。在等的這段時間,應用程序不能執行任何指令。
6.進程與線程
進程:執行中的程序。
線程:在一個程序內部也可以實現多個任務併發執行,其中每個任務稱爲線程,線程是比進程更小的執行單位,它是在一個進程中獨立的控制流,即程序內部的控制流。
內核對象是系統的一種資源,內核對象一經創建,任何應用程序都可以使用。系統通過一個使用計數來管理內核對象,常用的內核對象如下:
內核對象 |
產生方法 |
event |
CreateEvent |
mutex |
CreateMutex |
semaphore |
CreateSemaphore |
file |
CreateFile |
file-mapping |
CreateFileMapping |
process |
CreateProcess |
thread |
CreateThread |
內核對象產生方式不同,但是都會獲得一個handle句柄作爲識別,每被使用一次,使用計數加1,其結束的時候都是調用CloseHandle。其中process對象是一個數據結構,系統通過它來管理進程。
執行一個程序,就會產生一個進程,最直接的就是通過windows的shell執行一個App.exe應用程序,當雙擊就可運行.流程如下:
通過CreateProcess創建進程:
BOOL WINAPI
CreateProcessW(
__in_opt LPCWSTR lpApplicationName,
__inout_opt LPWSTR lpCommandLine,
__in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes,
__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in BOOL bInheritHandles,
__in DWORD dwCreationFlags,
__in_opt LPVOID lpEnvironment,
__in_opt LPCWSTR lpCurrentDirectory,
__in LPSTARTUPINFOW lpStartupInfo,
__out LPPROCESS_INFORMATION lpProcessInformation
);
<待續>