第3章,[标签 Win32] :WM_CREATE 消息的产生

专栏导航

上一篇:第3章,[标签 Win32] :消息的处理

回到目录

下一篇:无

本节前言

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

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

本节呢,我们首次讲解 WM_CREATE 消息的产生与处理。

一. WM_CREATE 消息的产生

在 HelloWin 中,窗口过程函数 WndProc 所接收到的第一条消息是 WM_CREATE 。

当 WinMain 函数中处理 CreateWindow 函数调用时,WndProc 将接收到该消息。

注意,是 WinMain 函数在处理 CreateWindow 函数调用的代码,在程序的执行流尚处于 CreateWindow函数而并未返回到 WinMain 中的时候,WM_CREATE 消息产生了。

WM_CREATE 消息产生了以后,它立即由 Windows 将其发送给了窗口过程函数 WndProc 。

在这里呢,我们需要关注的信息有很多。

首先,WM_CREATE 函数在产生了以后,它不是被 Windows 放入应用程序 HelloWin 的消息队列之中,而是由 Windows 直接将其发送给窗口过程函数 WndProc 。所以,WM_CREATE 消息是一条非队列消息,它也是窗口过程函数 WndProc 所接收到的第一条消息。

其次,在这里,所谓的【Windows 直接将其发送给窗口过程函数 WndProc】,它的确切的意思是,Windows 会直接调用窗口过程函数 WndProc 。在调用的时候,第一个参数为该窗口的句柄,第二个参数为 WM_CREATE 。

当产生 WM_CREATE 消息的前一刻,程序其实正处于 CreateWindow 函数之中,并未返回到 HelloWin 程序的 WinMain 函数之中。因此,在这个时候,WinMain 函数中的变量 hwnd 还尚未被设置为窗口句柄的值。

虽说程序尚未返回到 WinMain 函数之中,虽说 hwnd 变量尚不知道窗口句柄是什么,然而,在此时,窗口句柄已经是有了,Windows 已经是给我们造好了这个句柄,Windows 还在程序产生了 WM_CREATE 消息以后,在调用窗口过程函数 WndProc 的时候,使用了这个已经造好了的句柄。

在 Windows 发送 WM_CREATE 消息,也就是 Windows 直接调用窗口过程函数 WndProc 函数的时候,使用到了 CreateWindow 函数中造好了的窗口句柄,而此窗口句柄究竟是什么,此时,HelloWin 程序中的 WinMain 函数尚不知晓,因为此时 CreateWindow 函数尚未返回,WinMain 函数中的 hwnd 变量尚未接收到 CreateWindow 返回的窗口句柄值。

有点绕。

二. WM_CREATE 消息的产生与接收时机

学习本分节,大家需要首先具备使用 Visual Studio 来调试 C/C++ 代码的先修知识。如果你此时还不了解,那么,可以前往本本专栏的目录中,学习相关的课节内容。我将其中的部分课节展示在下面。

在这里,我仅将其中的第一节调试课节的链接贴在下面。

参考课节:编程技能:调试01,调试介绍

我们接着讲。

在这里呢,我先将本章代码 HelloWin 再次贴在下面。

复制代码
/*------------------------------------------------------------
   HELLOWIN.C -- 在窗口客户区显示 "Hello, Windows Program"
                 改编自佩措尔德的 HelloWin 程序
  ------------------------------------------------------------*/

#include <windows.h>
#include <mmsystem.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    PSTR szCmdLine, int iCmdShow)
{
    static TCHAR szAppName[] = TEXT("HelloWin");
    HWND         hwnd;
    MSG          msg;
    WNDCLASS     wndclass;

    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = szAppName;

    if (!RegisterClass(&wndclass))
    {
        MessageBox(NULL, TEXT("注册窗口类失败!"),
            szAppName, MB_ICONERROR);
        return 0;
    }

    hwnd = CreateWindow(szAppName,                  // window class name
        TEXT("The HelloWin Program"), // window caption
        WS_OVERLAPPEDWINDOW,        // window style
        CW_USEDEFAULT,              // initial x position
        CW_USEDEFAULT,              // initial y position
        CW_USEDEFAULT,              // initial x size
        CW_USEDEFAULT,              // initial y size
        NULL,                       // parent window handle
        NULL,                       // window menu handle
        hInstance,                  // program instance handle
        NULL);                     // creation parameters

    ShowWindow(hwnd, iCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}

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);
}

既然是说,Windows 直接选择了直接发送 WM_CREATE 消息,直接调用窗口过程函数 WndProc,那么,我们自己编写的 WndProc 便会接收到 WM_CREATE 消息。

而在 WndProc 接收到 WM_CREATE 消息的时候,WinMain 中的 CreateWindow 依然是并未返回,其中的 hwnd 变量依然不知晓窗口句柄的具体值。

