目录
- 什么是 Win32 API
- 开发环境准备(安装 VS Code 与 MinGW-w64)
- 第一个 Win32 程序:从 WinMain 开始
- 窗口类与注册
- 创建窗口
- 消息循环
- 窗口过程(WndProc)
- 完整示例代码
- 编译和运行程序
- 常用数据类型与命名约定
- 下一步学习建议
1. 什么是 Win32 API
简单说,Win32 API 就是微软提供的一套 函数库,用 C 语言风格写成,让你能够在 Windows 系统上创建带界面的程序。无论是画窗口、处理鼠标键盘、弹出对话框,这些操作最终都要调用 Win32 API。
虽然现在有很多高级框架(如 Qt、C# WinForms),但它们底层依然离不开 Win32 API。把它学明白,以后学其他东西会轻松很多。
2. 开发环境准备
我们使用 VS Code 写代码,用 MinGW-w64 进行编译。两者都是免费的。
2.1 安装 VS Code
- 打开 https://code.visualstudio.com/ 下载安装包。
- 运行安装程序,一路 "下一步" 即可。
- 安装完成后打开 VS Code。
2.2 安装 MinGW-w64
MinGW-w64 是一个可以在 Windows 上生成原生 Windows 程序的 C/C++ 编译器(gcc/g++)。
- 从 WinLibs 或 MSYS2 下载 MinGW-w64。推荐 WinLibs ,简单直接:
- 进入 WinLibs 网站,选择最新版本(例如 GCC 13.2.0),下载
winlibs-x86_64-posix-seh-gcc-13.2.0-mingw-w64msvcrt-11.0.1-r1.zip(类似名称的压缩包,选择 x86_64 , posix , seh)。
- 进入 WinLibs 网站,选择最新版本(例如 GCC 13.2.0),下载
- 解压到你希望安装的目录,例如
C:\mingw64。 - 配置环境变量:
- 在 Windows 搜索栏输入"环境变量",选择"编辑系统环境变量"。
- 点击"环境变量"按钮。
- 在"系统变量"列表中找到
Path,双击编辑。 - 点击"新建",输入
C:\mingw64\bin(根据你解压的路径修改),然后确定保存。
- 验证安装:
- 打开命令提示符(Win+R,输入
cmd回车),输入g++ --version。 - 如果显示版本信息,则安装成功。
- 打开命令提示符(Win+R,输入
2.3 配置 VS Code(可选)
VS Code 只是一个编辑器,我们直接用命令行编译,不需要在 VS Code 里面配置编译任务,所以这一步可以跳过。你可以直接在 VS Code 里写好代码,然后在终端手动运行编译命令。
3. 第一个 Win32 程序:从 WinMain 开始
普通的 C++ 控制台程序入口是 main 函数。但 Windows 带界面的程序入口叫做 WinMain。
新建一个文件 main.cpp,输入以下代码:
cpp
#include <windows.h>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
// 程序将在这里开始执行
return 0;
}
代码解释
#include <windows.h>:包含所有 Win32 API 的声明,必须放在最开头。int WINAPI WinMain(...):Windows 程序的入口函数。WINAPI:函数调用约定,告诉编译器用 Windows 标准的方式传递参数,照着写就行。- 参数 :
hInstance:当前程序的"实例句柄",可以理解为程序在内存里的身份证。hPrevInstance:历史遗留,现在总是NULL,不用管它。lpCmdLine:启动程序时附带的命令行参数(字符串,不包含程序名)。nCmdShow:窗口的初始显示方式(最大化、最小化还是正常),由系统决定。
这个程序现在什么也没做,运行后会直接退出。
4. 窗口类与注册
在 Windows 里,每个窗口都属于一个 窗口类。窗口类定义了窗口的共性:背景颜色、鼠标样式、图标,以及最重要的------消息处理函数。
你需要先填写一个 WNDCLASSEX 结构体,然后用 RegisterClassEx 注册这个类。
cpp
WNDCLASSEX wc = {0}; // 全部清零
wc.cbSize = sizeof(WNDCLASSEX); // 结构体大小,必须填
wc.style = CS_HREDRAW | CS_VREDRAW; // 窗口改变大小时自动重绘
wc.lpfnWndProc = WindowProc; // 窗口过程函数(稍后自己写)
wc.hInstance = hInstance; // 所属应用程序实例
wc.hCursor = LoadCursor(NULL, IDC_ARROW); // 鼠标箭头样式
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // 背景颜色(默认窗口色)
wc.lpszClassName = "MyWindowClass"; // 类的名字,自己随便起
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); // 窗口图标
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); // 小图标
if (!RegisterClassEx(&wc)) {
MessageBox(NULL, "窗口类注册失败!", "错误", MB_ICONERROR);
return 1;
}
关键点说明
lpfnWndProc:指向一个函数,这个函数负责处理所有窗口消息(鼠标点击、键盘输入等)。我们会在第7节编写它。hInstance:直接从WinMain的参数传过来。lpszClassName:字符串,给这个窗口类起个名字,后面创建窗口时要用到。- 如果注册失败,弹出一个错误对话框,程序返回 1 退出。
5. 创建窗口
有了窗口类,就能用 CreateWindowEx 生成一个实实在在的窗口了。
cpp
HWND hwnd = CreateWindowEx(
0, // 扩展风格,0 表示没有
"MyWindowClass", // 刚才注册的窗口类名字
"我的第一个 Win32 窗口", // 窗口标题栏文字
WS_OVERLAPPEDWINDOW, // 窗口基本样式(有边框、标题栏、最大最小按钮等)
CW_USEDEFAULT, CW_USEDEFAULT, // 窗口左上角位置,使用默认值
800, 600, // 窗口宽度和高度
NULL, // 父窗口(没有就写 NULL)
NULL, // 菜单(没有就写 NULL)
hInstance, // 应用程序实例句柄
NULL // 额外数据,不用
);
if (hwnd == NULL) {
MessageBox(NULL, "窗口创建失败!", "错误", MB_ICONERROR);
return 1;
}
CreateWindowEx 返回一个 HWND 类型的值,这叫 窗口句柄 。可以把它理解为窗口的"身份证号",以后操作这个窗口全靠它。如果创建失败,返回 NULL。
6. 消息循环
窗口创建好了,但它还不会自己动,也不会显示。我们需要两件事:
- 显示窗口
- 进入一个 死循环,不断接收操作系统发给窗口的消息(鼠标、键盘、重绘等),并交给窗口过程函数处理。
这就是 Windows 的 事件驱动 模型。
cpp
ShowWindow(hwnd, nCmdShow); // 根据系统要求的方式显示窗口
UpdateWindow(hwnd); // 立即刷新一次窗口(触发一次绘制)
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) // 从消息队列取消息
{
TranslateMessage(&msg); // 处理键盘消息(将按键转成字符消息)
DispatchMessage(&msg); // 把消息送给窗口过程函数
}
解读
GetMessage:如果队列里没消息,程序会在这里"睡觉",不占 CPU。一旦有消息(比如移动鼠标),它会拿到消息并返回非零值。当收到WM_QUIT消息时返回 0,循环结束,程序退出。TranslateMessage:将按键消息WM_KEYDOWN转换成WM_CHAR,方便处理文字输入。DispatchMessage:调用当初注册的窗口过程函数,把消息交出去。
7. 窗口过程(WndProc)
这是你自己写的函数,用来处理各种消息。函数原型必须如下:
cpp
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
具体实现就是一个 switch,根据消息类型做不同处理。没处理的消息交给系统默认处理。
cpp
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CLOSE: // 用户点击了关闭按钮(或按 Alt+F4)
DestroyWindow(hwnd); // 销毁窗口
return 0;
case WM_DESTROY: // 窗口被销毁时会收到此消息
PostQuitMessage(0); // 发出退出消息,结束消息循环
return 0;
case WM_PAINT: // 窗口需要重绘(被挡住后又露出来、尺寸改变等)
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps); // 开始绘制
TextOut(hdc, 50, 50, "你好,Win32 API!", 17); // 在 (50,50) 位置输出文字
EndPaint(hwnd, &ps); // 结束绘制
}
return 0;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam); // 默认处理,必须调用
}
}
重点
WM_CLOSE:用户关闭窗口时产生,应调用DestroyWindow销毁窗口。WM_DESTROY:窗口被销毁后产生,必须调用PostQuitMessage(0),否则程序收不到退出消息,永远卡在消息循环里。WM_PAINT:窗口需要绘制时产生,用BeginPaint拿到绘图设备HDC,然后用TextOut等函数画内容,最后EndPaint结束。DefWindowProc处理所有我们没处理的消息(如标题栏的移动、大小调整等),千万不要漏掉。
8. 完整示例代码
把前面的碎片拼起来,就是一个能编译运行的窗口程序。创建一个 main.cpp 文件,写入以下内容:
cpp
#include <windows.h>
// 提前声明窗口过程函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
// ---------- 1. 注册窗口类 ----------
WNDCLASSEX wc = {0};
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc; // 窗口过程
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = "MyWindowClass";
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
if (!RegisterClassEx(&wc)) {
MessageBox(NULL, "窗口类注册失败!", "错误", MB_ICONERROR);
return 1;
}
// ---------- 2. 创建窗口 ----------
HWND hwnd = CreateWindowEx(
0,
"MyWindowClass",
"我的第一个 Win32 窗口",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
800, 600,
NULL,
NULL,
hInstance,
NULL
);
if (hwnd == NULL) {
MessageBox(NULL, "窗口创建失败!", "错误", MB_ICONERROR);
return 1;
}
// ---------- 3. 显示窗口并进入消息循环 ----------
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
// ---------- 4. 窗口过程 ----------
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CLOSE:
DestroyWindow(hwnd);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
TextOut(hdc, 50, 50, "你好,Win32 API!", 17);
EndPaint(hwnd, &ps);
}
return 0;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
9. 编译和运行程序
9.1 用 g++ 编译
打开 命令提示符 (或 VS Code 里的终端),切换到 main.cpp 所在目录,执行以下命令:
g++ main.cpp -o MyApp.exe -lgdi32 -mwindows
参数解释
main.cpp:源文件名。-o MyApp.exe:指定输出文件名,叫MyApp.exe。-lgdi32:链接 GDI 库(里面包含TextOut等绘图函数)。-mwindows:告诉链接器这是一个 Windows 图形程序,不要显示控制台黑窗口。
9.2 运行
编译成功后,直接双击 MyApp.exe,就会弹出一个窗口,里面显示"你好,Win32 API!"。点击右上角 × 可以关闭。
如果编译报错,请检查:
- 代码是否完全拷贝正确。
- 是否安装了 MinGW-w64 并配置了环境变量。
- 命令行中是否加上了
-lgdi32 -mwindows。
10. 常用数据类型与命名约定
Win32 API 定义了很多自己的类型,初看很乱,但一旦熟悉就习惯了。
| 类型 | 实际含义 | 说明 |
|---|---|---|
BOOL |
int |
布尔值,非零即为真 |
HWND |
void*(句柄) |
窗口句柄 |
HINSTANCE |
void*(句柄) |
实例句柄 |
HDC |
void*(句柄) |
绘图设备上下文 |
LPSTR |
char* |
字符串指针 |
LPCSTR |
const char* |
常量字符串指针 |
UINT |
unsigned int |
无符号整数 |
LRESULT |
long/intptr_t |
窗口过程返回值 |
WPARAM |
unsigned int(32位)/UINT_PTR(64位) |
消息附带参数 |
LPARAM |
long(32位)/LONG_PTR(64位) |
消息附带参数 |
常量命名普遍采用大写字母加下划线,如 CS_HREDRAW、WM_PAINT。前缀 CS 表示类风格,WM 表示窗口消息。
11. 下一步学习建议
你已经成功写出第一个 Windows 窗口程序,接下来可以继续探索:
- 处理更多消息 :尝试响应
WM_KEYDOWN(按键)、WM_LBUTTONDOWN(鼠标左键)等,让程序有交互。 - 学习 GDI 绘图 :用
Rectangle、Ellipse、MoveToEx和LineTo画图形,用CreateSolidBrush创建画刷。 - 添加按钮、文本框 :用
CreateWindow创建"BUTTON"、"EDIT"等子控件。 - 资源文件 :学会用
.rc文件加入菜单、图标、对话框等资源。 - 理解 Unicode :本教程为了简单使用了 ANSI 版本的 API(字符串不带
L)。实际开发常需支持中文和多语言,可以进一步学习宽字符版本(MessageBoxW、TextOutW等)。
Win32 API 虽然繁琐,但它是打开 Windows 底层世界的一把钥匙。坚持下去,你会慢慢发现操作系统是怎样运作的。祝你学习顺利!