第3章,[标签 Win32] :窗口类03,窗口过程函数字段

专栏导航

上一篇:第3章,[标签 Win32] :窗口类02,类风格字段

回到目录

下一篇:第3章,[标签 Win32] :窗口类03,窗口过程函数与消息机制

本节前言

有一段时间没去写文章和发表文章了。一点琐事缠身,导致了写文章的中断。现在,算是继续进行和了。

对于本节所讲解的知识,有可能,你会需要时不时地参考本专栏之前的文章。真的遇到了需要参考之前的文章的知识点,请你自行查阅。

我呢,也会提到一部分的参考课节。但是呢,你不应该依赖于我的主动提及。最好呢,你自己能够多去了解和查看本专栏目录。

学习本节所讲的知识,需要你事先了解过本章的完整代码。完整的代码,请参考下述文章链接。

参考课节:第3章,[标签 Win32] :本章程序代码

在之前,我们有讲过窗口类的基本情况。

为了便于学习,我们还是再次贴出窗口类的各个成员变量,如下所示。

复制代码
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);】,就变成了定义新类型的代码。这一新类型可以用来声明函数指针变量。

函数除了函数名,返回值和参数列表之外,它也存在着调用约定这一要素。

关于调用约定,我们之前有讲过,你可以参考如下链接,来了解调用约定的知识。

参考课节:第1章 :第一个 WIn32 程序,程序入口

在上述文章链接的第七分节里面,有涉及调用约定。

我们平时所写的函数,一般都是采用 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 的用法,可能不是很直观。大家在学习它的时候,如果有困难,建议你多看看,多学学。

结束语

关于窗口过程函数,本节就先讲这么多了。

下一节,我们接着来讲窗口过程函数。

专栏导航

上一篇:第3章,[标签 Win32] :窗口类02,类风格字段

回到目录

下一篇:第3章,[标签 Win32] :窗口类03,窗口过程函数与消息机制

相关推荐
千里马-horse4 小时前
在android中 spdlog库的log如何在控制台上输出
android·c++·spdlog
一苓二肆4 小时前
代码加密技术
linux·windows·python·spring·eclipse
LinXunFeng4 小时前
如何舒适地沉浸式编程,这是我的答案
windows·程序员·mac
aramae4 小时前
详细分析平衡树--红黑树(万字长文/图文详解)
开发语言·数据结构·c++·笔记·算法
再卷也是菜4 小时前
C++篇(13)计算器实现
c++·算法
_w_z_j_5 小时前
C++----变量存储空间
开发语言·c++
初听于你5 小时前
深入了解—揭秘计算机底层奥秘
windows·tcp/ip·计算机网络·面试·架构·电脑·vim
lingran__6 小时前
算法沉淀第五天(Registration System 和 Obsession with Robots)
c++·算法
莱茶荼菜6 小时前
一个坐标转换
c++·算法