Win32 API应用程序框架结构简
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Win32 API应用程序框架结构(2)
1.3.3 头文件的类型
与传统的C程序一样,在程序的开始都会包含头文件,并在程序中会有对调用函数的申明。
在每一个用C编写的Windows程序中,都会用到一个头文件WINDOWS.H。
其实,WINDOWS.H只是一个包含文件,包含了其他的Windows头文件。
当然,在这些被包含的头文件中也可能还包含着其他的Windows头文件。
这些头文件中最重要的有如下几种。
●WINDEF.H:基本类型的定义。
●WINNT.H:支持Unicode的类型定义。
●WINGDI.H:图形设备接口的定义。
●WINUSER.H:用户接口的定义。
●WINBASE.H:内核函数的定义。
●WINSOCK.H和WINSOCK2.H:用于网络通信的套接字的定义。
在这些头文件中,定义了Windows提供的所有数据类型、常数标识符、函数原型和数据结构等,都是Windows文档中最重要的部分。
在Visual C++的Include 子目录中可以找到所有的头文件。
1.3.4 函数和数据结构
在C语言中有一个入口函数main,在Windows应用程序中同样也有一个入口函数WinMain,它的原型在WINBASE.H中声明,如下所示:
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance,
LPSTR lpCmdLine, int nShowCmd);
其中,第一个参数被称为“实例句柄”,该句柄惟一地标识了该程序,其他Windows 函数可以通过将该句柄作为参数来调用该程序;第二个参数只是为了与早期的版本相兼容,在32位Windows程序中,该概念已经不再被采用,所以总是设置为NULL;第三个参数用于运行程序的命令行,可以通过它将文件在程序启动时载入内存;第四个参数表示程序在运行时是以何种方式显示的(从正常、最大化和最小化中选一)。
在程序Hello中,除了用到WinMain函数外,还用到了以下的API函数。
●LoadIcon:为程序加载图标以供使用。
●LoadCursor:为程序加载鼠标指针以供使用。
●GetStockObject:获取图形对象。
●RegisterClass:为主程序的窗口注册窗口类。
●ShowWindow:在屏幕上显示窗口。
●UpdateWindow:命令窗口刷新自身。
●GetMessage:从消息队列中获取消息。
●TranslateMessage:转换某些键盘消息。
●DispatchMessage:将消息发送给窗口过程。
●BeginPaint:开始在窗口的客户区绘制。
●GetClientWindow:获得窗口客户区的尺寸。
●SetTextColor:设置文本的颜色。
●DrawText:显示文本串。
●EndPaint:结束绘制。
●InvalidateRect:强制刷新指定的区域,程序Hello刷新整个窗口的客户区。
●PostQuitMessage:在消息队列中插入一条“退出”消息。
●DefWindowProc:执行默认的消息处理。
这些函数在头文件WINDOWS.H中都有原型的申明,具体的信息可以查阅MSDN (Microsoft Developer Network)中SDK平台的部分函数说明。
除了用到的这些API函数外,还使用了在Windows头文件中定义的4个数据类型:MSG(消息结构)、WNDCLASS(窗口类结构)、PAINTSTRUCT(绘图结构)和RECT(矩形结构)。
1. 消息结构
消息结构定义一个用于发送的消息,其中包含消息的具体信息,在程序接收到消息后,根据其中包含的信息做出相应的处理。
其结构声明如下:
typedef struct tagMSG {
HWND hwnd; // 获得消息的窗口句柄
UINT message; // 消息标志
WPARAM wParam; // 消息的附加信息
LPARAM lParam; // 消息的附加信息
DWORD time; // 消息的发送时间
POINT pt; // 当消息发送时,鼠标所处的屏幕位置
} MSG, *PMSG;
2. 窗口类结构
窗口总是在窗口类的基础上创建的,窗口类用来标识处理窗口消息的窗口过程。
在一个窗口类的基础上可以创建多个窗口的实例,所以在程序创建窗口之前,必须先调用函数RegisterClass注册一个窗口类。
该函数中只带有一个参数,它是一个指向类型为WNDCLASS的结构指针。
其结构声明如下:
typedef struct _WNDCLASS {
UINT style; // 窗口类的风格
WNDPROC lpfnWndProc; // 指向窗口过程的指针
int cbClsExtra; // 分派给窗口类的扩展的字节数
int cbWndExtra; // 分派给窗口实例的扩展的字节数
HINSTANCE hInstance; // 窗口的实例句柄
HICON hIcon; // 类图标的句柄
HCURSOR hCursor; // 类鼠标指针的句柄
HBRUSH hbrBackground; // 刷新背景所用的画刷的句柄
LPCTSTR lpszMenuName; // 窗口类包含的菜单的名称
LPCTSTR lpszClassName; // 窗口类名
} WNDCLASS, *PWNDCLASS;
现在,结合程序Hello依次来看WNDCLASS结构中的各个域。
●第一个域语句:ws.style = CS_HREDRAW | CS_VREDRAW
这条语句使用C的按位“或”操作符组合了两个“类风格”标识符。
有关的标识符是在WINUSER.H中定义的,可以查看该文件中以CS为前缀的标识符定义集合。
在本程序中,使用的这两个标识符的意义是当窗口水平方向尺寸(CS_HREDRAW)发生改变时和当窗口的垂直方向尺寸(CS_VREDRAW)发生改变时,窗口都要完全刷新。
这一点在改变窗口的尺寸时,可以明显地看到。
无论窗口如何变化,文本总是出现在窗口的中央(因为在客户区输出文本时,定义将其显示在窗口的中央,每一次窗口的刷新都要执行此操作)。
●第二个域语句:ws.lpfnWndProc = (WNDPROC)WndProc
这条语句的作用是将窗口类的窗口过程设置为WndProc,这是在本程序中定义的第二个函数,用来处理基于这个窗口类创建的所有窗口的全部消息。
在类定义中,实际上使用的是一个指向函数的指针,通过该指针调用同名函数。
●第三和第四个域语句:wc.cbClsExtra = 0;和wc.cbWndExtra = 0
这两条语句用于为程序预留空间,程序可以根据需要来使用它们。
在本程序中没有使用到,所以赋值为零。
●第五个域语句:wc.hInstance = hInstance
这条语句指定了程序的实例句柄,该句柄可以从WinMain函数的第一个参数处获得。
●第六个域语句:wc.hIcon = LoadIcon( NULL, IDI_APPLICATION )
这条语句用于为所有基于这个窗口类建立的窗口设置一个图标。
图标是一个小的位图图像,将出现在Windows任务栏中和窗口的标题栏的左端。
用户可以定义自己的图标加载到程序中,也可以使用系统提供的图标。
在本程序中,为简单起见,使用系统提供的应用程序的标准图标(ID为IDI_APPLICATION),使用函数LoadIcon将图标加载到应用程序中。
该函数返回一个图表句柄。
●第七个域语句:wc.hCursor = LoadCursor(NULL, IDC_ARROW)
这条语句表示窗口载入一个预定义命名为IDC_ARROW的鼠标光标(形状为箭头),并返回该光标的句柄。
在Win32 API中定义了一系列的鼠标光标,具体内容可以参考MSDN文档中Platform SDK/Windows User Interface/LoadCursor等内容。
当程序启动且鼠标的位置处于窗口的客户区时,鼠标变成箭头形状。
另外,也可以自定义鼠标光标的形状。
●第八个域语句:wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1)
这条语句指定了基于这个窗口类所创建的窗口的背景颜色。
在Windows API中,用某一种颜色覆盖背景一般都需要用到画刷(brush),以指定的颜色来填充一个区域。
在本程序中,使用了比窗口颜色稍亮的颜色作为画刷的颜色。
在Windows 中还定义了一些标准的画刷,称为“备用画刷”(stock brush)。
利用GetStockObject函数可以调用一个备用画刷。
例如,
hBrush=GetStockObject(WHITE_ BRUSH),其中的WHITE_BRUSH就是一个标准的画刷
Win32 API应用程序框架结构(3)
●第九个域语句:wc.lpszMenuName = lpszAppName
这条语句指定窗口类的菜单,其中,lpszAppName指向存储菜单名字符串的指针。
在本程序中没有用到菜单,所以该字段被设置为NULL。
●第十个域语句:wc.lpszClassName = lpszAppName
最后一个域给出了窗口类的类名。
在提供的程序中,类名和窗口类的菜单名相同。
3. 矩形结构
矩形结构相对简单,它定义了四个LONG域,前两个表示矩形左上点的横坐标和纵坐标(通常用X和Y表示)的值,后两个表示矩形右下点的X,Y值。
其结构声明如下:
typedef struct _RECT {
LONG left; // 左上点的X坐标
LONG top; // 左上点的Y坐标
LONG right; // 右下点的X坐标
LONG bottom; // 右下点的Y坐标
} RECT, *PRECT;
有关绘图结构的具体内容将在讲解GDI(图形设备接口,Graph Device Interface)时重点介绍。
下一小节将具体介绍Windows消息机制,整个Windows 是以消息(Message)作为传递信息的手段的消息机制可以说是Windows的灵魂。
1.3.5 消息机制
1. 消息循环
在调用了UpdateWindows之后,窗口就会出现在显示器上,此时Windows就为程序维护一个“消息队列”。
当Windows侦测到程序的用户键盘和鼠标输入数据时,就将此输入事件转化为一个“消息”,并将此消息放到程序的消息队列中。
程序通过执行被称为“消息循环”的代码从消息队列中取出消息。
实现循环的程序代码如下:
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
其中,变量msg是类型为MSG的结构,在前一小节中已经介绍过这个结构的有关信息。
消息循环首先从消息队列中取出一个消息,其方法如下所示:
GetMessage(&msg, NULL, 0, 0);
它将取到的消息赋给一个指向名为msg的MSG结构的指针。
第二、第三和第四个参数分别设置为NULL或者0,表示程序将接收它创建的所有窗口的所有消息。
只要从消息队列中取出的消息的message域不为WM_QUIT(其值为0x0012),GetMessage就返回一个非零的值。
然后,进行一些键盘消息转换(在讲解有关键盘输入的章节中进行详细讨论),其方法是:
TranslateMessage( &msg );
最后,消息又将被转回Windows,Windows会将消息发送给适当的窗口过程进行处理,即Windows调用窗口过程(在程序Hello中是WndProc函数),其方法是:
DispatchMessage(&msg);
当处理完消息后,窗口过程又将控制返回给Windows,此时,Windows仍处在函数DispatchMessage的调用中。
当得到返回后,Windows结束DispatchMessage 函数的调用,重新开始调用GetMessage函数从消息队列中获取消息。
从以上可以看出,消息只是在窗口过程和Windows之间传递,程序并不会直接调用窗口过程。
如果要从程序调用自己的窗口过程,就要用到SendMessage函数,这在后续的章节中会重点讨论。
并不是所有的消息都会进入消息队列,加入消息循环。
一般来说,进队的消息基本上是用户输入的结果(包括鼠标输入和键盘输入)。
另外,进队的消息还包括时钟消息(WM_TIMER)、刷新消息(WM_PAINT)和退出消息(WM_QUIT)等。
不进队的消息一般来自调用特定的Windows函数。
例如,调用CreateWindow时,将直接给窗口过程发送一个WM_CREATE的消息;调用ShowWindow时,将直接给窗口过程发送名为WM_SIZE和WM_SHOWWINDOW的消息。
消息的循环和处理十分复杂,但绝大部分的消息处理都由Windows完成,而且是有规律可循的。
消息其实只是应用程序与Windows交互的媒介,存在于窗口过程和Windows之中。
2. 消息及消息处理
窗口过程接收的每一个消息都是用数值标识的,也就是传递给窗口过程msg
结构中的message参数。
在Windows头文件WINUSER.H中,为每个消息参数定义以“WM”(Window Message)为前缀的标识符。
当然,也可以在自己程序的头文件中定义自己的消息参数。
一般来说,Windows程序员都会选择使用switch和case的结构来确定窗口过程接收到的是什么消息。
窗口过程在处理消息时,必须返回零,所有窗口过程不予处理的消息都应该被传给函数DefWindowProc处理。
事实上, DefWindowProc 并不是惟一处理其他消息的函数,在接触多文档时,还会碰到用于相似处理内容的函数。
从函数DefWindowProc返回的值必须由窗口过程返回,函数只是将消息进行加工,具体工作还是要由Windows来完成。
在程序中,WndProc函数只处理三种消息:WM_PAINT,WM_KEYDOWN和WM_DESTROY。
窗口过程的总体结构如下:
switch(uMsg)
{
// 定义有关变量
case WM_PAINT :
// 处理WM_PAINT消息的过程
break;
case WM_KEYDOWN :
// 处理WM_KEYDOWN消息的过程
break;
case WM_DESTROY :
// 处理WM_DESTROY消息的过程
break;
default :
return(DefWindowProc(hWnd, uMsg, wParam, lParam));
}
return(0L);
在上面结构的定义中,利用“default :”来调用DefWindowProc函数为窗口过程不予处理的消息提供的默认的处理是必需的。
因为在窗口过程中,有很多很重要的工作都要Windows来完成。
如果不能将这些消息及时地发送给Windows,则窗口的创建和维护就不会成功。
用户可以将此函数去掉后再编译运行,就会发现其问题所在。
(1)WM_PAINT消息
当客户区的一部分或者全部变为无效时,将由这个消息通知程序“刷新”该区域。
在很多情况下,客户区会变得无效,下面介绍经常遇到的几种情况。
●在创建窗口时,客户区将变得无效。
此时,整个客户区都是无效的。
在程序运行函数UpdateWindow时,将发送第一个WM_PAINT的消息,指示窗口过程在客户区域画一些东西。
●改变应用程序窗口的大小时,窗口的整个客户区将变得无效。
因为在定义窗口类的第一个域style时,曾经将标志设置为CS_HREDRAW和
CS_VREDRAW,这一设置将指示Windows在窗口改变大小后,使整个窗口无效。
此时,又将发送一个WM_PAINT的消息。
●在移动窗口并发生窗口重叠时,窗口客户区将变得无效。
此时,Windows将不会保存本窗口中被另外一个窗口覆盖部分的客户区内容。
此时,窗口客户区将变成无效,发送一个WM_PAINT的消息。
除了这些由程序接收到外部变化和自动刷新之外,还可以通过函数强制刷新客户区的一部分或者全部,将在下一个消息中介绍这种方法。
对消息WM_PAINT的处理,一般都从BeginPaint函数调用开始,其调用方法是:
hdc=BeginPaint(hWnd, &ps);
结束消息WM_PAINT的处理,一般通过调用EndPaint函数完成,其调用方法是:EndPaint(hWnd, &ps);
在这两个调用中,第一个参数是程序的窗口句柄,第二个参数是指向类型为PAINTSTRUCT结构的指针。
在调用函数BeginPaint成功时,程序将调用在窗口类中定义的背景画刷来将整个客户区刷新,并返回一个“设备环境句柄(hdc)”。
所谓设备环境,是指物理输出设备(如显示器)及其设备驱动程序。
在窗口的客户区画任何东西(包括文本和图形)都需要用到设备环境句柄。
当程序调用了函数EndPaint后,就使设备环境句柄不再有效。