上一篇,講了一個簡單的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程序也就完了,它只顯示一個帶有標題欄的可最大 化,最小化的窗口,除了能夠關閉它,你幾乎不能進行其他任何操作,因為我們除了處理窗口銷毀這個 消息,其他任何消息我們都沒處理。