從WinMain開始
本文應一個初學Windows程式設計的朋友而作。
目錄
根據Joel的抽象滲漏法則,所有重大的抽象機制在某種程度上都是有漏洞的。Joel舉過一個例子:
C++字串型別應該能讓你假裝字串是個基本型別,它們嘗試“字串很難處理”這個事實抽象掉,讓它使用上象整型一樣容易,幾乎所有C++字串型別都會過載加號運算子,才能把字串連線寫成s + "bar"。不過你知道嗎?不管怎麼努力,世上還是沒有C++字串型別能讓你寫成 "foo"+"bar",因為C++裡的字串常數一定是char *,絕對不會變成字串。這個抽象機制呈現一個程式語言本身不給補的漏洞。
當我想訓練某人成為C++程式設計師時,最好能完全不教char *和指標運算,直接去學STL字串。問題是總有一天他們會寫出 "foo" + "bar" 這樣的程式碼,然後看到怪事出現,於是我就得停下來教他們有關char *的事情。他們也可能會試著呼叫某個需要OUT LPTSTR引數的Windows API,於是又得把char *、指標、Unicode、wchar_t以及tchar.h搞懂,才會知道如何呼叫。而這些全都是漏洞。
用API來搭建一個GUI程式是比較枯燥的,這種對於Windows GUI程式的枯燥搭建進行的抽象封裝,它的所謂“某種程度上的漏洞”,也許就是使程式設計師根本不知道每個視窗都有一個視窗類(不是指OO語言裡的Class),而每一個視窗類都有一個回撥函式(Callback)來對不同的視窗訊息進行不同的響應。
做為現在才接觸Windows GUI程式設計的初學者,幾乎都不瞭解一個Windows GUI程式是從WinMain開始的(前面說過,從WinMain開始也只是一個抽象而已,真實的情況並不是這樣),那麼如何僅僅使用Windows的API函式來建立一個GUI程式呢?
1、WinMain()函式
首先,必須要宣告一個WinMain()函式(為了簡明起見,這裡先不討論_tWinMain這個巨集,也不考慮Unicode的問題),它的原型在Windows.h中定義:
- int WINAPI WinMain(
- HINSTANCE hInstance, //程式當前例項的控制程式碼,以後隨時可以用GetModuleHandle(0)來獲得
- HINSTANCE hPrevInstance, //這個引數在Win32環境下總是0,已經廢棄不用了
- char * lpCmdLine, //指向以/0結尾的命令列,不包括EXE本身的檔名,
- //以後隨時可以用GetCommandLine()來獲取完整的命令列
- int nCmdShow //指明應該以什麼方式顯示主視窗
- );
int WINAPI WinMain(
HINSTANCE hInstance, //程式當前例項的控制程式碼,以後隨時可以用GetModuleHandle(0)來獲得
HINSTANCE hPrevInstance, //這個引數在Win32環境下總是0,已經廢棄不用了
char * lpCmdLine, //指向以/0結尾的命令列,不包括EXE本身的檔名,
//以後隨時可以用GetCommandLine()來獲取完整的命令列
int nCmdShow //指明應該以什麼方式顯示主視窗
);
宣告,並且實現這個函式,讓Linker程式可以找到它,讓程式語言的執行時刻庫在完成一些必要的初始化工作後,能夠正確地呼叫它。所以,認為它就是程式的入口點,也是一種簡單的“抽象法則”。
在這個入口點函式中,需要按順序做下面幾件事(如果是基於事先設計並存放在資源裡的對話方塊的程式,稍有不同,以後再說):
- 用RegisterClassEx函式登記一個獨一無二的Class
- 用CreateWindowEx函式建立一個主視窗
- 進入一個”訊息迴圈“,直到收到WM_QUIT訊息
- 從WinMain函式返回
基本上所有的流程都如出一轍,所以完全可以設計出一個“Template模式”出來重用,讓以後的程式直接從某個抽象基類繼承,實現基類所需的虛方法就可以了,不過為了不偏離重心,還是用C語言的方式寫出來:
- int WINAPI WinMain(HINSTANCE, HINSTANCE, char *, int cmdShow) {
- if (registerMyClass() && createMyWindow(cmdShow)) {
- return messageLoop();
- } else {
- std::ostringstream msg;
- msg << "建立主視窗失敗,錯誤程式碼:" << GetLastError();
- MessageBoxA(0, msg.str().c_str(), 0, MB_OK | MB_ICONSTOP);
- return 0;
- }
- }
int WINAPI WinMain(HINSTANCE, HINSTANCE, char *, int cmdShow) {
if (registerMyClass() && createMyWindow(cmdShow)) {
return messageLoop();
} else {
std::ostringstream msg;
msg << "建立主視窗失敗,錯誤程式碼:" << GetLastError();
MessageBoxA(0, msg.str().c_str(), 0, MB_OK | MB_ICONSTOP);
return 0;
}
}
如此簡單,WinMain這個函式只有這麼短,分別呼叫三個自定義函式就OK了。
2、視窗訊息回撥函式
簡單地說,回撥(Callback)函式就是一個按規定原型實現的一個函式,當別人來呼叫。比如說,每個視窗都有一個視窗類(用RegisterClassEx登記的Class,或者系統預設已實現的Class),每個視窗類有一個回撥函式,當視窗收到WIndows訊息的時候,就會去呼叫這個回撥函式,而這個回撥函式的程式碼是程式設計師自己寫的,用來根據實際情況處理不同的視窗訊息。
- LRESULT CALLBACK onMainWndMessage(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam) {
- switch (msg) {
- case WM_DESTROY:
- PostQuitMessage(0); //如果是“視窗銷燬”事件,則應該在訊息佇列中投遞
- break; //一個WM_QUIT訊息,使GetMessage()返回FALSE
- default:
- return DefWindowProc(wnd, msg, wParam, lParam);
- }
- return 0;
- }
LRESULT CALLBACK onMainWndMessage(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_DESTROY:
PostQuitMessage(0); //如果是“視窗銷燬”事件,則應該在訊息佇列中投遞
break; //一個WM_QUIT訊息,使GetMessage()返回FALSE
default:
return DefWindowProc(wnd, msg, wParam, lParam);
}
return 0;
}
3、登記視窗類
在建立主視窗之前,一定要先用RegisterClassEx這個API函式登記一個類,類名必須是獨一無二的,所以一般都用GUID字串來做類名。
- bool registerMyClass() {
- WNDCLASSEX wce = {0};
- wce.cbSize = sizeof(wce);
- wce.style = CS_VREDRAW | CS_HREDRAW;
- wce.lpfnWndProc = &onMainWndMessage; //指明回撥函式
- wce.hInstance = GetModuleHandle(0);
- wce.hIcon = LoadIcon(0, MAKEINTRESOURCE(IDI_WINLOGO));
- wce.hCursor = LoadCursor(0, MAKEINTRESOURCE(IDC_ARROW));
- wce.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_BTNFACE+1);
- wce.lpszClassName = CLASS_NAME; //獨一無二的類名
- wce.hIconSm = wce.hIcon;
- return 0!=RegisterClassEx(&wce);
- }
bool registerMyClass() {
WNDCLASSEX wce = {0};
wce.cbSize = sizeof(wce);
wce.style = CS_VREDRAW | CS_HREDRAW;
wce.lpfnWndProc = &onMainWndMessage; //指明回撥函式
wce.hInstance = GetModuleHandle(0);
wce.hIcon = LoadIcon(0, MAKEINTRESOURCE(IDI_WINLOGO));
wce.hCursor = LoadCursor(0, MAKEINTRESOURCE(IDC_ARROW));
wce.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_BTNFACE+1);
wce.lpszClassName = CLASS_NAME; //獨一無二的類名
wce.hIconSm = wce.hIcon;
return 0!=RegisterClassEx(&wce);
}
4、建立主視窗
(略,直接看完整程式碼)
5、訊息迴圈
訊息迴圈很簡單,僅當GetMessage這個API函式返回FALSE時,才退出迴圈。而GetMessage()僅當處理到訊息佇列中的WM_QUIT訊息時才會返回FALSE。
- int messageLoop() {
- MSG msg;
- while (GetMessage(&msg, 0, 0, 0)) {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
- return static_cast<int>(msg.wParam);
- }
int messageLoop() {
MSG msg;
while (GetMessage(&msg, 0, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return static_cast<int>(msg.wParam);
}
- #include <sstream>
- #include <Windows.h>
- //獨一無二的類名,一般用GUID字串,以免與其他程式的類名重複
- static const char * CLASS_NAME = "{198CEAB2-AD78-4ed3-B099-247639080CB0}";
- /************************************************************************
- 回撥函式,當主視窗收到任何Windows訊息時被呼叫
- ************************************************************************/
- LRESULT CALLBACK onMainWndMessage(HWND wnd, UINT msg, WPARAM wParam, LPARAM lParam) {
- switch (msg) {
- case WM_DESTROY:
- PostQuitMessage(0); //如果是“視窗銷燬”事件,則應該在訊息佇列中投遞
- break; //一個WM_QUIT訊息,使GetMessage()返回FALSE
- default:
- return DefWindowProc(wnd, msg, wParam, lParam);
- }
- return 0;
- }
- /************************************************************************
- 登記自己的視窗類
- ************************************************************************/
- bool registerMyClass() {
- WNDCLASSEX wce = {0};
- wce.cbSize = sizeof(wce);
- wce.style = CS_VREDRAW | CS_HREDRAW;
- wce.lpfnWndProc = &onMainWndMessage; //指明回撥函式
- wce.hInstance = GetModuleHandle(0);
- wce.hIcon = LoadIcon(0, MAKEINTRESOURCE(IDI_WINLOGO));
- wce.hCursor = LoadCursor(0, MAKEINTRESOURCE(IDC_ARROW));
- wce.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_BTNFACE+1);
- wce.lpszClassName = CLASS_NAME; //獨一無二的類名
- wce.hIconSm = wce.hIcon;
- return 0!=RegisterClassEx(&wce);
- }
- /************************************************************************
- 建立並顯示主視窗
- ************************************************************************/
- bool createMyWindow(int cmdShow) {
- HWND mainWnd = CreateWindowEx(0, CLASS_NAME, "Demo", WS_OVERLAPPEDWINDOW,
- CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
- 0, 0, GetModuleHandle(0), 0);
- if (0!=mainWnd) {
- ShowWindow(mainWnd, cmdShow);
- UpdateWindow(mainWnd);
- return true;
- } else {
- return false;
- }
- }
- /************************************************************************
- 訊息迴圈
- ************************************************************************/
- int messageLoop() {
- MSG msg;
- while (GetMessage(&msg, 0, 0, 0)) {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
- return static_cast<int>(msg.wParam);
- }
- /************************************************************************
- WinMain,程式入口
- ************************************************************************/
- int WINAPI WinMain(HINSTANCE, HINSTANCE, char *, int cmdShow) {
- if (registerMyClass() && createMyWindow(cmdShow)) {
- return messageLoop();
- } else {
- std::ostringstream msg;
- msg << "建立主視窗失敗,錯誤程式碼:" << GetLastError();
- MessageBoxA(0, msg.str().c_str(), 0, MB_OK | MB_ICONSTOP);
- return 0;
- }
- }
相關文章
- 不怕從零開始,只怕從未開始!
- 從零開始
- WinMain是如何被呼叫的AI
- 從0開始用MavenMaven
- flutter 從零開始-1Flutter
- 從零開始學PythonPython
- Arch! 從安裝開始
- Python Web開發:從 wsgi 開始PythonWeb
- 從零開始仿寫一個抖音App——開始APP
- webpack 從開始到結束Web
- 從頭開始學習VuexVue
- 從零開始--webpack 4 配置Web
- 從零開始學 Spring BootSpring Boot
- 學技術,從性趣開始
- 解密Vuex: 從原始碼開始解密Vue原始碼
- 從今天開始挑戰 LeetCodeLeetCode
- 線段樹從零開始
- 從零開始學正則
- 從 0 開始學架構架構
- 2020年,生活從“不易”開始
- 黑客入門,從HTB開始黑客
- 從零開始認識 SparkSpark
- 從零開始學習laravelLaravel
- 幾何庫從零開始
- Flutter 從一個demo開始Flutter
- 從零開始學習KafkaKafka
- 從 0 開始瞭解 DockerDocker
- 從me.name = 'forceddd' 開始
- Kaizen從哪裡開始下手?AI
- 從0開始fastjson漏洞分析ASTJSON
- 【ROS】從零開始學ROSROS
- 從零開始開發一個 WebpackWeb
- 202409071506,開始寫程式碼,從0開始 驗證基本架子
- Eggjs 從放棄到開始使用JS
- 從Android Studio 開始的ARCore之旅Android
- 從零開始dumpdecrypted砸殼解析
- 從零開始機器學習機器學習
- 從零開始的fgg--htmlHTML
- 從零開始搭建腳手架