WIN32编程

APIENTRY WinMain 和WINAPI WinMain的区别

在 Windows 编程中,WinMain 是 Windows 图形用户界面(GUI)应用程序的入口点(Entry Point)。在不同的编译器和工具集中,函数的声明可能略有不同,但通常有两种常见的声明方式:

  1. int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nCmdShow)
  2. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)

这两者之间的主要区别在于调用约定(Calling Convention):

  • APIENTRY 是一个宏,它会根据编译器和目标平台选择正确的调用约定。在 Windows 上,它通常被定义为 __stdcall__stdcall 是一种标准的调用约定,它规定了参数的传递方式和堆栈的清理方式。
  • WINAPI 是另一个宏,它也通常被定义为 __stdcall。在实际使用中,APIENTRYWINAPI 可以互换使用,它们都表示使用标准的 __stdcall 调用约定。

所以,这两个函数声明在实际使用中基本上是等效的,你可以根据个人或项目的偏好选择其中一个使用。通常来说,WINAPI 是更常见的使用方式。

win32编程为什么叫win32

“Win32” 是指 Windows 32-bit 的编程模型。它起源于早期的 Windows 操作系统版本,当时 Windows 主要运行在 32 位的处理器架构上。”Win32” 编程通常涉及使用 Windows API(应用程序编程接口)来创建图形用户界面(GUI)应用程序。

Windows API 提供了一组函数和数据结构,允许开发者创建和管理窗口、处理用户输入、绘制图形等。在 32 位架构中,这些 API 被称为 “Win32 API”,而对应的编程模型就被称为 “Win32 编程”。

随着时间的推移,Windows 操作系统逐渐演变,引入了新的架构和功能,包括 64 位架构、UWP(Universal Windows Platform)等。因此,”Win32” 编程的术语在某种程度上成为一个历史遗留的称呼,用来指代传统的 32 位 Windows 编程模型。但即便在现代 Windows 开发中,”Win32” 仍然被广泛使用,尤其是在传统的桌面应用程序开发中。

CreateWindowEx和CreateWindow的区别是什么

CreateWindowExCreateWindow 是 Windows API 中用于创建窗口的两个函数,它们之间的主要区别在于 CreateWindowEx 允许你指定扩展的窗口样式,而 CreateWindow 则仅支持基本的窗口样式。

CreateWindowEx:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cCopy codeHWND CreateWindowEx(
DWORD dwExStyle,
LPCWSTR lpClassName,
LPCWSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam
);
  • dwExStyle 允许你指定扩展样式,如 WS_EX_TOPMOST、WS_EX_TOOLWINDOW 等。
  • 支持更多的窗口样式和扩展功能。

CreateWindow:

1
2
3
4
5
6
7
8
9
10
11
12
13
cCopy codeHWND CreateWindow(
LPCWSTR lpClassName,
LPCWSTR lpWindowName,
DWORD dwStyle,
int x,
int y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam
);
  • 不支持扩展样式(没有 dwExStyle 参数)。
  • 用于创建基本的窗口。

通常情况下,如果你的窗口需要使用一些扩展样式,例如创建工具栏窗口、置顶窗口等,你会选择使用 CreateWindowEx。如果你的窗口只需要基本的窗口样式,那么使用 CreateWindow 即可。

在实际使用中,两者的选择取决于你的窗口的需求和设计。

也就是说EX代表的是扩展的意思,具有更多功能相对于本身的函数来说。

::MessageBox为什么前面有两个冒号

在 C++ 中,:: 是作用域解析运算符(Scope Resolution Operator)。它用于指明使用全局命名空间中的符号,或者指定某个命名空间的成员。

::MessageBox 中的 :: 表示调用全局命名空间中的 MessageBox 函数。在 Windows 编程中,MessageBox 函数用于显示消息框。通过使用作用域解析运算符,你可以确保调用的是全局命名空间中的函数,而不是某个局部命名空间中可能存在的同名函数。

