日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

玩转Win32开发(2):完整的开发流程

發布時間:2023/12/1 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 玩转Win32开发(2):完整的开发流程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
?

上一篇中我給各位說了一般人認為C++中較為難的東西——指針。其實對于C++,難點當然不局限在指針這玩意兒上,還有一些有趣的概念,如模板類、虛基類、純虛函數等,這些都是概念性的東西,幾乎每一本C++書上都會介紹,而平時我們除了會接觸到純虛函數外,其他的不多用。純虛函數,你可以認為與C#中的抽象方法或接口中的方法類似,即只定義,不實現。好處就是多態,發何處理,由派生類來決定。

在開始吹牛之前,我先推薦一套視頻教程,孫鑫老師的C++教程,共20課,我不是幫他老人家打廣告,而是因為孫老師講的課是我聽過的最好的課,我都看過4次了,我完全可以用他的視頻教程來復習C++的。

?

好了,F話說完了,下面我就扯一下編寫一個Win32應用程序的大致流程,不管你的程序有多么復雜,多么變態,其基本思路和流程是不變的。這就好比你寫書法的時候,特別是寫楷書,我不管你用的是歐體、顏體,還是柳體,你都得遵守“永字八法”基本規則。

那么,我們要編寫一個Win32應用程序,要經過哪幾個步驟呢?

你不妨想一想,你有一家工廠是生產女性服裝的,如果你要生產一批新式服裝(例如某種冬裝),你會有哪些流程?

首先,如果我們確定要做這么一款服式,我們要請設計師來把服裝設計好,然后打版,打版就是生成基本樣本,以后工人就按照這個樣本做就行了。

其次,注冊產品,向上級主管申報,登記后就轉入車間或下游加工企業開工。

再次,為了展示你的新產品的特色,你要舉辦一場服裝表演。

接著、持續更新,發現產品存在的問題,不斷改進修正。

最后,推向市場。

我們開發Win32應用程序也是遵守這樣的規范。不過,我想現在很少人用Win32在實際開發中,畢竟它的開發效率是相當地低下,所以,曾被某些人誤認為只適用于開發木馬程序。其實,也不一定的,不要太邪惡了。

MFC對Win API函數的封裝,后來出現了托管C++,你可以用于寫WinForm程序,這樣可以提高開發效率。

如果你有足夠的時間,如果你還在學習編程,如果你是剛進入大學的年輕有為者,你不用急,因為你會有更多的時間磨煉,你應當考慮多學一點C類語言,C++的學習你會發現你能學到很多其他語言中學不到的知識,特別是接觸到不少原理性的東西,能加深你對編程哲學的認知。

?

一、WinMain入口點

我們在學習標準C++的時候,都知道每個應用程序運行時都會先進入入口點函數main,而當從main函數跳出時程序就結束了。在Windows編程里面,也是一樣的,只是我們的入口點函數不叫main,叫WinMain,這個函數不同于main,我們不能亂來,它的定義必須與聲明保持一致。

我建議各位安裝VS的時候,都順便更新幫助文檔到本地硬盤,這樣我們可以方便查找。有一點要注意,目前DestTop Develop的文檔基本上是英文的,做好心理準備。

WinMain函數怎么寫呢,不用記的,到MSDN文檔一搜,直接復制就行了。

