专栏导航
上一篇:第3章,[标签 Win32] :窗口类02,类风格字段
下一篇:第3章,[标签 Win32] :窗口类03,窗口过程函数与消息机制
本节前言
有一段时间没去写文章和发表文章了。一点琐事缠身,导致了写文章的中断。现在,算是继续进行和了。
对于本节所讲解的知识,有可能,你会需要时不时地参考本专栏之前的文章。真的遇到了需要参考之前的文章的知识点,请你自行查阅。
我呢,也会提到一部分的参考课节。但是呢,你不应该依赖于我的主动提及。最好呢,你自己能够多去了解和查看本专栏目录。
学习本节所讲的知识,需要你事先了解过本章的完整代码。完整的代码,请参考下述文章链接。
在之前,我们有讲过窗口类的基本情况。
为了便于学习,我们还是再次贴出窗口类的各个成员变量,如下所示。
typedef struct {
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
} WNDCLASS, * PWNDCLASS;
本节,我们要去讲解的,是敞口过程函数指针字段,也是第 2 个字段,lpfnWndProc 。
下面,我们进入本节的正式学习。
一. 窗口过程函数
窗口过程函数,它可以说是 Windows API 程序设计的重点与核心了。Windows API 程序的运行的大部分工作,都在本函数中进行。我们来粗略地看一看这一函数的代码。
在本章程序代码的 60 行到 88 行,是本程序的窗口过程函数代码。
我将窗口过程函数代码摘录在下面。
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
switch (message)
{
case WM_CREATE:
PlaySound(TEXT("HelloWin.wav"), NULL, SND_FILENAME | SND_ASYNC);
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
DrawText(hdc, TEXT("Hello, Windows Program !"), -1, &rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
对于这个函数,我们暂时不需要深究,因为后面我们会去正式讲解它。
此时,你有个印象就好了。
本节,我们需要关注的一段代码,如下图的红色框线所示。
图1
在窗口类中,有一个字段,名叫 lpfnWndProc,它用来保存程序的窗口过程函数指针。
由本章程序的窗口过程函数的头部可知,窗口过程函数名为 WndProc,我们将这个函数名赋给窗口类变量 wndclass 的 lpfnWndProc 成员就可以了。
二. WNDPROC 类型
在窗口类中,lpfnWndProc 成员的类型为 WNDPROC 。
我们来看一看这一类型的定义情况。
typedef LRESULT (CALLBACK* WNDPROC)(HWND, UINT, WPARAM, LPARAM);
这是 typedef 在定义函数指针类型时候的一种用法。
这一段代码,可能有些难度。
我们分解着来讲解。
首先呢,我们来讲解一个简单的情形。
我们来看一个函数代码。
int func_01(double a, int x)
{
int res;
res = a * a + 3 - x;
return res;
}
对于上述的代码,如果你来写它的函数声明,要怎么来写呢?
我们可以将它的函数声明写成下面的样子。
int func_01(double a, int x);
也可以写成下面的样子。
int func_01(double, int);
函数名,标识的是一个函数的地址,也就是函数的指针。函数指针,也是一种指针类型。
下面,我们来声明一个变量,这个变量可以被赋值为一个函数指针。我们可以像下面这样子来做。
int (*func_pointer)(double, int);
在上面的示例代码里面,我们声明了一个函数指针变量,此变量的名字为 func_pointer 。这一函数指针变量所指向的函数,它的参数列表的类型为 (double, int),返回值为 int 类型。
与原来的函数声明代码【int func_01(double, int);】相比,声明函数指针变量的代码【int (*func_pointer)(double, int);】多了一个中间的括号和星号部分。括号和星号,正好可以用来区分普通的函数声明与函数指针变量的声明。
如果,我们将括号给去掉,变为下面的样子。
int *func_pointer(double, int);
在这种情况下,我们是声明了一个函数,函数名为 func_pointer,参数列表的类型为 (double, int),返回值为【int *】类型。
而加上了括号,成为【int (*func_pointer)(double, int);】以后,则我们所声明的,便不是一个函数,而是一个函数指针变量。
我们也可以将此函数指针变量的声明写成如下的样子。
int (*func_pointer)(double a, int x);
在一个普通的函数声明里,如果我们将函数名替换成一个新的组合,这个新组合的外面是一对括号,里面是一个星号加上右边的标识符,则总体来看,这个新的声明,就是声明了一个函数指针变量。
而一个函数指针变量声明中,我们若是将参数列表与返回值之间的括号组合部分,替换成一个普通的标识符,则形成的声明,便是一个普通的函数声明。
函数指针变量,是一个单独的变量。我们如果想要将这一变量类型提炼出来,我们可以这样子做。
typedef int (*FUNC_POINTER)(double, int);
或者也可以这样子写。
typedef int (*FUNC_POINTER)(double a, int x);
经过这样的处理以后,我们声明了一个新的类型。类型名为 FUNC_POINTER,这一类型可以用来声明一个函数指针变量,所声明的函数指针变量所指向的函数,它的参数列表为 (double, int) 或 (double a, int x),返回值为 int 。
我们可以用这个新的类型来声明函数指针变量,比如可以像下面这样子来声明变量。
FUNC_POINTER func_pointer_01;
FUNC_POINTER func_pointer_02;
FUNC_POINTER func_pointer_03;
当然了,如果你不厌其烦,你也可以像下面这样子来声明三个函数指针变量。
int (*func_pointer_01)(double, int);
int (*func_pointer_02)(double, int);
int (*func_pointer_03)(double, int);
相比之下,还是用 typedef 重新定义类型以后,再用新的类型名去声明变量,代码会更有层次感一些。
在原本的函数指针变量声明代码【int (*func_pointer)(double, int);】的基础上,左边再加上一个 typedef 关键字,形成【typedef int (*FUNC_POINTER)(double, int);】,就变成了定义新类型的代码。这一新类型可以用来声明函数指针变量。
函数除了函数名,返回值和参数列表之外,它也存在着调用约定这一要素。
关于调用约定,我们之前有讲过,你可以参考如下链接,来了解调用约定的知识。
在上述文章链接的第七分节里面,有涉及调用约定。
我们平时所写的函数,一般都是采用 CDECL 调用约定。当不明确书写调用约定的名称的时候,我们所采用的,便是默认的 CDECL 调用约定。
调用约定有很多种,一种常用的调用约定,叫做标准调用,其标识符为 __stdcall 。
在 Windows 程序设计中,有很多的重定义的标识符,都是 __stdcall 的别名。
我们之前学过的 WINAPI 是 __stdcall 的别名,CALLBACK 也是它的别名。
我们再来看一看本章程序的窗口过程函数的头部。
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
在这段函数头部里面,LRESULT 是返回值类型,一般情况下它等价于 long 型。而WndProc及其后的东西,为函数名与参数列表。
在返回值类型 LRESULT 与函数名 WndProc 之间的东西,为 CALLBACK 。
CALLBACK 为 __stdcall 的别名,表示标准调用。
有了 CALLBACK,表明本章程序代码中的窗口过程函数 WndProc,采用的调用约定是标准调用 __stdcall 。
对于此窗口过程函数,本章程序代码给出的函数声明代码如下。
图2
图2 中的红色框线所示的部分,为窗口过程函数的声明代码。
我们将其摘录在下面的代码块中。
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
如果我们来声明一个函数指针变量,这个变量可以指向此窗口过程函数,那么,我们可以像下面这样子来声明。
LRESULT (CALLBACK * FuncPointer_WndProc)(HWND, UINT, WPARAM, LPARAM);
或者呢,换一个变量名,将上面的声明代码变为下面这样子。
LRESULT (CALLBACK * lpfnWndProc)(HWND, UINT, WPARAM, LPARAM);
如果,我们想要定义一个新的类型,此新的类型所声明的变量,可以用来指向本章程序的窗口过程函数,我们可以像下面这样子来做。
typedef LRESULT (CALLBACK * WNDPROC)(HWND, UINT, WPARAM, LPARAM);
定义了这一新的类型之后,我们可以用它来声明函数指针变量,并将此函数指针变量赋值为本章程序的窗口过程函数。代码如下。
WNDPROC lpfnWndProc;
lpfnWndProc = WndProc;
而函数指针变量类型 WNDPROC 及其声明的函数指针变量 lpfnWndProc 都被封装在了窗口类 WNDCLASS 里面了,而本章程序代码中的窗口类变量为 wndclass,所以,就有了下面的代码。
图3
到了这里,窗口过程函数字段,及其类型 WNDPROC,我们就算是讲完了。
在这一过程中,我们还讲解了 typedef 在定义函数指针变量类型的用法。
typedef,对我来讲,当初学习它的时候,也算是稍微耗费了一些思考。
typedef 的用法,可能不是很直观。大家在学习它的时候,如果有困难,建议你多看看,多学学。
结束语
关于窗口过程函数,本节就先讲这么多了。
下一节,我们接着来讲窗口过程函数。