首发
http://blog.csdn.net/beyondcode
http://www.cnblogs.com/beyond-code/
http://hi.baidu.com/beyondcode
SDK编程群号:81543028 欢迎加入
上一篇,讲了一个简单的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平台下编写并完成编译运行,我会将源代码上传到群空间或其他地方,以方便新手朋友们对比学习~