举个例子,如果你在某个命名空间中定义了自己的 MessageBox 函数,那么在调用时如果使用 ::MessageBox,就能够确保调用的是全局命名空间中的 MessageBox,而不会产生命名冲突。

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nCmdShow) 参数说明解释一下

WinMain 是 Windows GUI 应用程序的入口点,它接收并处理应用程序的启动参数,初始化应用程序的主窗口,然后进入消息循环以响应用户输入。下面是对 WinMain 函数参数的简要解释:

  1. hInstance (HINSTANCE):

    • 描述:应用程序实例的句柄。
    • 解释:hInstance 是当前应用程序实例的标识符,它在应用程序启动时由操作系统传递。这个句柄用于标识应用程序的内存空间和其他资源。
  2. hPrevInstance (HINSTANCE):

    • 描述:先前实例的句柄。
    • 解释:在现代 Windows 中,这个参数通常被忽略,因为多数 Windows 程序都是单实例的。这个参数的存在是为了兼容旧的 16 位 Windows 应用程序。
  3. lpCmdLine (LPSTR):

    • 描述:命令行参数。
    • 解释:lpCmdLine 包含了启动应用程序时传递的命令行参数。这是一个以空格分隔的字符串,可以包含应用程序需要的任何信息。需要注意,这是一个以空格为分隔符的单个字符串,而不是一个字符串数组。
  4. nCmdShow (int):

    • 描述:窗口显示状态。

    • 解释:

      1
      nCmdShow

      用于指定应用程序窗口的初始显示状态。它可以是以下常量之一:

      • SW_HIDE: 隐藏窗口。
      • SW_SHOWNORMAL: 显示窗口,并将其激活。
      • SW_SHOWMINIMIZED: 显示为最小化窗口。
      • SW_SHOWMAXIMIZED: 显示为最大化窗口。
      • SW_SHOWNOACTIVATE: 显示窗口,但不将其激活。
      • SW_RESTORE: 激活并显示窗口。如果窗口最小化或最大化,将恢复其原始大小和位置。
      • SW_SHOWDEFAULT: 使用操作系统默认显示状态。

WinMain 的返回值通常是程序的退出代码。在 Windows 中,一般约定返回 0 表示程序正常退出。

HWND hWnd = ::FindWindow(nullptr, TEXT(“无标题 - 记事本”)); 解释一下这段代码并且列举出FindWindow函数的参数

这段代码使用了 Windows API 中的 FindWindow 函数来查找指定类名和窗口标题的顶层窗口,并将找到的窗口句柄存储在 hWnd 变量中。以下是对代码和 FindWindow 函数参数的解释:

代码解释:

1
2
cppCopy code
HWND hWnd = ::FindWindow(nullptr, TEXT("无标题 - 记事本"));
  • HWND hWnd

    • 描述:窗口句柄。
    • 解释:hWnd 是一个句柄变量,用于存储找到的窗口的句柄。HWND 是 Windows 中表示窗口句柄的数据类型。
  • ::FindWindow(nullptr, TEXT("无标题 - 记事本"))

    • 描述:调用 FindWindow 函数来查找指定窗口。

    • 解释:

      1
      FindWindow

      函数用于在桌面窗口中查找符合指定类名和窗口标题的顶层窗口。在这里:

      • 第一个参数 nullptr 表示没有指定窗口类名,即不限制窗口的类。
      • 第二个参数是窗口标题,这里是 “无标题 - 记事本”。通过这两个参数,FindWindow 将尝试找到符合条件的顶层窗口。

FindWindow 函数的参数:

1
2
3
4
cppCopy codeHWND FindWindow(
LPCWSTR lpClassName,
LPCWSTR lpWindowName
);
  • lpClassName
    • 描述:窗口类名。
    • 解释:如果不关心类名,可以将其设为 nullptr。否则,可以指定要查找的窗口类名。
  • lpWindowName
    • 描述:窗口标题。
    • 解释:指定要查找的窗口的标题。可以使用部分标题文本来匹配窗口。