[cpp]?view plaincopyprint?
  • int?CALLBACK?WinMain(??
  • ????_In_??HINSTANCE?hInstance,??
  • ????_In_??HINSTANCE?hPrevInstance,??
  • ????_In_??LPSTR?lpCmdLine,??
  • ????_In_??int?nCmdShow??
  • ??);??

  • 這個函數帶了一個CALLBACK,說明它是一個回調函數,那么這個CALLBACK是啥呢。我們先不管,我們先動寫一個Windows,讓大家有一個更直觀的認識。

    1、啟動你的開發工具,版本任意。

    2、從菜單欄中依次【文件】【新建】【項目】,在新建項目窗口中,選擇Win32-Win32應用程序。

    ?

    2、點擊確定后,會彈出一個向導,單擊【下一步】。項目類型選擇Windows應用程序,附加選項選擇空項目,我們要自己編寫實現代碼。

    ?

    3、單擊完成,項目創建成功。打開【解決方案資源管理器】,在“源文件”文件夾上右擊,從菜單中找到【添加】【新建項】,注意,是源文件,不要搞到頭文件去了。

    在新建項窗口中選C++代碼文件,.cpp后綴的,不要選錯了,選成頭文件,不然無法編譯,因為頭文件是不參與編譯的。文件名隨便。

    ?

    包含Windows.h頭文件,這個是最基本的。

    [cpp]?view plaincopyprint?
  • #include?<Windows.h>??

  • 然后是入口點,這個我們直接把MSDN的聲明Ctrl + C,然后Ctrl + V上去就行了。

    [cpp]?view plaincopyprint?
  • int?CALLBACK?WinMain(??
  • ????_In_??HINSTANCE?hInstance,??
  • ????_In_??HINSTANCE?hPrevInstance,??
  • ????_In_??LPSTR?lpCmdLine,??
  • ????_In_??int?nCmdShow??
  • ??)??
  • {??
  • ??
  • ????return?0;??
  • }??

  • WinMain返回整型,返回0就行了,其實是進程的退出碼,一定要0,不要寫其他,因為0表示正常退出,其他值表示非正常退出。

    剛才我們提到這個函數帶了CALLBACK,那么,它是什么?很簡單,你回到IDE,在CALLBACK上右擊,選【轉到定義】,看看吧。

    我們看到它其實是一個宏,原型如下:

    [cpp]?view plaincopyprint?
  • #define?CALLBACK????__stdcall??
  • 這時候我們發現了,它其實就是__stdcall,那么這個__stdcall是什么呢?它是和__cdecl關鍵字對應的,這些資料,你網上搜一下就有了,如果你覺得不好理解,你不妨這樣認為,__stdcall是專門用來調用Win API 的,反正MSDN上也是這樣說的,它其實是遵循Pascal的語法調用標準,相對應地,__cdecl是C語言的調用風格,這個也是編譯器選項。
    打開項目屬性,找到節點C/C++\高級,然后查看一下調用約定,我們看到默認是選擇C風格調用的,所以,WIN API 函數才用上關鍵字__stdcall,如果你實在不懂,也沒關系,這個東西一般不影響我們寫代碼,但屬性窗口中的編譯器選項不要亂改,改掉了可能會導致一些問題。

    ?

    那么CALLBACK有什么特別呢?一句話:函數不是我們調用的,但函數只定義了模型沒有具體處理,而代碼處理權在被調用者手里。怎么說呢,我們完全把它理解為.NET中的委托,我想這樣就好理解了,委托只聲明了方法的參數和返回值,并沒有具體處理代碼。

    WinMain是由系統調用的,而WinMain中的代碼如何寫,那操作系統就不管了。就好像我告訴你明天有聚會,一起去爬山,反正我是通知你了,至于去不去那是你決定了。

    接下來看看入口點函數的參數。

    注意,我們平時看到很多如HANDLE,HINSTANCE,HBRUSH,WPARAM。LPARAM,HICON,HWND等一大串數據類型,也許我們會說,怎么Windows開發有那么多數據類型。其實你錯了,人總是被眼睛所看到的東西欺騙,Win API 中根本沒有什么新的數據類型,全都是標準C++中的類型,說白了,這些東西全是數字來的。如果你不信,自己可以研究一下。

    它定義這些名字,只是方便使用罷了,比如下面這樣:

    [cpp]?view plaincopyprint?
  • int?hWindow;??
  • int?hIcon;??
  • int?theAppInstance;??
  • 第一個變量指的是窗口的句柄,第二個指的是一個圖標的句柄,第三個是當前應用程序的實例句柄,你看看,如果我們所有的句柄都是int,我們就無法判斷那些類型是專門用來表示光標資源,不知道哪些類型是專用來表示位圖的句柄了,但是,如果我們這樣:

    [cpp]?view plaincopyprint?
  • #defin?HBRUSH??int64??
  • 這樣就很直觀,我一看這名就知道是Brush Handlers,哦,我就明白它是專門用來管理內存中的畫刷資源的,看,這就很明了,所以,通常這些新定義的類型或者宏,都是取有意義的名字。比如消息,它也是一個數字,如果我說115代表叫你去滾,但光是一個115誰知道你什么意思,但是,如果我們為它定義一個宏:

    [cpp]?view plaincopyprint?
  • #define?WM_GET_OUT????115??
  • 這樣,只要我SendMessage(hwnd, ?WM_GET_OUT, NULL, NULL),你就會收到一條消息,滾到一邊去。

    ?

    WinMain的第一個參數是當前應用程序的實例句柄,第二個參數是前一個實例,比如我把kill.exe運行了兩個實例,進程列表中會有兩個kill.exe,這時候第一次運行的實例號假設為0001,就傳遞第一個參數hInstance,第二次運行的假設實例號為0002,就傳給了hPrevInstance參數。

    lpCmdLine參數從名字上就猜到了,就是命令行參數,那LPSTR是啥呢,它其實就是一個字符串,你可以跟入定義就知道了,它其實就是char*,指向char的指針,記得我上一篇文章中說的指針有創建數組的功能嗎?對,其實這里傳入的命令行參數應該是char[ ],這就是我在第一篇文章中要說指針的原因。

    這里告訴大家一個技巧,我們怎么知道哪些參數是指針類型呢,因為不是所有參數都有 * 標識。技巧還是在命名上,以后,只要我們看到P開頭的,或者LP開頭的,都是指針類型

    比如LPWSTR,LPCTSTR,LPRECT等等。

    最后一個參數nCmdShow是主窗口的顯示方式。它定義了以下宏。

    ?

    ValueMeaning
    SW_HIDE0

    Hides the window and activates another window.

    SW_MAXIMIZE3

    Maximizes the specified window.

    SW_MINIMIZE6

    Minimizes the specified window and activates the next top-level window in the Z order.

    SW_RESTORE9

    Activates and displays the window. If the window is minimized or maximized, the system restores it to its original size and position. An application should specify this flag when restoring a minimized window.

    SW_SHOW5

    Activates the window and displays it in its current size and position.

    SW_SHOWMAXIMIZED3

    Activates the window and displays it as a maximized window.

    SW_SHOWMINIMIZED2

    Activates the window and displays it as a minimized window.

    SW_SHOWMINNOACTIVE7

    Displays the window as a minimized window. This value is similar toSW_SHOWMINIMIZED, except the window is not activated.

    SW_SHOWNA8

    Displays the window in its current size and position. This value is similar toSW_SHOW, except the window is not activated.

    SW_SHOWNOACTIVATE4

    Displays a window in its most recent size and position. This value is similar toSW_SHOWNORMAL, except the window is not activated.

    SW_SHOWNORMAL1

    Activates and displays a window. If the window is minimized or maximized, the system restores it to its original size and position. An application should specify this flag when displaying the window for the first time.

    ?


    這個參數是操作系統傳入的,我們無法修改它。那么,應用程序在運行時,是如何決定這個參數的呢?看看這個,不用我介紹了吧,你一定很熟悉。

    我們寫了WinMain,但我們還要在WinMain前面預先定義一個WindowProc函數。C++與C#,Java這些語言不同,你只需記住,C++編譯器的解析是從左到右,從上到下的,如果某函數要放到代碼后面來實現,但在此之前要使用,那么你必須先聲明一下,不然編譯時會找不到。這里因為我們通常會把WindowProc實現放在WinMain之后,但是在WinMain中設計窗口類時要用到它的指針,這時候,我們必須在WinMain之前聲明WindowProc。

    同樣地,WindowProc的定義我們不用記,到MSDN直接抄就行了。

    [cpp]?view plaincopyprint?
  • #include?<Windows.h>??
  • //?必須要進行前導聲明??
  • LRESULT?CALLBACK?WindowProc(??
  • ????_In_??HWND?hwnd,??
  • ????_In_??UINT?uMsg,??
  • ????_In_??WPARAM?wParam,??
  • ????_In_??LPARAM?lParam??
  • );??
  • ??
  • int?CALLBACK?WinMain(??
  • ????_In_??HINSTANCE?hInstance,??
  • ????_In_??HINSTANCE?hPrevInstance,??
  • ????_In_??LPSTR?lpCmdLine,??
  • ????_In_??int?nCmdShow??
  • ??)??
  • {??
  • ????return?0;??
  • }??
  • //?在WinMain后實現??
  • LRESULT?CALLBACK?WindowProc(??
  • ????_In_??HWND?hwnd,??
  • ????_In_??UINT?uMsg,??
  • ????_In_??WPARAM?wParam,??
  • ????_In_??LPARAM?lParam??
  • )??
  • {??
  • ????return?DefWindowProc(hwnd,?uMsg,?wParam,?lParam);??
  • }??

  • 前導聲明與后面實現的函數的簽名必須一致,編譯才會認為它們是同一個函數。在WindowProc中返回DefWindowProc是把我們不感興趣或者沒有處理的消息交回給操作系統來處理。也許你會問,函數的名字一定要叫WindowProc嗎?當然不是了,你可以改為其他名字,如MyProc,但前提是返回值和參數的類型以及個數必須一致。

    [cpp]?view plaincopyprint?
  • LRESULT?CALLBACK?MyProc(??
  • ????_In_??HWND?hwnd,??
  • ????_In_??UINT?uMsg,??
  • ????_In_??WPARAM?wParam,??
  • ????_In_??LPARAM?lParam??
  • )??
  • 這個函數帶了CALLBACK,說明不是我們調用的,也是由操作系統調用的,我們在這個函數里面對需要處理的消息進行響應。至于,為什么可以改函數的名字而系統為什么能找到這個函數呢,后面你就知道了。

    ?

    二、設計與注冊窗口類

    設計窗口類,其實就是設計我們程序的主窗口,如有沒有標題欄,背景什么顏色,有沒有邊框,可不可以調整大小等。要設計窗口類,我們用到一個結構——

    [cpp]?view plaincopyprint?
  • typedef?struct?tagWNDCLASS?{??
  • ??UINT??????style;??
  • ??WNDPROC???lpfnWndProc;??
  • ??int???????cbClsExtra;??
  • ??int???????cbWndExtra;??
  • ??HINSTANCE?hInstance;??
  • ??HICON?????hIcon;??
  • ??HCURSOR???hCursor;??
  • ??HBRUSH????hbrBackground;??
  • ??LPCTSTR???lpszMenuName;??
  • ??LPCTSTR???lpszClassName;??
  • }?WNDCLASS,?*PWNDCLASS;??
  • 通常情況下,我們用WNDCLASS就可以了,當然還有一個WNDCLASSEX的擴展結構,在API里面,凡是看到EX結尾的都是擴展的意思,比如CreateWindowEx就是CreateWindow的擴展函數。

    第一個成員是窗口的類樣式,注意,不要和窗口樣式(WS_xxxxx)混淆了,這里指的是這個窗口類的特征,不是窗口的外觀特征,這兩個style是不一樣的。

    它的值可以參考MSDN,通常我們只需要兩個就可以了——CS_HREDRAW?|?CS_VREDRAW,從名字就看出來了,就是同時具備水平重畫和垂直重畫。因為當我們的窗口顯示的時候,被其他窗口擋住后重新顯示,或者大小調整后,窗口都要發生繪制,就像我們在紙上涂鴉一樣,每次窗口的變化都會“粉刷”一遍,并發送WM_PAINT消息。

    lpfnWndProc參數就是用來設置你用哪個WindowProc來處理消息,前面我說過,我們只要不更改回調函數的返回值和參數的類型和順序,就可以隨意設置函數的名字,那為什么系統可以找到我們用的回調函數呢,對的,就是通過lpfnWndProc傳進去的,它是一個函數指針,也就是它里面保存的是我們定義的WindowProc的入口地址,使用很簡單,我們只需要把函數的名字傳給它就可以了。

    cbClsExtra和cbWndExtra通常不需要,設為0就OK。hInstance是當前應用程序的實例句柄,從WinMain的hInstance參數中可以得到。hIcon和hCursor就不用我說了,看名字就知道了。

    hbrBackground是窗口的背景色,你也可以不設置,但在處理WM_PAINT消息時必須繪制窗口背景。也可以直接用系統定義的顏色,MSDN為我們列出這些值,大家不用記,直接到MSDN拿來用就行了,這些都比較好理解,看名字就知道了。

    • COLOR_ACTIVEBORDER
    • COLOR_ACTIVECAPTION
    • COLOR_APPWORKSPACE
    • COLOR_BACKGROUND
    • COLOR_BTNFACE
    • COLOR_BTNSHADOW
    • COLOR_BTNTEXT
    • COLOR_CAPTIONTEXT
    • COLOR_GRAYTEXT
    • COLOR_HIGHLIGHT
    • COLOR_HIGHLIGHTTEXT
    • COLOR_INACTIVEBORDER
    • COLOR_INACTIVECAPTION
    • COLOR_MENU
    • COLOR_MENUTEXT
    • COLOR_SCROLLBAR
    • COLOR_WINDOW???????????????????????????????????????? /*??這個就是窗口的默認背景色? */
    • COLOR_WINDOWFRAME
    • COLOR_WINDOWTEXT

    lpszMenuName指的是菜單的ID,沒有菜單就NULL,lpszClassName就是我們要向系統注冊的類名,字符,不能與系統已存在的類名沖突,如“BUTTON”類。

    所以,在WinMain中設計窗口類。

    [cpp]?view plaincopyprint?
  • //?類名??
  • WCHAR*?cls_Name?=?L"My?Class";??
  • //?設計窗口類??
  • WNDCLASS?wc;??
  • wc.hbrBackground?=?(HBRUSH)COLOR_WINDOW;??
  • wc.lpfnWndProc?=?WindowProc;??
  • wc.lpszClassName?=?cls_Name;??
  • wc.hInstance?=?hInstance;??
  • 窗口類設計完成后,不要忘了向系統注冊,這樣系統才能知道有這個窗口類的存在。向操作系統注冊窗口類,使用RegisterClass函數,它的參數就是一個指向WNDCLASS結構體的指針,所以我們傳遞的時候,要加上&符號。

    [cpp]?view plaincopyprint?
  • //?注冊窗口類??
  • RegisterClass(&wc);??

  • ?

    三、創建和顯示窗口

    窗口類注冊完成后,就應該創建窗口,然后顯示窗口,調用CreateWindow創建窗口,如果成功,會返回一個窗口的句柄,我們對這個窗口的操作都要用到這個句柄。什么是句柄呢?其實它就是一串數字,只是一個標識而已,內存中會存在各種資源,如圖標、文本等,為了可以有效標識這些資源,每一個資源都有其唯一的標識符,這樣,通過查找標識符,就可以知道某個資源存在于內存中哪一塊地址中,就好比你出身的時候,長輩都要為你取個名字,你說名字用來干嗎?名字就是用來標識你的,不然,你見到A叫小明,遇到B又叫小明,那誰知道哪個才是小明啊?就好像你上大學去報到號,會為你分配一個可以在本校學生中唯一標識你的學號,所有學生的學號都是不同的,這樣,只要通過索引學號,就可以找到你的資料。

    CreateWindow函數返回一個HWND類型,它就是窗口類的句柄。

    [cpp]?view plaincopyprint?
  • //?創建窗口??
  • HWND?hwnd?=?CreateWindow(??
  • ????cls_Name,???????????//類名,要和剛才注冊的一致??
  • ????L"我的應用程序",??????????//窗口標題文字??
  • ????WS_OVERLAPPEDWINDOW,????????//窗口外觀樣式??
  • ????38,?????????????//窗口相對于父級的X坐標??
  • ????20,?????????????//窗口相對于父級的Y坐標??
  • ????480,????????????????//窗口的寬度??
  • ????250,????????????????//窗口的高度??
  • ????NULL,???????????????//沒有父窗口,為NULL??
  • ????NULL,???????????????//沒有菜單,為NULL??
  • ????hInstance,??????????//當前應用程序的實例句柄??
  • ????NULL);??????????????//沒有附加數據,為NULL??
  • if(hwnd?==?NULL)????????????????//檢查窗口是否創建成功??
  • ????return?0;??

  • 窗外觀的樣式都是WS_打頭的,是Window Style的縮寫,這個我就不說了,MSDN上全有了。

    窗口創建后,就要顯示它,就像我們的產品做了,要向客戶展示。顯示窗口調用ShowWindow函數。

    [cpp]?view plaincopyprint?
  • //?顯示窗口??
  • ShowWindow(hwnd,?SW_SHOW);??
  • 既然要顯示窗口了,那么ShowWindow的第一個參數就是剛才創建的窗口的句柄,第二個參數控制窗口如何顯示,你可以從SW_XXXX中選一個,也可以用WinMain傳進來的參數,還記得WinMain的最后一個參數嗎?

    ?

    四、更新窗口(可選)

    為什么更新窗口這一步可有可無呢?因為只要程序在運行著,只要不是最小化,只要窗口是可見的,那么,我們的應用程序會不斷接收到WM_PAINT通知。這里先不說,后面你會明白的。好了,更新窗口,當然是調用UpdateWindow函數。

    [cpp]?view plaincopyprint?
  • //?更新窗口??
  • UpdateWindow(hwnd);??

  • ?

    五、消息循環

    Windows操作系統是基于消息控制機制的,用戶與系統之間的交互,程序與系統之間的交互,都是通過發送和接收消息來完成的。就好像軍隊一樣,命令一旦傳達,就要執行,當然,我們的應用程序和軍隊不一樣,我們收到指令不一要執行,我們是可以選擇性地執行。

    我們知道,代碼是不斷往前執行的,像我們剛才寫的WinMain函數一樣,如果你現在運行程序,你會發現什么都沒有,是不是程序不能運行呢,不是,其實程序是運行了,只是它馬上結束了,只要程序執行跳出了WinMain的右大括號,程序就會結束了。那么,要如何讓程序不結束了,可能大家注意到我們在C程序中可以用一個getchar()函數來等到用戶輸入,這樣程序就人停在那里,直到用戶輸入內容。但我們的窗口應用不能這樣做,因為用戶有可能進行其他操作,如最小化窗口,移動窗口,改變窗口大小,或者點擊窗口上的按鈕等。因此,我們不能簡地弄一個getchar在那里,這樣就無法響應用戶的其他操作了。

    可以讓程序留在某處不結束的另一個方法就是使用循環,而且是死循環,這樣程序才會永久地停在某個地方,但這個死循環必須具有跳出的條件,不然你的程序會永久執行,直達停電或者把電腦砸了。

    這樣消息循環就出現了,只要有與用戶交互,系統人不斷地向應用程序發送消息通知,因為這些消息是不定時不斷發送的,必須有一個綬沖區來存放,就好像你去銀行辦理手續要排隊一樣,我們從最前端取出一條一條消息處理,后面新發送的消息會一直在排隊,直到把所有消息處理完,這就是消息隊列

    要取出一條消息,調用GetMessage函數。函數會傳入一個MSG結構體的指針,當收到消息,會填充MSG結構體中的成員變量,這樣我們就知道我們的應用程序收到什么消息了,直到GetMessage函數取不到消息,條件不成立,循環跳出,這時應用程序就退出。MSG的定義如下:

    [cpp]?view plaincopyprint?
  • typedef?struct?tagMSG?{??
  • ??HWND???hwnd;??
  • ??UINT???message;??
  • ??WPARAM?wParam;??
  • ??LPARAM?lParam;??
  • ??DWORD??time;??
  • ??POINT??pt;??
  • }?MSG,?*PMSG,?*LPMSG;??
  • hwnd不用說了,就是窗口句柄,哪個窗口的句柄?還記得WindowProc回調函數嗎?你把這個函數交給了誰來處理,hwnd就是誰的句柄,比如我們上面的代碼,我們是把WindowProc賦給了新注冊的窗口類,并創建了主窗口,返回一個表示主窗口的句柄,所以,這里MSG中的hwnd指的就是我們的主窗口。

    message就是我們接收到的消息,看到,它是一個數字,無符號整型,所以我們操作的所有消息都是數字來的。wParam和lParam是消息的附加參數,其實也是數值來的。通常,lParam指示消息的處理結果,不同消息的結果(返回值)不同,具體可參閱MSDN。

    有了一個整型的值來表示消息,我們為什么還需要附加參數呢?你不妨想一下,如果接收一條WM_LBUTTONDOWN消息,即鼠標左鍵按下時發送的通知消息,那么,我們不僅知道左鍵按下這件事,我們更感趣的是,鼠標在屏幕上的哪個坐標處按下左鍵,按了幾下,這時候,你公憑一條WM_LBUTTONDOWN消息是無法傳遞這么多消息的。可能我們需要把按下左鍵時的坐標放入wParam參數中;最典型的就是WM_COMMAND消息,因為只要你使用菜單,點擊按鈕都會發送這樣一條消息,那么我怎么知道用戶點了哪個按鈕呢?如果窗口中只有一個按鈕,那好辦,用戶肯定單擊了它,但是,如果窗口上有10個按鈕呢?而每一個按鈕被單擊都會發送WM_COMMAND消息,你能知道用戶點擊了哪個按鈕嗎?所以,我們要把用戶點擊了的那個按鈕的句柄存到lParam參數中,這樣一來,我們就可以判斷出用戶到底點擊了哪個按鈕了。

    GetMessage函數聲明如下:

    [cpp]?view plaincopyprint?
  • BOOL?WINAPI?GetMessage(??
  • ??_Out_?????LPMSG?lpMsg,??
  • ??_In_opt_??HWND?hWnd,??
  • ??_In_??????UINT?wMsgFilterMin,??
  • ??_In_??????UINT?wMsgFilterMax??
  • );??
  • 這個函數在定義時帶了一個WINAPI,現在,按照前面我說的方法,你應該猜到,它就是一個宏,而真實的值是__stdcall,前文中說過了。

    第一個參數是以LP開頭,還記得嗎,我說過的,你應該想到它就是 MSG* ,一個指向MSG結構的指針。第二個參數是句柄,通常我們用NULL,因為我們會捕捉整個應用程序的消息。后面兩個參數是用來過濾消息的,指定哪個范圍內的消息我接收,在此范圍之外的消息我拒收,如果不過濾就全設為0.。返回值就不說了,自己看。

    [cpp]?view plaincopyprint?
  • //?消息循環??
  • MSG?msg;??
  • while(GetMessage(&msg,?NULL,?0,?0))??
  • {??
  • ????TranslateMessage(&msg);??
  • ????DispatchMessage(&msg);??
  • }??
  • TranslateMessage是用于轉換按鍵信息的,因為鍵盤按下和彈起會發送WM_KEYDOWN和WM_KEYUP消息,但如果我們只想知道用戶輸了哪些字符,這個函數可以把這些消息轉換為WM_CHAR消息,它表示的就是鍵盤按下的那個鍵的字符,如“A”,這樣我們處理起來就更方便了。

    DispatchMessage函數是必須調用的,它的功能就相當于一根傳送帶,每收到一條消息,DispatchMessage函數負責把消息傳到WindowProc讓我們的代碼來處理,如果不調用這個函數,我們定義的WindowProc就永遠接收不到消息,你就不能做消息響應了,你的程序就只能從運行就開始死掉了,沒有響應。


    ?

    六、消息響應

    其實現在我們的應用程序是可以運行了,因為在WindowProc中我們調用了DefWindowProc,函數,消息我們不作任何處理,又把控制權路由回到操作系統來默認處理,所以,整個過程中,我們現在的消息循環是成立的,只不過我們不做任何響應罷了。

    好的,現在我把完整的代碼貼一下,方便你把前面我們說的內容串聯起來。

    [cpp]?view plaincopyprint?
  • #include?<Windows.h>??
  • //?必須要進行前導聲明??
  • LRESULT?CALLBACK?WindowProc(??
  • ????_In_??HWND?hwnd,??
  • ????_In_??UINT?uMsg,??
  • ????_In_??WPARAM?wParam,??
  • ????_In_??LPARAM?lParam??
  • );??
  • ??
  • //?程序入口點??
  • int?CALLBACK?WinMain(??
  • ????_In_??HINSTANCE?hInstance,??
  • ????_In_??HINSTANCE?hPrevInstance,??
  • ????_In_??LPSTR?lpCmdLine,??
  • ????_In_??int?nCmdShow??
  • ??)??
  • {??
  • ????//?類名??
  • ????WCHAR*?cls_Name?=?L"My?Class";??
  • ????//?設計窗口類??
  • ????WNDCLASS?wc;??
  • ????wc.hbrBackground?=?(HBRUSH)COLOR_WINDOW;??
  • ????wc.lpfnWndProc?=?WindowProc;??
  • ????wc.lpszClassName?=?cls_Name;??
  • ????wc.hInstance?=?hInstance;??
  • ????//?注冊窗口類??
  • ????RegisterClass(&wc);??
  • ??
  • ????//?創建窗口??
  • ????HWND?hwnd?=?CreateWindow(??
  • ????????cls_Name,???????????//類名,要和剛才注冊的一致??
  • ????????L"我的應用程序",??//窗口標題文字??
  • ????????WS_OVERLAPPEDWINDOW,?//窗口外觀樣式??
  • ????????38,?????????????//窗口相對于父級的X坐標??
  • ????????20,?????????????//窗口相對于父級的Y坐標??
  • ????????480,????????????????//窗口的寬度??
  • ????????250,????????????????//窗口的高度??
  • ????????NULL,???????????????//沒有父窗口,為NULL??
  • ????????NULL,???????????????//沒有菜單,為NULL??
  • ????????hInstance,??????????//當前應用程序的實例句柄??
  • ????????NULL);??????????????//沒有附加數據,為NULL??
  • ????if(hwnd?==?NULL)?//檢查窗口是否創建成功??
  • ????????return?0;??
  • ??
  • ????//?顯示窗口??
  • ????ShowWindow(hwnd,?SW_SHOW);??
  • ??
  • ????//?更新窗口??
  • ????UpdateWindow(hwnd);??
  • ??
  • ????//?消息循環??
  • ????MSG?msg;??
  • ????while(GetMessage(&msg,?NULL,?0,?0))??
  • ????{??
  • ????????TranslateMessage(&msg);??
  • ????????DispatchMessage(&msg);??
  • ????}??
  • ????return?0;??
  • }??
  • //?在WinMain后實現??
  • LRESULT?CALLBACK?WindowProc(??
  • ????_In_??HWND?hwnd,??
  • ????_In_??UINT?uMsg,??
  • ????_In_??WPARAM?wParam,??
  • ????_In_??LPARAM?lParam??
  • )??
  • {??
  • ????return?DefWindowProc(hwnd,?uMsg,?wParam,?lParam);??
  • }??

  • 所有代碼看上去貌似很正常,也遵守了流程,設計窗口類,注冊窗口類,創建窗口,顯示窗口,更新窗口,消息循環。是吧,這段代碼看上去毫無破綻,運行應該沒問題吧。好,如果你如此自信,那就試試吧。

    按下F5試試運行。
    哈哈,結果會讓很多人失望,很多初學者就是這樣,一切看起來好像正常,于是有人開始罵VC是垃圾,是編譯器有bug,也有人開始想放棄了,媽的,這么難,不學了。人啊,總是這樣,老指責別人的問題,從不在自己身上找問題,是真的VC的bug嗎?

    我前面說了,這段代碼貌似很正常,呵呵,你看到問題在哪嗎?給你兩分鐘來找錯。我提示一下,這個程序沒有運行是因為主窗口根本就沒有創建,因為我在代碼里面做了判斷,如果窗口順柄hwnd為NULL,就退出,現在程序一運行就退出了,明顯是窗口創建失敗。

    …………

    好了,不用找了,很多人找不出來,尤其是許多初學者,不少人找了一遍又一遍,都說沒有錯誤,至少代碼提示沒說有錯,編譯運行也沒報錯,所以不少人自信地說,代碼沒錯。

    其實你是對的,代碼確實沒有錯,而問題就出在WNDCLASS結構上,認真看一下MSDN上有關RegisterClass函數說明中的一句話,這句話很多人沒注意到,但它很關鍵。

    You must fill the structure with the appropriate class attributes before passing it to the function.

    現在你明白了吧,還不清楚?沒關系,看看我把代碼這樣改一下你就知道了。

    [cpp]?view plaincopyprint?
  • //?設計窗口類??
  • WNDCLASS?wc;??
  • wc.cbClsExtra?=?0;??
  • wc.cbWndExtra?=?0;??
  • wc.hCursor?=?LoadCursor(hInstance,?IDC_ARROW);;??
  • wc.hIcon?=?LoadIcon(hInstance,?IDI_APPLICATION);;??
  • wc.lpszMenuName?=?NULL;??
  • wc.style?=?CS_HREDRAW?|?CS_VREDRAW;??
  • wc.hbrBackground?=?(HBRUSH)COLOR_WINDOW;??
  • wc.lpfnWndProc?=?WindowProc;??
  • wc.lpszClassName?=?cls_Name;??
  • wc.hInstance?=?hInstance;??
  • 現在,你運行一下,你一定能看到窗口。

    但現在你對窗口無法進行操作,因為后續的代碼還沒完成。

    為什么現在又可以了呢?MSDN那句話的意思就是說我們在注冊窗口類之前必須填充WNDCLASS結構體,何為填充,就是要為結構的所有成員賦值,就算不需要你也要為它賦一個NULL或0,因為結構在創建時沒有對成員進行初始化,這就導致變量無法正確的分配內存,最后注冊失敗。

    那么,如果一個結構體成員很多,而我只需要用到其中三個,其他的也要初始化,是不是很麻煩,是的,除了為每個成員賦值,還有一種較簡單的方法,就是在聲明變量時給它賦一對大括號,里面放置結構體的應該分配內存的大小,如:

    [cpp]?view plaincopyprint?
  • //?設計窗口類??
  • WNDCLASS?wc?=?{?sizeof(WNDCLASS)?};??
  • wc.hbrBackground?=?(HBRUSH)COLOR_WINDOW;??
  • wc.lpfnWndProc?=?WindowProc;??
  • wc.lpszClassName?=?cls_Name;??
  • wc.hInstance?=?hInstance;??
  • 這樣一來,我們也發現,窗口也可以成功創建。

    我們還可以更簡單,直接把sizeof也去掉,在聲明變量時,直接賦一對空的大括號就行了,就如這樣。

    [cpp]?view plaincopyprint?
  • WNDCLASS?wc?=?{?};??
  • 這樣寫更簡單,窗口類同樣可以正常注冊。大括號代表的是代碼塊,這樣,結構體有了一個初值,因此它會按照結構體的大小分配了相應的內存。

    ?

    為什么會這樣呢?這里涉及到一個關于結構體的一個很有趣的賦值方式。我們先放下我們這個例子,下面我寫一個簡單的例子,你就明白了。

    [cpp]?view plaincopyprint?
  • #include?<stdio.h>??
  • ?typedef?struct?rectStruct??
  • ?{??
  • ?????int?x;??
  • ?????int?y;??
  • ?????int?width;??
  • ?????int?height;??
  • ?}?RECT,?*PRECT;??
  • ??
  • ?void?main()??
  • ?{??
  • ?????RECT?rect?=?{?0,?0,?20,?30?};??
  • ?????printf("矩形的坐標是:%d,?%d\n矩形的大小:%d?,?%d",?rect.x,?rect.y,?rect.width,?rect.height);??
  • ?????getchar();??
  • ?}??
  • 在本例中,我們定義了一個表示矩形的結構體 RECT ,它有四個成員,分別橫坐標,縱坐標,寬度,高度,但是,我們在聲明和賦值中,我們只用了一對大括號,把每個成員的值,按照定義的順序依次寫到大括號中,即{ 0, 0, 20, 30?},x的值為0,y的值為0,width為20,height的值為30。

    也就是說,我們可以通過這種簡單的方法向結構變量賦值,注意值的順序要和成員變量定義的順序相同。

    現在,回到我們的Windows程序來,我們明白了這種賦值方式,對于 WNDCLASS wc = {? } 就不難理解了,這樣雖然大括號里面是空的,其實它已經把變量初始化了,都賦了默認值,這樣一來,就可以正確分配內存了。

    ?

    七、為什么不能退出

    通常情況下,當我們的主窗口關閉后,應用程序應該退出(木馬程序除外),但是,我們剛才運行后發現,為什么我的窗口關了,但程序不退出呢?前面我說了,要退出程序,就要先跳出消息循環,和關閉哪個窗口無關。因此,我們要解決兩個問題:

    1、如果跳出消息循環;

    2、什么時候退出程序。

    其實兩個問題是可以合并到一起解決。

    首先要知道,當窗口被關閉,為窗口所分配的內存會被銷毀,同時,我們會收到一條WM_DESTROY消息,因而,我們只要在收到這條消息時調用PostQuitMessage函數,這個函數提交一條WM_QUIT消息,而在消息循環中,WM_QUIT消息使GetMessage函數返回0,這樣一來,GetMessage返回FALSE,就可以跳出消息循環了,這樣應用程序就可以退出了。

    所以,我們要做的就是捕捉WM_DESTROY消息,然后PostQuitMessage.

    [cpp]?view plaincopyprint?
  • //?在WinMain后實現??
  • LRESULT?CALLBACK?WindowProc(??
  • ????_In_??HWND?hwnd,??
  • ????_In_??UINT?uMsg,??
  • ????_In_??WPARAM?wParam,??
  • ????_In_??LPARAM?lParam??
  • )??
  • {??
  • ????switch(uMsg)??
  • ????{??
  • ????case?WM_DESTROY:??
  • ????????{??
  • ????????????PostQuitMessage(0);??
  • ????????????return?0;??
  • ????????}??
  • ????}??
  • ????return?DefWindowProc(hwnd,?uMsg,?wParam,?lParam);??
  • }??

  • 我們會收到很多消息,所以用switch判斷一下是不是WM_DESTROY消息,如果是,退出應用程序。

    好了,這樣,我們一個完整的Windows應用程序就做好了。

    下面是完整的代碼清單。

    [cpp]?view plaincopyprint?
  • #include?<Windows.h>??
  • //?必須要進行前導聲明??
  • LRESULT?CALLBACK?WindowProc(??
  • ????_In_??HWND?hwnd,??
  • ????_In_??UINT?uMsg,??
  • ????_In_??WPARAM?wParam,??
  • ????_In_??LPARAM?lParam??
  • );??
  • ??
  • //?程序入口點??
  • int?CALLBACK?WinMain(??
  • ????_In_??HINSTANCE?hInstance,??
  • ????_In_??HINSTANCE?hPrevInstance,??
  • ????_In_??LPSTR?lpCmdLine,??
  • ????_In_??int?nCmdShow??
  • ??)??
  • {??
  • ????//?類名??
  • ????WCHAR*?cls_Name?=?L"My?Class";??
  • ????//?設計窗口類??
  • ????WNDCLASS?wc?=?{?};??
  • ????wc.hbrBackground?=?(HBRUSH)COLOR_WINDOW;??
  • ????wc.lpfnWndProc?=?WindowProc;??
  • ????wc.lpszClassName?=?cls_Name;??
  • ????wc.hInstance?=?hInstance;??
  • ????//?注冊窗口類??
  • ????RegisterClass(&wc);??
  • ??
  • ????//?創建窗口??
  • ????HWND?hwnd?=?CreateWindow(??
  • ????????cls_Name,???????????//類名,要和剛才注冊的一致??
  • ????????L"我的應用程序",??//窗口標題文字??
  • ????????WS_OVERLAPPEDWINDOW,?//窗口外觀樣式??
  • ????????38,?????????????????//窗口相對于父級的X坐標??
  • ????????20,?????????????????//窗口相對于父級的Y坐標??
  • ????????480,????????????????//窗口的寬度??
  • ????????250,????????????????//窗口的高度??
  • ????????NULL,???????????????//沒有父窗口,為NULL??
  • ????????NULL,???????????????//沒有菜單,為NULL??
  • ????????hInstance,??????????//當前應用程序的實例句柄??
  • ????????NULL);??????????????//沒有附加數據,為NULL??
  • ????if(hwnd?==?NULL)?//檢查窗口是否創建成功??
  • ????????return?0;??
  • ??
  • ????//?顯示窗口??
  • ????ShowWindow(hwnd,?SW_SHOW);??
  • ??
  • ????//?更新窗口??
  • ????UpdateWindow(hwnd);??
  • ??
  • ????//?消息循環??
  • ????MSG?msg;??
  • ????while(GetMessage(&msg,?NULL,?0,?0))??
  • ????{??
  • ????????TranslateMessage(&msg);??
  • ????????DispatchMessage(&msg);??
  • ????}??
  • ????return?0;??
  • }??
  • //?在WinMain后實現??
  • LRESULT?CALLBACK?WindowProc(??
  • ????_In_??HWND?hwnd,??
  • ????_In_??UINT?uMsg,??
  • ????_In_??WPARAM?wParam,??
  • ????_In_??LPARAM?lParam??
  • )??
  • {??
  • ????switch(uMsg)??
  • ????{??
  • ????case?WM_DESTROY:??
  • ????????{??
  • ????????????PostQuitMessage(0);??
  • ????????????return?0;??
  • ????????}??
  • ????}??
  • ????return?DefWindowProc(hwnd,?uMsg,?wParam,?lParam);??
  • } ?
  • 轉載于:https://www.cnblogs.com/lvdongjie/p/4502532.html

    總結

    以上是生活随笔為你收集整理的玩转Win32开发(2):完整的开发流程的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。