为了了解这一过程,我们稍微修改一下 HelloWin 的代码,修改之后,代码如下。

复制代码
/*------------------------------------------------------------
   HELLOWIN.C -- 在窗口客户区显示 "Hello, Windows Program"
                 改编自佩措尔德的 HelloWin 程序
  ------------------------------------------------------------*/

#include <windows.h>
#include <mmsystem.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HWND         MyHwnd;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    PSTR szCmdLine, int iCmdShow)
{
    static TCHAR szAppName[] = TEXT("HelloWin");
//    HWND         hwnd;
    MSG          msg;
    WNDCLASS     wndclass;

    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = szAppName;

    if (!RegisterClass(&wndclass))
    {
        MessageBox(NULL, TEXT("注册窗口类失败!"),
            szAppName, MB_ICONERROR);
        return 0;
    }

    MyHwnd = NULL;

    MyHwnd = CreateWindow(szAppName,                  // window class name
        TEXT("The HelloWin Program"), // window caption
        WS_OVERLAPPEDWINDOW,        // window style
        CW_USEDEFAULT,              // initial x position
        CW_USEDEFAULT,              // initial y position
        CW_USEDEFAULT,              // initial x size
        CW_USEDEFAULT,              // initial y size
        NULL,                       // parent window handle
        NULL,                       // window menu handle
        hInstance,                  // program instance handle
        NULL);                     // creation parameters

    ShowWindow(MyHwnd, iCmdShow);
    UpdateWindow(MyHwnd);

    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}

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);
}

大家可以将其暂时复制到自己的 HelloWin.cpp 代码文件里面,然后呢,重新生成解决方案。

重新生成了解决方案以后,我们需要设置三个断点,位置如下面的几个截图所示。
图1 图2

然后呢,请大家一起来执行菜单命令【调试 -> 开始调试】。结果如下。
图3

在修改后的 HelloWin.cpp 代码文件里面,我故意地没有在 WinMain 函数中声明 HWND 型变量 hwnd,而是选择在 WinMain 函数的上面,声明了一个 HWND 型变量,MyHwnd 。这个名为 MyHwnd 的变量,在 WInMain 中,替换了原版的 WinMain 函数中使用 hwnd 的几处位置。

当我们设置好了如图1 和图2 所示的三个断点以后,我们执行【开始调试】的指令的时候,程序首先是停在了 40 行的位置,如图3 所示。

此时,程序停在 40 行,但是尚未执行第 40 行的代码,也就是,CreateWindow 函数,此时是尚未执行的。

接下来,我们执行菜单命令【调试 -> 继续】,结果如下。
图4

此时呢,程序停在了第 72 行的位置。

此时,我们需要在 visual studio 中设置一下监视变量。设置情况如下
图5

也就是说,我们需要在【监视1】选项卡中,添加一个监视变量,MyHwnd,以观测这个全局变量的值的变化情况。

添加好了以后,我们会发现,此时,它的值为 0,也就是 NULL 。

关于 NULL,其实它就是 0 值。在我所学习的 Linux 0.12 内核里面,它被定义为【(void *)0】,一个 0 值指针。在 Windows 里面,它依然是一个 0 值,不过,不是【void *】类型。

Windows 对 NULL 的定义代码如下。

#define NULL 0

你可以自己在 Visual Studio 2019 里面,将鼠标指针悬停在代码中的 NULL 的文本区域里面,Visual Studio 会弹出来一个提示框,以指出 NULL 的定义代码。

我们在新代码的 38 行,主动地设置了 MyHwnd 的值,将其设置为 NULL 。这一设置情况,我们可以在图3 中观察到。

我们第一次执行【开始调试】菜单命令的时候,程序停在了图3 中的行号 40 的位置,当时,行号 38 行的代码已经执行完毕,MyHwnd 已经被赋值完了,已经是 NULL 了。

而在我们执行了【继续】调试指令,程序停在了图4 所示的 72 行的位置的时候,根据我们刚刚在【监视1】选项卡中的观察结果,此时的 MyHwnd 的值,依然是 NULL 值。此时,窗口过程函数已经接收到了 WM_CREATE 消息,并且正要执行针对 WM_CREATE 消息的第一行处理代码。

我们再次执行菜单命令【调试 -> 继续】,结果如下。
图6

我们顺便看看此时的【监视1】选项卡,结果如下。
图7

此时,MyHwnd 有值了。由于是刚刚被 CreateWindow 函数的返回值赋予了新的值,所以,截图中,MyHwnd 变量的值呈现为红色字体。

在图3 里面,由于程序刚刚将 MyHwnd 赋值为 NULL,所以,此时 MyHwnd 的值必定为 NULL 。而在图4 和图5 里面,此时的 MyHwnd 变量,依然是 NULL 值。而到了图6 和图7 的时候,MyHwnd 变量刚刚被赋值为 CreateWindow 函数的返回值,一个非零的、有意义的窗口句柄值。

