1. 程式人生 > >入職作業總結(3.0)Windows程式開發

入職作業總結(3.0)Windows程式開發

看DirectX書的的時候,發現示例程式碼給出的部分與以往的主函式int main(int argc, char **argv)不同,以為只是給了個函式,主函式得自己寫。查閱了資料後發現是孤陋寡聞,原來windows應用程式的入口函式定義本來就比較特別。

第一個windows程式

Windows中應用程式入口是這樣的int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)。各個引數含義見 這裡

程式碼中,只有LRESULT CALLBACK myWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

是由使用者自己定義的,確定視窗外觀,行為等屬性的函式。

雖然LRESULT CALLBACK myWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)定義了大部分視窗的引數和邏輯,但主函式從未顯示地呼叫過該函式。每當DispatchMessage()函式(在上述主函式迴圈中)被呼叫時,它會間接地導致Windows呼叫myWindowProc()函式。具體說明如下。

建立視窗

根據官方介紹,每個視窗(window)都與一個 視窗類(window class)相關聯。必須注意的是,從C++的角度來說,每個 視窗類 並不是一個C++的類,而是一個被作業系統使用的資料結構

。視窗類 是在執行時註冊的,具體的註冊過程如下,先填充一個WNDCLASS類:

    const wchar_t CLASS_NAME[]  = L"Sample Window Class";
    WNDCLASS wc = { };
    wc.lpfnWndProc   = WindowProc;   //函式指標,定義了應用視窗的行為
    wc.hInstance     = hInstance;
    wc.lpszClassName = CLASS_NAME;

lpfnWndProc是一個指向application-defined function 的指標,這個函式稱為window procedure 或“window proc.”。
hInstance

是一個應用例項的控制代碼,該控制代碼從WinMain(主函式)的引數中獲得。
lpszClassName是一個區別視窗類的字串。這個字串侷限於當前程序內,所以這個名字只需要在本程序內獨一無二即可。但是,有一些名字,如”Button”被系統用來作為控制的類名,所以使用者需要避開這些名字。
WNDCLASS還有其他的成員,但我們可以暫且忽略他們,或將他們置為0。

第二步,呼叫RegisterClass函式註冊該視窗類

RegisterClass(&wc);

第三步,呼叫CreateWindowEx建立視窗並獲得返回的控制代碼:

HWND hwnd = CreateWindowEx(
    0,                              // Optional window styles.
    CLASS_NAME,                     // Window class
    L"Learn to Program Windows",    // Window text
    WS_OVERLAPPEDWINDOW,            // Window style
    // Size and position
    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
    NULL,       // Parent window    
    NULL,       // Menu
    hInstance,  // Instance handle
    NULL        // Additional application data
);

第四步,呼叫ShowWindow顯示視窗:

ShowWindow(hwnd, nCmdShow);

其中,nCmdShow引數可被用來最小化或最大化視窗。
至此,視窗建立過程已經完成。

訊息傳遞

一個GUI程式需要應對使用者行為(滑鼠鍵盤等)和系統行為(如檢測到外設,系統功耗模式切換等),windows採用一種訊息傳遞機制來處理程式執行時接收到的這些行為。
訊息是一個專用的數字用來指代特定的事件,如按下滑鼠左鍵對應的訊息#define WM_LBUTTONDOWN 0x0201。此外,一些訊息還包含額外的資料,比如上述的WM_LBUTTONDOWN就包含x,y座標。
傳遞訊息的方式是由作業系統呼叫對應視窗註冊的window procedure(前文提到)。
對於每一個建立視窗的執行緒,作業系統建立一個對應的佇列,由它保管對應執行緒負責的所有視窗(可能不止一個)。這個佇列不能被使用者直接操作,但可以通過呼叫GetMessage函式來從中獲取一個訊息

GetMessage(&msg, NULL, 0, 0);

GetMessage函式移除佇列頂的第一個訊息。如果佇列空,則該函式阻塞。如果需要在後臺處理某些任務,則可以建立一個其他執行緒來處理。函式中第一個引數是MSG結構的地址。如果呼叫成功,該結構會包含檢測到的行為以及對應視窗(可能同時有多個視窗)等資料。其他三個引數(一般置為0)給與使用者濾出所需要訊息的能力。
雖然MSG結構包含很多資訊,但我們一般不直接訪問它,而是呼叫以下兩個函式:

TranslateMessage(&msg); 
DispatchMessage(&msg);

TranslateMessage主要處理鍵盤相關的輸入,並將輸入(鍵按下,升起)轉換為對應字元。

DispatchMessage函式告訴作業系統呼叫訊息對應視窗的 視窗處理函式(window procedure)。

一般來說,GetMessage都會返回一個非零值。當你想退出你的程式時,可以考慮呼叫PostQuitMessage函式:

PostQuitMessage(0);

該函式會將一個WM_QUIT訊息放到訊息佇列中。這個訊號是個特殊的訊號,它會導致GetMessage函式返回0。這個資訊時由該函式產生的,意味著(大部分情況下) 視窗處理函式 不可能收到一個這樣的訊號**,也就不需要在視窗處理函式中針對該訊號進行特殊處理。

視窗處理函式

如前文所述,DispatchMessage函式會呼叫對應視窗的視窗處理函式,該視窗處理函式需要有以下形式(函式名可以不同):

    LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

引數:hwnd是所指視窗的控制代碼,uMsg是訊息值,wParamlParam是訊息的附帶資料。
返回值:LRESULT是一個程式返回給Windows的整數返回值(翻譯自MSDN文件,沒完全懂,感覺是類似return 0中的0的作用)。

PS:CALLBACK的作用我找到這麼一句話:

簡單的說,我們呼叫別人的API叫call,呼叫的第三方api呼叫我們的函式叫回調(callback)

上述過程中,我們呼叫DispatchMessage來呼叫我們的WindowProc函式,所以叫回撥函式

一種常見的做法是在WindowProc函式中利用switch來處理不同訊號。對於不想特殊處理的訊號,可以呼叫DefWindowProc執行系統預設的反饋操作。

PS:採用VS編譯的時候,由於一開始選擇的專案型別是console win32 project(命令列版)所以會失敗。想要編譯winMain專案需要做如下調整

  1. 選單中選擇 Project->Properties, 彈出Property Pages視窗
  2. 在左邊欄中依次選擇:Configuration Properties->C/C++->Preprocessor,然後在右邊欄的Preprocessor Definitions對應的項中刪除_CONSOLE, 新增_WINDOWS.
  3. 在左邊欄中依次選擇:Configuration Properties->Linker->System,然後在右邊欄的SubSystem對應的項改為Windows(/SUBSYSTEM:WINDOWS)