点击这里获得更多Windows界面开发相关的资料。要了解开源的界面库WinxGui,请访问WinxGui的官方网站。
出处:从WinMain开始
作者:于无声处
本文应一个初学Windows程序设计的朋友而作。
目录
根据Joel的抽象渗漏法则,所有重大的抽象机制在某种程度上都是有漏洞的。Joel举过一个例子:
当我想训练某人成为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 //指明应该以什么方式显示主窗口
);
声明,并且实现这个函数,让Linker程序可以找到它,让编程语言的运行时刻库在完成一些必要的初始化工作后,能够正确地调用它。所以,认为它就是程序的入口点,也是一种简单的“抽象法则”。
在这个入口点函数中,需要按顺序做下面几件事(如果是基于事先设计并存放在资源里的对话框的程序,稍有不同,以后再说):
- 用RegisterClassEx函数登记一个独一无二的Class
- 用CreateWindowEx函数创建一个主窗口
- 进入一个”消息循环“,直到收到WM_QUIT消息
- 从WinMain函数返回
基本上所有的流程都如出一辙,所以完全可以设计出一个“Template模式”出来重用,让以后的程序直接从某个抽象基类继承,实现基类所需的虚方法就可以了,不过为了不偏离重心,还是用C语言的方式写出来:如此简单,WinMain这个函数只有这么短,分别调用三个自定义函数就OK了。
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 ;
}
}
2、窗口消息回调函数
简单地说,回调(Callback)函数就是一个按规定原型实现的一个函数,当别人来调用。比如说,每个窗口都有一个窗口类(用RegisterClassEx登记的Class,或者系统缺省已实现的Class),每个窗口类有一个回调函数,当窗口收到WIndows消息的时候,就会去调用这个回调函数,而这个回调函数的代码是程序员自己写的,用来根据实际情况处理不同的窗口消息。
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字串来做类名。
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。
MSG msg;
while (GetMessage( & msg, 0 , 0 , 0 )) {
TranslateMessage( & msg);
DispatchMessage( & msg);
}
return static_cast < int > (msg.wParam);
}
#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 ;
}
}