从零开始学 Win32 API:C++ 窗口编程实战(VS Code + MinGW-w64 命令行详解)

目录

  1. 什么是 Win32 API
  2. 开发环境准备(安装 VS Code 与 MinGW-w64)
  3. 第一个 Win32 程序:从 WinMain 开始
  4. 窗口类与注册
  5. 创建窗口
  6. 消息循环
  7. 窗口过程(WndProc)
  8. 完整示例代码
  9. 编译和运行程序
  10. 常用数据类型与命名约定
  11. 下一步学习建议

1. 什么是 Win32 API

简单说,Win32 API 就是微软提供的一套 函数库,用 C 语言风格写成,让你能够在 Windows 系统上创建带界面的程序。无论是画窗口、处理鼠标键盘、弹出对话框,这些操作最终都要调用 Win32 API。

虽然现在有很多高级框架(如 Qt、C# WinForms),但它们底层依然离不开 Win32 API。把它学明白,以后学其他东西会轻松很多。


2. 开发环境准备

我们使用 VS Code 写代码,用 MinGW-w64 进行编译。两者都是免费的。

2.1 安装 VS Code

  1. 打开 https://code.visualstudio.com/ 下载安装包。
  2. 运行安装程序,一路 "下一步" 即可。
  3. 安装完成后打开 VS Code。

2.2 安装 MinGW-w64

MinGW-w64 是一个可以在 Windows 上生成原生 Windows 程序的 C/C++ 编译器(gcc/g++)。

  1. WinLibsMSYS2 下载 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)。
  2. 解压到你希望安装的目录,例如 C:\mingw64
  3. 配置环境变量:
    • 在 Windows 搜索栏输入"环境变量",选择"编辑系统环境变量"。
    • 点击"环境变量"按钮。
    • 在"系统变量"列表中找到 Path,双击编辑。
    • 点击"新建",输入 C:\mingw64\bin(根据你解压的路径修改),然后确定保存。
  4. 验证安装:
    • 打开命令提示符(Win+R,输入 cmd 回车),输入 g++ --version
    • 如果显示版本信息,则安装成功。

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. 消息循环

窗口创建好了,但它还不会自己动,也不会显示。我们需要两件事:

  1. 显示窗口
  2. 进入一个 死循环,不断接收操作系统发给窗口的消息(鼠标、键盘、重绘等),并交给窗口过程函数处理。

这就是 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_HREDRAWWM_PAINT。前缀 CS 表示类风格,WM 表示窗口消息。


11. 下一步学习建议

你已经成功写出第一个 Windows 窗口程序,接下来可以继续探索:

  • 处理更多消息 :尝试响应 WM_KEYDOWN(按键)、WM_LBUTTONDOWN(鼠标左键)等,让程序有交互。
  • 学习 GDI 绘图 :用 RectangleEllipseMoveToExLineTo 画图形,用 CreateSolidBrush 创建画刷。
  • 添加按钮、文本框 :用 CreateWindow 创建 "BUTTON""EDIT" 等子控件。
  • 资源文件 :学会用 .rc 文件加入菜单、图标、对话框等资源。
  • 理解 Unicode :本教程为了简单使用了 ANSI 版本的 API(字符串不带 L)。实际开发常需支持中文和多语言,可以进一步学习宽字符版本(MessageBoxWTextOutW 等)。

Win32 API 虽然繁琐,但它是打开 Windows 底层世界的一把钥匙。坚持下去,你会慢慢发现操作系统是怎样运作的。祝你学习顺利!

相关推荐
果汁华1 小时前
Function Calling 与 Python 实战完整指南
开发语言·网络·python
小小晓.1 小时前
C++:语句和作用域
开发语言·c++
fqbqrr2 小时前
2607C++,soui,xplayer视频播放器
c++·soui
wanderist.2 小时前
Lambda表达式在算法竞赛中的应用
java·开发语言·算法
zh路西法3 小时前
【10天速通ROS2-PX4无人机】(四) 关掉GPS和气压计,纯激光定位还能飞吗
c++·无人机·px4·ros2·卡尔曼滤波·fastlio2
海天鹰3 小时前
PHP上传文件
android·开发语言·php
yyds_yyd_100864 小时前
1464. 数组中两元素的最大乘积(2026.07.27)
数据结构·c++·算法·leetcode
Yeauty5 小时前
渲染成图再 CLI 拼接,还是进程内直推?Rust 帧到视频的两条路
开发语言·rust·音视频
geovindu5 小时前
CSharp: Breadth First Search Algorithm and Depth First Search Algorithm
开发语言·后端·算法·c#·.net·搜索算法