程序的调试进行到图3 的时候,行号 40 行的代码尚未执行,但是程序的调试进度指针是指向此处的。

程序的调试进行到图 4 的时候,行号 40 的代码已经是执行了一部分,CreateWindow 函数已经是执行了一部分,但是,尚未全部执行完,并未返回。在 CreateWindow 并未返回的时候,Windows 发送了 WM_CREATE 消息,调用了 窗口过程函数 WndProc,并且在调用 WndProc 的时候使用了已经造好,而 MyHwnd 尚不知道的窗口句柄值。

而在程序的调试进行到图6 的时候,行号 40 的代码执行完毕了,程序停在了下一个代码行 52 行的位置。行号 40 到 行号 50 的部分,为 CreateWindow 函数调用的代码,行号 51 为空行,行号 52 是行号 40 之后的第一个有效的代码行。也就是,在进行到图6,调试指针停在 52 行的时候,行号 40 到 50 所在的语句刚刚被执行完。此时,MyHwnd 刚刚接收了来自 CreateWindow 的返回值,窗口句柄。

我们的调试到此为止,接下来,还请大家执行一下【调试 -> 停止调试】菜单命令,然后将 HelloWin.cpp 的代码恢复为原来的版本,然后生成一次解决方案。

三. WM_CREATE 是非队列消息

这一点,我认为,是有必要去强调的。

经过前面的讲解,我们已经知道,当窗口过程函数 WndProc 接收到 WM_CREATE 消息的时候,WinMain 中的 CreateWindow 函数仅仅是执行了一部分,而尚未返回。此时,ShowWindow 和 UpdateWindow 函数都是未执行的,消息循环代码更是尚未执行。

消息循环代码,指的就是 WinMain 中的 While 循环代码了。忘了的,请自行查阅本章代码。

程序所产生的队列消息,会被 Windows 放在程序的消息队列里面。消息队列里面的消息,会通过消息循环代码,将其检索出来,并发送给相应的窗口过程函数。

而 WM_CREATE 消息在产生和被 WndProc 接收与处理的时候,程序还尚未执行到消息循环部分,就连消息循环代码之前的 ShowWindow 和 UpdateWindow 函数也尚未执行到。

WM_CREATE 消息,没有进入过程序的消息队列里面,也不是由消息循环代码来检索和分发给窗口过程的,它是由 Windows 直接发送的。

在产生 WM_CREATE 消息的时候,Windows 直接调用窗口过程函数 WndProc,并在调用参数中,在第二个参数的位置,传递了 WM_CREATE 这一参数值,而第一个参数位置传递的是已经产生了的窗口句柄值。

至于第三个参数和第四个参数,我没研究过。想要研究的话,也不难,以后,我们可能会去探讨一下,WM_CREATE 消息在产生的时候,Windows 在发送这一消息的时候,所使用的第三个参数和第四个参数的值。

总 结

个人觉得,对于 WM_CREATE 消息的产生与处理时机,大家是需要理解的。无论是学习 Windows API 程序设计,还是学习 Windows MFC 程序设计,理解 WM_CREATE 消息的产生与接收时机都很重要。重要在哪儿,这会儿,我还有些说不清楚。总之,很重要。

所以呢,请大家务必掌握。

还有,WM_CREATE 是非队列消息,它在产生的时候,由 Windows 直接发送给相应的窗口过程函数。

本节结束。

专栏导航

上一篇:第3章,[标签 Win32] :消息的处理

回到目录

下一篇:无

相关推荐
会员果汁1 小时前
双向链式队列-C语言
c语言·数据结构
路由侠内网穿透.1 小时前
本地部署问答社区 Apache Anwser 并实现外部访问
服务器·windows·网络协议·apache·远程工作
草莓熊Lotso1 小时前
《算法闯关指南:优选算法--前缀和》--31.连续数组,32.矩阵区域和
c++·线性代数·算法·矩阵
程序喵大人1 小时前
CMake入门教程
开发语言·c++·cmake·cmake入门
csuzhucong1 小时前
斜转魔方、斜转扭曲魔方
前端·c++·算法
我不会插花弄玉1 小时前
类与对象-上【由浅入深-C++】
c++
郝学胜-神的一滴1 小时前
Horse3D游戏引擎研发笔记(十):在QtOpenGL环境下,视图矩阵与投影矩阵(摄像机)带你正式进入三维世界
c++·3d·unity·游戏引擎·godot·图形渲染·unreal engine
C语言不精1 小时前
c语言-优雅的多级菜单设计与实现
c语言·开发语言·算法
-森屿安年-2 小时前
二叉平衡树的实现
开发语言·数据结构·c++