终于下定决心,往C++上爬了,用了两天的时间把C++基础语法语义等复习了一遍,再用了几天的时间粗略地把Win32程序设计过了一遍,之后的一礼拜把一本MFC入门书消灭了,咋一看还干了挺多的事情,呵呵,感觉还不错。最近在忙着看MCF的一些内容。目前看得不深入,所以感想不多,先谈谈自己的入门感想吧,总结一下心得,温故知新。
为什么把题目叫做MFC开门篇之Windows API程序设计呢?学习MFC,当然要先了解一下Win32程序设计的相关内容,一个简单的理由就是MFC封装了Window API,所以在学习MFC之前很有必要了解一下Windows API,毕竟MFC从Window API来的,总得需要知道点MFC的一点底细才能赶路。
Win32绝大部分事件是通过消息通信然后转到消息处理函数去处理。而Win32程序的大体框架也如下一般:
//Windows API 头文件
#include<windows.h>
//应用程序实例
HINSTANCE hInst;
//窗口句柄
HWND hWnd;
//消息
MSG msg;
//声明函数原型--------------------------------------------------------------------------------------
ATOM RegisterClass(HINSTANCE hInstance); //注册窗口类函数
BOOL Create(HINSTANCE, int); //程序实例初始化函数
LRESULT CALLBACK WndProc(HWND, UINT,
WPARAM, LPARAM); //窗口函数
int CALLBACK WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
)
{
RegisterClass(hInstance); //定义和注册窗口类
Create(hInstance, nCmdShow); //创建窗口
ShowWindow(hWnd, nCmdShow); //显示窗口
UpdateWindow(hWnd); //更新屏幕显示
while (GetMessage(&msg, NULL, 0, 0)) //消息循环
{
TranslateMessage(&msg); //转化键盘消息
DispatchMessage(&msg); //分派消息
}
return msg.wParam;
}
//注册窗口类函数的实现--------------------------------------------------------------------------
ATOM RegisterClass(HINSTANCE hInstance)
{
..........
}
//创建窗口函数的实现-----------------------------------------------------------------------------
BOOL Create(HINSTANCE hInstance, int nCmdShow)//创建窗口函数实现
{
..........
}
//窗口函数的实现--------------------------------------------------------------------------------------
LRESULT CALLBACK WndProc(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case: .........
case: .........
case: ..........
............................
default: / /如果没有符合的处理则交给系统
DefWindowProc(hWnd, message, wParam,lParam);
}
return 0;
}
Windows 32程序运行流程一般为:当Windows的外壳(shell)检测到使用者欲执行一个Windows程序,于是调用加载器把该程序加载,然后调用C startup code,C startup code再调用WinMain函数开始执行程序,当窗口类初始化、创建以及窗口生成等准备工作做完了之后,就进入消息循环,直到程序退出时,此时GetMessage()函数返回0,此时进程受到WM_QUIT消息,循环结束,WinMain代码执行至完毕,,这里值得注意的是GetMessage的返回值,下面将会说明。
而一般完成一个程序的简要步骤是:
a.注册窗口类型
b.创建窗口
c.显示和更新窗口
d.消息循环
而Windows程序是以消息为基础的,所以消息处理函数也即窗口函数LRESULT CALLBACK WndProc(HWND, UINT,
WPARAM, LPARAM); 一定要写好。
让我们再来看一下上面的各个框架中的函数的简单解析。各函数简单解析如下:
1.
int CALLBACK WinMain( HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
)
这个函数是程序进入点,相当于C语言Dos程序或者Console程序中的main函数。CALLBACK是关键字,表明它是“回调函数”,该函数可由系统调用。CALLBACK被定义为_stdcall,是一种函数调用习惯,关系到参数进入到堆栈的次序,以及处理堆栈的责任归属,(这句不是很理解,没有很多实践,摘自《深入浅出MFC》)。
它的四个参数由系统传入,参数hInstance是当前实例的句柄,它唯一标识运行中的实例,注意,只有运行中的程序实例,才有实例句柄)。一个应用程序可以运行多个实例,每运行一个实例,系统都会给该实例分配一个句柄值,并通过hInstance参数传递给WinMain函数。
hPrevInstance表示前一个应用程序的实例,可以由它来判断当前的进程是否是某个程序的第一个实例,而从MSDN可知:For a Win32-based application, this parameter is always NULL,此参数已弃置,但利用CreatMutex()函数实现一个程序只有一个实例运行,具体查看MSDN了解,或者可以参考http://www.newasp.net/tech/program/23115.html。
第三个参数lpCmdLine是一个以空终止的字符串,指定传递给应用程序的命令行参数。例如:在D盘下有一个sunxin.txt文件,当我们用鼠标双击这个文件时将启动记事本程序(notepad.exe),此时系统会将D:/sunxin.txt作为命令行参数传递给记事本程序的WinMain函数,记事本程序在得到这个文件的全路径名后,就在窗口中显示该文件的内容。要在VC++开发环境中向应用程序传递参数,可以单击菜单【Project】→【Settings】,选择“Debug”选项卡,在“Program arguments”编辑框中输入你想传递给应用程序的参数。
第四个参数nCmdShow指定程序的窗口应该如何显示,例如最大化、最小化、隐藏等。这个参数的值由该程序的调用者所指定,应用程序通常不需要去理会这个参数的值。
2.
ATOM RegisterClass(HINSTANCE hInstance); 参数为当前应用程序实例句柄。在Windows32程序中,所有的窗口类需要注册了才能使用,之后我们可以用这个窗口类来产生不同的窗口,其中最重要的结构变量是WNDCLASS,具体成员查看MSND即可得到还有一个需要注意的是WNDCLASS这个结构变量里的lpfnWndProc成员,它需要把窗口函数的地址复制给它进程才能在受到消息的时候转到指定的窗口函数进行消息的处理。
3.
BOOL Create(HINSTANCE, int); 此函数用于产生窗口对象,才能用于后面的窗口显示和更新,用CreateWindow()函数自定义窗口的大小,窗口名字等。
4.
ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); ,这两个函数用于显示和更新窗口,比较简单。
5.
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);这个函数被称之为窗口的生命中枢,就像我们的大脑,在我们接受到外界的信息之后总会传到大脑进行处理(思考)。而窗口函数正是进程在收到消息后进行处理的地方。 参数HWND是用来表示是哪个窗口(子窗口)传过来的消息,UINT存储消息内容,可以在此函数的switch/case结构中处理各种各样的消息,例如WM_CLOSE,WM_QUIT,WM_PAINTWM_COMMAND等,根据不同的消息进行不同的操作。WPARAM和LPARAM这两个含义因消息的不同而不同,例如收到WM_COMMAND消息时,WPARAM参数地位字节存储的是传来命令字窗口的ID,高位字节存储所发生的事件,所以如果要知道WPARAM的含义,那么得根据具体情况来分类,这里就不列举了。
6.
消息循环这一部分也可以把它写成一个函数,这个我们只是写成几条语句。在这里我们用到了GetMessage来抓消息,让我们先来看看MSDN如何解释这个函数
情况一:If the function retrieves a message other than WM_QUIT, the return value is nonzero.
情况二:If the function retrieves the WM_QUIT message, the return value is zero.
情况三:If there is an error, the return value is -1. For example, the function fails if hWnd is an invalid window handle or lpMsg is an invalid pointer. To get extended error information, call GetLastError.
正常情况下(情况一),GetMessage后进入循环,利用TranslateMessage()来转化键盘消息,再利用DispatchMessage()函数将消息传给窗口函数处理。
情况二:如果收到退出WM_QUIT消息,那么GetMessage返回0,退出循环,结束程序。这里需要注意的是:当我们按下关闭键的时候,系统发出WM_CLOSE消息,DefWindowProc收到WM_CLOSE调用DestroyWindow把窗口清除,DestroyWindow发出WM_DESTROY消息,而一般窗口函数中对WM_DESTROY消息的反映是PostQuitMesssage,这个函数发出WM_QUIT,此时便结束循环(结束进程)。
如果有错误(情况三),那么GetMessage返回-1, call GetLastError()to get the error if you need.
7.
消息映射的雏形.让我们先不看其他的部分,把注意力集中在窗口函数,采用switch/case结构使得一个消息对应一个处理的代码,那么我们是否可以这样做:把处理部分的代码封装成一个函数,再定义一个结构变量
Struct Msg_ENTRY{
NINT Msg;
LONG (*pfn)(hWnd, message, wParam)//函数指针
};
把消息和函数对应起来,然后在窗口函数那里用一个For循环把所有的消息遍历,如果传来的Msg与Msg_ENTRY中的Msg相等,则转去执行这个消息对应的函数?这样不管我们以后增加多少消息,for循环那里的代码可以不变,只需要建立消息和对应处理函数的映射然后实现这个函数就行了。答案是可以的,而且这种处理方法正是MFC消息映射的雏形,例如MFC采用ON_WM_CLOSE这个宏把WM_CLOSE和OnClose()函数对应起来,你只需要添加ON_WM_CLOSE然后完成OnClose函数的实现即可,你也可以自定义函数与消息对应。当然,MFC的实现没那么简单。
正在看侯俊杰先生的《深入浅出MFC》,现在看了一点而已,对MFC的大概框架也有一点理解,但是还不深入,需要慢慢学习,以后有感想再贴上来和大家分享。
写到这里也差不多了,我也只是学习了Window程序设计的一点皮毛,MFC也是正在学习中。希望朋友们可以交流相互学习一下,共同进步。如果你发现文章中的有错误的说法,请告诉我,我可不希望误导了读者,谢谢啦。