总体思路: 通过 FindWindow 函数,在桌面上查找指定类名(或不限制类名)和指定标题的顶层窗口,如果找到,则返回该窗口的句柄给 hWnd 变量。如果没有找到匹配的窗口,hWnd 将被设为 nullptr

注册窗口类

注册窗口类使用函数RegisterClassEx,一个窗口类定义了窗口的一些主要属性,如图标、光标、背景色和负责处理消息的窗口函数等。这些属性定义在WNDCLASSEX结构中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct _WNDCLASSEX{
UINT cbSize; // WNDCLASSEX结构的大小
UINT style; // 从窗口派生的窗口具有的风格
WNDPROC lpfnWndProc; // 消息处理函数指针
int cbClsExtra; // 指定紧跟在窗口类结构后的附加字节数
int cbWndExtra; // 指定紧跟在窗口示例后的附加字节数
HINSTANCE hInstance; // 本模块的实例句柄
HICON hIcon; // 窗口左上角图标的句柄
HCURSOR hCursor; // 光标的句柄
HBRUSH hbrBackground; // 背景画刷的句柄
LPCSTR lpszMenuName; // 菜单名
LPCSTR lpszClassName; // 该窗口类的名称
HICON hIconSm; // 小图标句柄
}WNDCLASSEX;


(1) 指定窗口类的风格

1
wndClass.style = CS_HREDRAW | CS_VREDRAW;	// 指定如果大小改变就重画

前缀CS_意为class style,在WINUSER.H中定义了全部可选样式。

1
2
3
4
5
6
7
8
9
10
11
#define CS_VREDRAW          0x0001
#define CS_HREDRAW 0x0002
#define CS_DBLCLKS 0x0008
#define CS_OWNDC 0x0020
#define CS_CLASSDC 0x0040
#define CS_PARENTDC 0x0080
#define CS_NOCLOSE 0x0200
#define CS_SAVEBITS 0x0800
#define CS_BYTEALIGNCLIENT 0x1000
#define CS_BYTEALIGNWINDOW 0x2000
#define CS_GLOBALCLASS 0x4000

(2)指定窗口处理函数地址

1
wndClass.lpfnWndProc = MainWindProc;

WNDCLASSEX 结构成员lpfnWndProc 指定了基于此类窗口的窗口函数。当窗口收到消息时Windows即自动调用这个函数通知应用程序。

(3) 本程序的实例句柄传给hInstance成员

1
wndClass.hInstance = hInstance;

(4) 设置图标和光标

1
2
wndClass.hIcon = ::LoadIcon(nullptr, IDI_APPLICATION);
wndClass.hCursor = ::LoadCursor(nullptr, IDC_ARROW);

LoadIcon 函数装载了一个预定义图标,命名为 IDI_APPLICATION
LoadCursor 函数装载了一个预定义的光标,命名为 IDC_ARROW

(5)指定窗口重画客户区画刷

1
wndClass.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH);	// 使用白色画刷

(6)指定窗口类名称

1
wndClass.lpszClassName = TEXT("MainWClass");  // 窗口类名称

填充完WNDCLASSEX 结构,就可以进行注册了。RegisterClassEx 函数调用失败将返回0

1
::RegisterClassEx(&wndClass);

创建窗口

要创建窗口,使用函数 CreateWindowEx 。

1
2
3
4
5
6
7
8
9
10
11
12
13
HWND hwnd = ::CreateWindowEx(\					
0, \ // dwExStyle, 扩展样式
TEXT("MainWClass"), \ // 类名
TEXT("My First Window"), \ // 标题
WS_OVERLAPPEDWINDOW, \ // 窗口风格
CW_USEDEFAULT, \ // X, 初始X坐标
CW_USEDEFAULT, \ // Y, 初始Y坐标
CW_USEDEFAULT, \ // nWidth, 宽度
CW_USEDEFAULT, \ // nHeight, 高度
nullptr, \ // hWndParent, 父窗口句柄
nullptr, \ // hMenu,菜单句柄
hInstance, \ // hInstance, 程序实例句柄
nullptr); // lpParam, 用户数据

