Windows API入门系列之五 -一个正儿八经的SDK程序
上一篇,講了一個簡單的SDK程序的多種版本的編寫,彈出了一個窗口,顯示了我們計算1到10的結果,計算的程序不是重點,重點在于,一:讓大家認識到Unicode版本的程序和ASCII版本的程序在編程方面的區別,以及怎么樣編寫出通用代碼的程序。二:怎么樣運用API或者c++庫函數格式化非字符數據到一個字符串中顯示出來。
不過,那個相當簡單的程序,還算不上是一個正兒八經的SDK程序,也就是說還不是一個純爺們兒,因為我們并親自完成一個SDK程序的經典步驟。而是調用了一個MessageBox?API函數,這個函數雖然使用簡單,但是在它的內部,那可是相當復雜啊~~~。怎么個復雜法,具體的我不知道,但是我知道的是一個SDK程序的經典步驟它是都用到了的,什么是編寫SDK程序的經典步驟呢?新手朋友們聽好了哦,現在我就告訴你。
第一步:注冊窗口類
第二步:創建窗口
第三步:消息循環
第四步:編寫窗口消息處理函數
上面我所說的,聽起來都比較專業,下面我就解釋一下,什么是注冊窗口類呢?注冊窗口類就是使用一個?窗口類結構體(WNDCLASSEX)?來描述一類窗口,這類窗口具有相同的屬性,也就是你在結構體WNDCLASSEX中指定的那些值。只要是用這個窗口類創建的窗口都具有這些特性。至于WNDCLASS能描述哪些特性,下面會具體講,這里你只要了解是用一個名叫WNDCLASSEX的結構體來描述一個窗口的類別。
創建窗口應該比較好理解吧,就是創建一個具體的窗口,好像是一句廢話嘛。也就是說這個窗口是根據一個窗口類而創建的,不是憑空而造的。意思是你要創建一個窗口,那么必須要有一個已經注冊的窗口類。
對于前兩步,我打一個比方,就好比你要造一輛車,那們第一步首先是干什么??當然是設計圖紙啦,圖紙上就有說明這種車有哪些特性。然后第二步才是根據這個圖紙來創建一個具體的看得見的車。所以我上面說的注冊窗口類就好比設計窗口的圖紙,然后就是根據這個窗口的圖紙來創建一個具體的窗口。都說成這樣了,應該明了了吧~~
至于消息循環,就是創建的窗口隨時都有可能發生很多事情,那么發生的這些事情怎么通知你呢?比如窗口最小化了,窗口大小改變了,怎么通知你呢???其實就就是通過消息循環不斷的取得窗口所發生的事情,然后以消息的形式發送給我們后面要介紹的窗口消息處理函數。
消息處理函數呢就是我們程序員負責編寫代碼對具體的消息進行具體的處理,當然你也可以不處理,交給系統的默認處理函數來處理。
對于這兩步,我也打一個比方。消息循環就好比汽車的一個總傳感器,它源源不斷的將汽車內部所發生的事情以消息的形式通過儀表板傳達給開車的人,開車的人根據具體的事情而采取具體的操作,當然你也可以不操作,無動于衷,對于windows消息來說,不操作倒沒有什么,而對于開車的人來說,不操作的后果就不好說了。?在這里,這個總傳感器就相當于SDK程序的消息循環,不斷的發送消息,而開車的人就相當于窗口消息處理函數,負責處理各種消息。明白了吧,還不明白的話就看看下面的具體的程序吧,也需還有最后一絲希望可以讓你恍然大悟。
講了正兒八經的SDK程序的經典步驟后,我們進入正式的代碼階段,通過代碼結合上面所講理論進一步鞏固知識。我講逐步講解并逐步編寫一個自己注冊窗口類,創建窗口,帶消息循環,并自己編寫消息處理過程的程序。
首先給出程序框架
/*?BY?beyondcode?*/
#include?<windows.h>
#include?<tchar.h>
LRESULT?CALLBACK?WinMessageProc(?HWND?hwnd,?UINT?msg,?WPARAM?wParam,?LPARAM?lParam?);
int?WINAPI?_tWinMain(?HINSTANCE?hInstance,?HINSTANCE?hPrevInstance,?LPTSTR?lpCmdLine,?int?nShowCmd?)
{
return?0;
}
第一個函數聲明,返回類型為LRESULT,本質經查看是long,然后函數調用約定CALLBACK和WINAPI是一樣的,都是__stdcall,說明函數調用相關的約定,不必深究。至于為什么用CALLBACK是為了意思顯而易見,表示是回調函數,什么是回調函數?也就是系統負責調用的,不必你親自調用的函數,所以你在你的程序里是看不到調用WinMessageProc這個函數的代碼的,你只負責編寫它的代碼,至于調用,系統會在有消息的時候自動調用它。WinMessageProc的參數類型和個數是規定好了的,不然系統怎么知道怎么掉用,所以不能更改。
再解釋一下這四個參數吧,第一個參數是一個窗口的句柄,也就是告訴你,是哪個窗口的消息,第二個參數是消息的類型,告訴你是什么消息,第三個和第四個參數是這個消息所帶的一些額外的但是必須的數據。你在窗口消息處理函數中只使用他們就可以了,他們的值都是系統傳遞進來的。你只是根據他們來判斷是哪個窗口的什么消息,并且獲取該消息的額外參數信息。
有了程序框架,我們來第一步,注冊一個窗口類
???//注冊一個名叫MyWindowClass的窗口類
WNDCLASSEX?wc;
wc.cbSize?=?sizeof(?wc?);
wc.style?=?CS_VREDRAW?|?CS_HREDRAW;
wc.cbClsExtra?=?0;
wc.cbWndExtra?=?0;
wc.hbrBackground?=?(HBRUSH)GetStockObject(WHITE_BRUSH);
wc.hCursor?=?LoadCursor(?NULL,?IDC_ARROW?);
wc.hIcon?=?LoadIcon(?NULL,?IDI_APPLICATION?);
wc.hIconSm?=?LoadIcon(?NULL,?IDI_APPLICATION?);
wc.hInstance?=?hInstance;
wc.lpfnWndProc?=?WinMessageProc;
wc.lpszMenuName?=?NULL;
wc.lpszClassName?=?_T("MyWindowClass");
if(?!RegisterClassEx(?&wc?)?)
{
MessageBox(?NULL,?_T("注冊窗口類出錯"),?_T("出錯"),?MB_OK?);
return?0;
}
上面的WNDCLASS的各個成員值我就不一一介紹是什么含義,MSDN上面講的非常清楚,我只講一兩個比較重點的,第一個lpszClassName這個成員,我們給它指定的是_T("MyWindowClass")這個值,這是指定這個窗口類的名字是什么,因為下面的創建窗口會用到這個名字。
lpfnWndProc這個成員是WinMessageProc這個函數,這是指定這個窗口類所創建的窗口的消息處理函數是哪一個,我們這里指定的是WinMessageProc。其他的參數我就不啰嗦了,各位不懂的MSDN一下或者在群里來交流一下。
指定了這個窗口類有哪些特性后就完了?當然沒有,沒有注冊怎么使用啊,所以還需要注冊,注冊調用RegisterClassEx這個API函數,將剛才的WNDCLASS變量的地址傳給它就可以進行注冊了,如果注冊失敗,返回值為零,成功的話返回值為非零。
注冊了窗口類,我們來第二步,創建一個窗口。代碼如下:
//?根據上面注冊的一個名叫MyWindowClass?的窗口類創建窗口
HWND?newWind?=?CreateWindowEx(?0L,?_T("MyWindowClass"),?_T("beyondcode"),?WS_OVERLAPPEDWINDOW,?0,?0,?200,?200,?NULL,?NULL,?hInstance,?NULL?);
if(?NULL==newWind?)
{
MessageBox(?NULL,?_T("創建窗口出錯"),?_T("出錯"),?MB_OK?);
return?0;
}
ShowWindow(?newWind,?nShowCmd?);
UpdateWindow(?newWind?);
可見,創建窗口用CreateWindowEx這個API函數,它的第一個參數是擴展樣式,我們這里不設置擴展樣式,所以傳遞0L,第二個參數就是窗口類的名字,我們這里指定我們上面已經注冊了的那個名叫MyWindowClass的窗口類,第三個參數是窗口的標題,隨便設置,第四個參數是窗口的樣式,我們這里設置的是WS_OVERLAPPEDWINDOW,一般主窗口都用這個樣式,就是有最大化,最小化框,有標題欄,有系統菜單。。具體的可以參見MSDN,第五個,六個,七個,八個參數分別指定窗口的初始坐標和長寬,第九個參數指定父窗口是哪個,這里沒有父窗口,所以傳遞NULL,第十個參數指定菜單的句柄,我們這里不設置菜單,所以傳遞NULL,第十一個是應用程序句柄,用WinMain傳遞進來的那個hInstance參數,第十二個參數表示額外數據,不設置,所以為NULL。
這個API函數有點復雜,不過用熟悉了也就不覺得了。這樣我們就創建了一個窗口,返回值是一個窗口的句柄,如果是NULL的話,說明創建窗口失敗了,如果不是NULL的話,說明成功了。
光創建成功了還不行,如果你不顯示和更新它,你還是看不到它,所以需要調用2個API函數,ShowWindow和UpdateWindow,參數就是剛才創建成功的那個窗口的句柄,至于ShowWindow的第二個參數是指顯示的類型,是最大化顯示呢還是最小化顯示呢,不過在程序中第一次調用ShowWindow必須使用WinMain所傳遞進來的參數的第四個參數的值。這是MSDN上說的~
窗口創建成功了,下面一步是消息循環了,消息循環說起來復雜,其實代碼挺簡單的,而且基本格式固定,如下:
//消息循環
MSG?msg;
while(?GetMessage(?&msg,?NULL,?0,?0?)?)
{
TranslateMessage(?&msg?);
DispatchMessage(?&msg?);
}
看到了嗎??一直在一個循環里面,一直調用GetMessage,只要GetMessage所取得的消息不是WM_QUIT的話,那么GetMessage的返回值就不是0,那么循環就一直進行。在循環內部,將GetMessage取得的消息傳遞給TranslateMessage和DispatchMessage兩個API函數進行處理.其中DispatchMessage就是將消息發送給了對應的窗口的窗口消息處理函數進行處理。至于TranslateMessage呢,則進行一些消息的轉換,可以先不深究。
最后就是編寫窗口消息處理函數的代碼了,你需要處理那些消息,那么你就編寫處理那些消息的代碼,對于你不處理的消息,則統統交給一個叫DefWindowProc的API函數進行默認的處理。
LRESULT?CALLBACK?WinMessageProc(?HWND?hwnd,?UINT?msg,?WPARAM?wParam,?LPARAM?lParam?)
{
switch?(?msg?)
{
case?WM_DESTROY:
{
PostQuitMessage(?0?);
break;
}
default:
return?DefWindowProc(?hwnd,?msg,?wParam,?lParam?);
}
return?0;
}
這里我們只處理了WM_DESTROY這個消息,這個消息是在窗口被銷毀的時候發送給窗口消息處理函數的,在窗口處理函數中,我們判斷這個消息是不是WM_DESTROY,如果是,就調用PostQuitMessage這個API函數,如果是其他消息,我們就不管,將參數全部傳遞給DefWindowProc這個函數進行處理。
而PostQuitMessage這個API函數的功能就是發送一個WM_QUIT的消息。而我們前面說到過,?在消息循環中GetMessage一旦取得WM_QUIT這個消息,就返回值為0,那么消息循環也就結束了,進而整個程序也就結束了,如果在這里我們處理WM_DESTORY函數,但是不調用PostQuitMessage,那么結果會怎樣呢,讀者朋友們思考一下~~
好了,到這里,這個什么功能也沒有的SDK程序也就完了,它只顯示一個帶有標題欄的可最大化,最小化的窗口,除了能夠關閉它,你幾乎不能進行其他任何操作,因為我們除了處理窗口銷毀這個消息,其他任何消息我們都沒處理。
這篇文章中的源代碼在VS2008,windows?7平臺下編寫并完成編譯運行,我會將源代碼上傳到群空間或其他地方,以方便新手朋友們對比學習~?
?
總結
以上是生活随笔為你收集整理的Windows API入门系列之五 -一个正儿八经的SDK程序的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ap接口 php_2018年小米高级 P
- 下一篇: Windows API入门系列之六 -自