• win32:第一个窗口程序-应用程序入口点(part.6)


    第一个窗口程序的最后一部分:应用程序入口函数wWinMain;这是Windows应用程序的主函数,负责初始化应用程序、注册窗口类、创建主窗口并进入消息循环处理消息。

    1. int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    2.                     _In_opt_ HINSTANCE hPrevInstance,
    3.                     _In_ LPWSTR    lpCmdLine,
    4.                     _In_ int       nCmdShow)
    5. {
    6.    UNREFERENCED_PARAMETER(hPrevInstance);
    7.    UNREFERENCED_PARAMETER(lpCmdLine);
    8.    // 初始化全局字符串
    9.    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    10.    LoadStringW(hInstance, IDC_WINDOWSPROJECT1, szWindowClass, MAX_LOADSTRING);
    11.    MyRegisterClass(hInstance);
    12.    // 执行应用程序初始化:
    13.    if (!InitInstance (hInstance, nCmdShow))
    14.   {
    15.        return FALSE;
    16.   }
    17.    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWSPROJECT1));
    18.    MSG msg;
    19.    // 主消息循环:
    20.    while (GetMessage(&msg, nullptr, 0, 0))
    21.   {
    22.        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
    23.       {
    24.            TranslateMessage(&msg);
    25.            DispatchMessage(&msg);
    26.       }
    27.   }
    28.    return (int) msg.wParam;
    29. }
    函数声明部分
    1. int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    2.                     _In_opt_ HINSTANCE hPrevInstance,
    3.                     _In_ LPWSTR    lpCmdLine,
    4.                     _In_ int       nCmdShow)

    APIENTRY 是一个宏,定义了函数的调用约定;具体来说,APIENTRY 在 Windows 平台上通常定义为 __stdcall__stdcall 是一种调用约定,规定了函数如何接收参数、返回值,以及函数调用时堆栈的清理方式。(这个部分在汇编部分有涉及到,这边再描述一下__stdcall调用约定的一些关键点)

    参数传递顺序: 参数从右向左传递(即最后一个参数最先压入堆栈)。
    堆栈清理: 函数自身负责清理堆栈。这与__cdecl不同,在__cdecl中,调用者负责清理堆栈。
    名称修饰: 在使用__stdcall时,编译器会对函数名进行修饰。这通常包括在函数名前加上一个下划线,并在后面加上@符号和参数的字节数。例如,void MyFunction(int a) 会被修饰为 _MyFunction@4。
    应用场景: __stdcall主要用于Win32 API函数以及一些第三方库的接口函数。

    接着来说一下程序入口函数的参数列表:

    hInstance:是当前应用程序实例的句柄;它是一个唯一标识应用程序的实例,用于加载资源(如图标、字符串、对话框模板等)和其他操作。
    hPrevInstance:这是上一个实例的句柄。在 16 位 Windows 中,它用于判断是否已经有一个实例在运行。对于 32 位和 64 位 Windows 应用程序,这个参数总是 NULL,所以一般不需要用它。
    lpCmdLine:是指向包含命令行参数的字符串的指针。
    nCmdShow:指定应用程序窗口的初始显示状态。这个参数可以有多种值,比如 SW_SHOW、SW_HIDE 等,用于决定窗口是最小化、最大化还是正常显示,通常在创建窗口时传递给 ShowWindow 函数。

    在Windows编程中,应用程序的实例(Instance)通常指的是应用程序在内存中的一个运行副本。每个实例都有一个唯一的句柄(HINSTANCE),这是一个标识符,用于区分和管理不同的实例。当你运行一个可执行文件(如 .exe),操作系统会为这个可执行文件分配内存,并启动一个新进程。这个进程就是应用程序的一个实例。你可以同时运行多个相同的可执行文件,每一个运行的进程都是该应用程序的一个实例。

    函数体部分
    1. UNREFERENCED_PARAMETER(hPrevInstance);
    2.   UNREFERENCED_PARAMETER(lpCmdLine);

    UNREFERENCED_PARAMETER 是一个宏,用于标记在函数中未使用的参数。这在编译时避免了未使用参数的警告。这行代码的作用是告诉编译器,这两个参数 hPrevInstancelpCmdLine 在函数体中没有被使用,但这是有意为之,并且这种情况是可以接受的。

    在标记未使用的参数后,模板代码就开始从资源文件加载字符串并注册窗口类。

    1. LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    2.    LoadStringW(hInstance, IDC_WINDOWSPROJECT1, szWindowClass, MAX_LOADSTRING);
    3.    MyRegisterClass(hInstance);

    LoadStringW 是一个Win32 API函数,用于从应用程序的资源文件中加载字符串资源;他的原型是

    1. int LoadStringW(
    2.  HINSTANCE hInstance,
    3.  UINT uID,
    4.  LPWSTR lpBuffer,
    5.  int cchBufferMax
    6. );

    hInstance: 应用程序实例的句柄。在这里,它指定了包含字符串资源的模块。

    uID: 字符串资源的标识符。在这里,IDS_APP_TITLEIDC_WINDOWSPROJECT1 是资源ID,通常在资源文件(如 .rc 文件)中定义。

    lpBuffer: 指向接收加载的字符串的缓冲区。

    cchBufferMax: 缓冲区的最大字符数,包括终止的空字符。

    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);:从资源文件中加载ID为 IDS_APP_TITLE 的字符串,并将其存储在 szTitle 缓冲区中。

    LoadStringW(hInstance, IDC_WINDOWSPROJECT1, szWindowClass, MAX_LOADSTRING);:从资源文件中加载ID为 IDC_WINDOWSPROJECT1 的字符串,并将其存储在 szWindowClass 缓冲区中。

    通过查看项目中的资源文件(.rc)的内容就可以找到载入的字符串是什么

    MyRegisterClass(hInstance);接着就是调用自定义的注册窗口类函数,去指定窗口的样式、窗口过程(处理窗口消息的回调函数)、窗口背景色等信息。(part.2)

    接着就是需要进行应用程序实例的初始化:这里调用了自定义函数InitInstance;在这个函数中我们会创建实例的主窗口,并根据nCmdShow参数指定程序窗口的显示方式。

    1. if (!InitInstance (hInstance, nCmdShow))
    2.   {
    3.        return FALSE;
    4.   }

    若实例初始化失败,则返回false,如若成功则显示主窗口。在实例初始化成功后加载加速键表(accelerator table),以便在消息循环中处理快捷键。

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWSPROJECT1));

    LoadAccelerators 是一个Win32 API函数,用于加载加速键表。加速键表定义了一组快捷键及其对应的命令,可以用来快捷地执行菜单命令。其原型如下:

    1. HACCEL LoadAccelerators(
    2.  HINSTANCE hInstance,
    3.  LPCWSTR lpTableName
    4. );

    hInstance: 应用程序实例的句柄,指定包含加速键表的模块。

    lpTableName: 指向包含加速键表的资源名称或标识符(通常使用 MAKEINTRESOURCE 宏转换资源ID)。

    当前项目的rc文件中的资源设置如下:

    这是菜单资源的标识符和类型。IDC_WINDOWSPROJECT1 是菜单的ID,MENU 表示这是一个菜单资源。BEGINEND:这些关键字用于定义菜单的开始和结束部分。

    POPUP:POPUP 定义了一个包含子菜单的顶级菜单项:

    "文件(&F)""帮助(&H)" 是两个顶级菜单项,它们分别包含一个或多个子菜单项。(&F)(&H) 是快捷键,按下 Alt+FAlt+H 可以打开相应的菜单。

    MENUITEM:MENUITEM 定义了一个具体的菜单项:

    "退出(&X)", IDM_EXIT 定义了一个名为 "退出" 的菜单项,(&X) 是快捷键,(&X) 是快捷键,IDM_EXIT 是菜单项的命令ID。

    "关于(&A) ...", IDM_ABOUT 定义了一个名为 "关于" 的菜单项,(&A) 是快捷键,IDM_ABOUT 是菜单项的命令ID。

    接着进行消息变量声明:

    MSG msg;

    MSG 结构体用于存储从消息队列中检索的消息,消息循环中会使用这个结构体来接收和处理窗口消息。MSG 结构体在 winuser.h 头文件中定义,用于包含窗口消息信息:

    1. typedef struct tagMSG {
    2.   HWND   hwnd;
    3.   UINT   message;
    4.   WPARAM wParam;
    5.   LPARAM lParam;
    6.   DWORD time;
    7.   POINT pt;
    8.   DWORD lPrivate;
    9. } MSG, *PMSG;

    hwnd: 接收消息的窗口句柄,message: 消息标识符(如 WM_PAINT, WM_KEYDOWN),以及其他参数:

    wParam: 消息的附加信息,具体内容取决于消息类型。
    lParam: 消息的附加信息,具体内容取决于消息类型。
    time: 消息被放入消息队列的时间戳。
    pt: POINT 结构体,表示消息发生时的光标位置。
    lPrivate: 私有数据,用于内部用途。

    声明变量后接着就需要实现消息循环:它在应用程序的整个生命周期中不断运行,处理来自操作系统和用户的各种消息。

    1. while (GetMessage(&msg, nullptr, 0, 0))
    2.   {
    3.        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
    4.       {
    5.            TranslateMessage(&msg);
    6.            DispatchMessage(&msg);
    7.       }
    8.   }

    GetMessage 函数从调用线程的消息队列中检索消息,并将其存储在 MSG 结构体中。lpMsg: 指向 MSG 结构体的指针,用于接收消息;hWnd: 指定消息的窗口句柄,nullptr 表示检索线程的所有消息;wMsgFilterMinwMsgFilterMax: 指定要检索的消息范围,0, 0 表示检索所有消息。

    TranslateAccelerator 函数将加速键消息转换为命令消息,hWnd: 接收消息的窗口句柄;hAccTable: 加速键表句柄;lpMsg: 指向 MSG 结构体的指针。加速键(Accelerator Key)消息是指在 Windows 应用程序中用于快捷键操作的一种消息类型。

    TranslateMessage 函数将虚拟键消息(如 WM_KEYDOWN)转换为字符消息(如 WM_CHAR);虚拟键消息(Virtual Key Messages)是 Windows 操作系统中用于处理键盘输入的一种消息类型。它们是由键盘驱动程序生成的消息,通常通过输入设备(如键盘)上的按键触发。

    DispatchMessage 函数将消息分派到窗口过程(Window Procedure),窗口过程根据消息类型执行相应的操作。

    消息循环的完整流程

    检索消息GetMessage 从消息队列中检索消息并存储在 msg 结构体中;如果 GetMessage 返回 0,表示收到 WM_QUIT 消息,退出消息循环。

    处理加速键TranslateAccelerator 检查消息是否为加速键,如果是,则翻译并处理它,如果 TranslateAccelerator 返回 TRUE,表示消息已处理,不需要进一步处理。

    翻译和分派消息:如果消息不是加速键或未处理,调用 TranslateMessage 将虚拟键消息转换为字符消息;调用 DispatchMessage 将消息分派到窗口过程,窗口过程根据消息类型执行相应的操作。

    最后程序的执行结果:

    关于窗口

  • 相关阅读:
    第12讲:DVM 以及 ART 是如何对 JVM 进行优化的?
    小黑子—spring:第一章 Bean基础
    [Java Base]Java 8 ~ Java17新特性
    html制作小猪佩奇卡通图案代码,使用HTML和CSS3绘制基本卡通图案的示例分享
    java常见的设置模式(下)
    关于我的人生(假如我工作13年就能得到3w个花西币)
    宇宙始终处于完美状态
    【数据结构】线性结构——数组、链表、栈和队列
    天龙八部TLBB系列 - 单体技能群伤
    驱动开发:内核R3与R0内存映射拷贝
  • 原文地址:https://blog.csdn.net/WolvenSec/article/details/140403089