函数调用成功将返回窗口句柄,失败返回nullptr。
第四个参数dwStyle的值是 WS_OVERLAPPEDWINDOW,即重叠窗口。由他指定的窗口有标题栏、系统菜单、可以改变大小的边框,以及最大化、最小化和关闭按钮。这是一个标准的窗口样式。下面是一些常见风格定于:

  • WS_BORDER 创建一个单边框窗口
  • WS_CAPTION 创建一个有标题框的窗口
  • WS_CHID 创建一个子窗口。这个风格不能与WS_POPVP合用
  • WS_DISABLED 创建一个初始状态为禁止的子窗口。
  • WS_DLGFRAME 创建一个带对话框边框风格的窗口,这种风格的窗口不能带标题条
  • WS_HSCROLL 创建一个有水平滚动条的窗口
  • WS_VSCROLL 创建一个有垂直滚动条的窗口
  • WS_ICONIC 创建一个初始状态为最小化状态的窗口,与WS_MINIMIZE风格相同
  • WS_MAXIMIZE 创建一个具有最大化按钮的窗口。
  • WS_OVERLAPPED 产生一个层叠的窗口。
  • WS_OVERLAPPEDWINDOW 创建一个具有WS_OVERLAPPED、WS_CAPTION、
  • WS_SYSMENU、WS_THICKFRAME、WS_MINIMZEBOX、WS_MAXMIZEBOX 风格的层叠窗口。
  • WS_POPUP 创建一个弹出式窗口
  • WS_SIZEBOX 创建一个可调边框的窗口。
  • WS_SYSMENU 创建一个在标题条上带有窗口菜单的窗口。
  • WS_THICKFRAME 创建一个具有可调边框的窗口
  • WS_VISIBLE 创建一个初始状态为可见的窗口

进入无限的消息循环

Windows为每一个线程维护一个消息队列,每当有一个输入发生,Windows就把用户输入翻译成消息放在消息队列中。GetMessage 函数可以从消息队列中取一个消息填充MSG结构。
如果消息队列中没有消息,这个函数会一直等下去,直到有消息进入消息队列为止。

1
2
3
4
5
6
7
8
typedef struct tagMSG {
HWND hwnd; // 消息要发向的窗口句柄
UINT message; // 消息标识符,以WM_开头的预定义值(Windows Message)
WPARAM wParam; // 消息的参数之一
LPARAM lParam; // 消息的参数之二
DWORD time; // 消息放入消息队列的时间
POINT pt; // 这是一个POINT数据结构,表示消息放入消息队列时鼠标的位置
} MSG,

GetMessage 函数从消息队列中取得的消息如果不是WM_QUIT ,则返回非零值。一个WM_QUIT 消息会使GetMessage 函数返回0,从而消息循环结束。

1
::TranslateMessage(&msg);

此调用把键盘输入翻译成可调用的消息。

1
::DispatchMessage(&msg);

DispatchMessage 函数分发一个消息到对应窗口的窗口函数。MainWndProc 处理消息后把控制权交给Windows,此时DispatchMessage 函数仍然继续工作,当他返回时,消息队列从调用GetMessage 函数开始进入下一轮。

处理消息代码

所有消息不做处理的消息都必须返回一个名为DefWindowProc的函数让Windows做默认处理,从DefWindowProc 函数返回的值也必须从消息处理函数返回。

每当客户区变为无效,消息处理函数就会收到一个新的WM_PAINT 消息。
WM_DESTORY 是窗口必须处理的一个消息,当用户关闭窗口时,消息处理函数就会收到一 个 WM_DESTORY消息。当接收到这个消息的时候,说明窗口正在销毁。

1
::PostQuitMessage(0);

PostQuitMessage函数会会向消息队列中插入一个 WM_QUIT 消息。GetMessage 函数如果从消息队列中获得到消息时 WM_QUIT ,它将返回0。从而退出消息循环。如果不使用函数 PostQuitMessage 发送WM_QUIT 消息,则界面虽然被销毁了,但是消息循环还在继续,程序还没有结束。