杰瑞科技汇

Windows API教程,从入门到精通怎么学?

目录

  1. 第一部分:Windows API 简介

    Windows API教程,从入门到精通怎么学?-图1
    (图片来源网络,侵删)
    • 什么是 Windows API?
    • 为什么学习 Windows API?
    • 开发环境准备
  2. 第二部分:第一个 Windows 程序 - "Hello, Windows!"

    • 程序结构解析:WinMain, WNDCLASS, RegisterClass, CreateWindow, ShowWindow, UpdateWindow, Message Loop
    • 代码详解
    • 编译与运行
  3. 第三部分:深入窗口过程

    • WndProc 函数详解
    • 消息类型:WM_PAINT, WM_DESTROY, WM_CLOSE
    • 处理用户输入:鼠标和键盘消息
  4. 第四部分:绘制图形

    • 获取设备上下文 (GetDC, BeginPaint, EndPaint)
    • 使用 GDI 函数绘制 (TextOut, MoveToEx, LineTo, Rectangle)
    • 一个简单的画板程序
  5. 第五部分:控件与资源

    Windows API教程,从入门到精通怎么学?-图2
    (图片来源网络,侵删)
    • 创建控件 (CreateWindowEx)
    • 消息处理与控件通信
    • 使用资源文件 (.rc) - 简单对话框
  6. 第六部分:进阶主题与学习资源

    • Unicode 与 MBCS
    • 现代 Windows 编程:Windows SDK 与 C++/WinRT
    • 推荐书籍与在线资源

第一部分:Windows API 简介

什么是 Windows API?

Windows API (Application Programming Interface) 是微软为 Windows 操作系统提供的一套核心函数、类、消息和结构,它就像是操作系统和应用程序之间的“中间人”或“翻译官”,当你用 C/C++ 编写一个 Windows 程序时,你实际上是在调用这些 API 函数,让操作系统为你完成窗口创建、图形绘制、文件操作、网络通信等底层任务。

为什么学习 Windows API?

  • 理解底层原理:学习 Windows API 能让你深刻理解 Windows 操作系统是如何管理窗口、消息、进程和线程的,这是学习更高级框架(如 MFC, Qt, .NET WinForms/WPF)的基础。
  • 性能极致:直接调用 API 是最直接的方式,没有额外的抽象层开销,能获得最佳的性能。
  • 功能全面:API 提供了操作系统最完整、最底层的功能,一些高级框架可能不会暴露所有细节。
  • 跨平台能力:虽然 API 是 Windows 特有的,但理解事件驱动、窗口句柄等概念可以平滑过渡到其他 GUI 框架(如 macOS 的 Cocoa, Linux 的 GTK)。

开发环境准备

  • 编译器:你需要一个 C/C++ 编译器,最经典和推荐的是 Visual Studio,社区版 (Community Edition) 是免费的,功能强大。
    • 安装 Visual Studio:从 Visual Studio 官网 下载并安装 "Visual Studio Community"。
    • 安装工作负载:在安装程序中,确保勾选 “使用 C++ 的桌面开发” 工作负载,这会自动安装编译器、Windows SDK 和必要的工具。
  • SDK (Software Development Kit):Windows SDK 包含了编译 Windows 程序所需的头文件(如 windows.h)、库文件和工具,Visual Studio 安装时会自动包含最新的 SDK。

第二部分:第一个 Windows 程序 - "Hello, Windows!"

这是 Windows 编程的 "Hello, World!",它会创建一个空白窗口,并在窗口标题栏显示 "Hello, Windows!"。

程序结构解析

一个最简单的 Windows 程序由以下几个关键部分组成:

Windows API教程,从入门到精通怎么学?-图3
(图片来源网络,侵删)
  • WinMain 函数:程序的入口点,类似于控制台程序的 main 函数,它负责注册窗口类、创建窗口、进入消息循环。
  • WNDCLASSEX 结构体:定义窗口的“蓝图”或“模板”,包括窗口过程、图标、光标、背景色等。
  • RegisterClassEx 函数:将 WNDCLASSEX 结构体注册到操作系统,使其成为一个合法的窗口类。
  • CreateWindowEx 函数:根据已注册的窗口类,创建一个具体的窗口实例。
  • ShowWindowUpdateWindow 函数:显示窗口,并立即发送一个 WM_PAINT 消息,让窗口进行首次绘制。
  • MSG 结构体和 GetMessage / DispatchMessageMSG 用于存储消息。GetMessage 从消息队列中获取一条消息,DispatchMessage 将消息发送给窗口过程进行处理。
  • WndProc 函数:窗口过程,是窗口的“大脑”,操作系统会把各种消息(如鼠标点击、键盘输入、重绘请求)发送到这里。

代码详解

创建一个新的 C++ 控制台项目(在 Visual Studio 中,选择 "空项目" 然后添加一个 .cpp 文件),并将以下代码粘贴进去。

#include <windows.h> // 包含所有 Windows API 的头文件
// 窗口过程的函数声明
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
// 程序入口点
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    // 1. 定义并注册窗口类
    WNDCLASSEX wcex = { 0 };
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW; // 窗口大小改变时重绘
    wcex.lpfnWndProc = WndProc;            // 指向窗口过程的函数指针
    wcex.hInstance = hInstance;
    wcex.hCursor = LoadCursor(NULL, IDC_ARROW); // 使用标准箭头光标
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // 使用默认窗口背景色
    wcex.lpszClassName = "MainWindowClass";      // 窗口类名
    if (!RegisterClassEx(&wcex)) {
        MessageBox(NULL, "窗口类注册失败!", "错误", MB_ICONERROR);
        return 1;
    }
    // 2. 创建窗口
    HWND hWnd = CreateWindowEx(
        0,                              // 扩展样式
        "MainWindowClass",              // 窗口类名
        "Hello, Windows!",              // 窗口标题
        WS_OVERLAPPEDWINDOW,            // 窗口样式
        CW_USEDEFAULT, CW_USEDEFAULT,   // x, y 坐标 (由系统决定)
        500, 400,                      // 宽度, 高度
        NULL,                           // 父窗口句柄
        NULL,                           // 菜单句柄
        hInstance,                      // 应用程序实例句柄
        NULL                            // 创建参数
    );
    if (!hWnd) {
        MessageBox(NULL, "窗口创建失败!", "错误", MB_ICONERROR);
        return 1;
    }
    // 3. 显示和更新窗口
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    // 4. 消息循环
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg); // 翻译键盘消息 (如 WM_KEYDOWN -> WM_CHAR)
        DispatchMessage(&msg);  // 将消息发送给 WndProc 处理
    }
    // 当 GetMessage 返回 0 时,程序结束
    return (int)msg.wParam;
}
// 5. 窗口过程函数
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
    PAINTSTRUCT ps;
    HDC hdc;
    switch (message) {
    case WM_PAINT: {
        // 当窗口需要重绘时,操作系统会发送此消息
        hdc = BeginPaint(hWnd, &ps);
        // TODO: 在这里进行绘制
        TextOut(hdc, 50, 50, "你好,Windows API世界!", 18);
        EndPaint(hWnd, &ps);
        break;
    }
    case WM_DESTROY: {
        // 当窗口被销毁时,发送此消息
        PostQuitMessage(0); // 发送 WM_QUIT 消息,使 GetMessage 循环退出
        break;
    }
    default:
        // 对于我们没有处理的任何消息,都交给系统默认处理
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

编译与运行

  1. 在 Visual Studio 中,选择 "生成" -> "生成解决方案"。
  2. 成功生成后,按 Ctrl + F5 运行(不调试运行),你会看到一个标题为 "Hello, Windows!" 的窗口。

第三部分:深入窗口过程

WndProc 是程序的核心,它是一个回调函数,由操作系统在特定事件发生时调用。

WndProc 函数详解

  • HWND hWnd: 此消息所属的窗口句柄。
  • UINT message: 消息的标识符,如 WM_PAINT, WM_LBUTTONDOWN
  • WPARAM wParam: 消息的附加信息 1,其含义取决于 message
  • LPARAM lParam: 消息的附加信息 2,其含义也取决于 message

消息类型

  • WM_PAINT: 当窗口的某部分需要被重绘时发送(窗口被另一个窗口遮挡后又露出来),处理此消息时,你需要获取设备上下文 (HDC) 并进行绘制。
  • WM_DESTROY: 当窗口被销毁时发送,这是清理资源的好地方,标准的做法是调用 PostQuitMessage(0),这会向消息队列中插入一个 WM_QUIT 消息,导致 GetMessage 循环结束,程序退出。
  • WM_CLOSE: 当用户点击窗口的 "X" 按钮时发送,默认情况下,DefWindowProc 会处理此消息并调用 DestroyWindow,从而触发 WM_DESTROY,你也可以在这里弹出一个 "确认退出" 的对话框。

处理用户输入

让我们在 WndProc 中添加鼠标点击事件的处理。

// 在 WndProc 的 switch 语句中添加 case WM_LBUTTONDOWN:
case WM_LBUTTONDOWN: {
    // lParam 包含鼠标的 x, y 坐标
    int xPos = LOWORD(lParam);
    int yPos = HIWORD(lParam);
    char buffer[100];
    sprintf_s(buffer, "鼠标点击位置: (%d, %d)", xPos, yPos);
    MessageBox(hWnd, buffer, "鼠标点击", MB_OK);
    break;
}

现在运行程序,点击窗口内的任意位置,就会弹出一个对话框显示点击坐标。


第四部分:绘制图形

绘制图形需要使用 GDI (Graphics Device Interface)。

获取设备上下文

设备上下文 (HDC) 是一个 GDI 对象,包含了绘制窗口所需的所有信息(如选中的画笔、字体、颜色等),获取 HDC 的主要方式有两种:

  • BeginPaint / EndPaint: 只用于响应 WM_PAINT 消息,它会自动使无效区域变为有效。
  • GetDC / ReleaseDC: 用于在非 WM_PAINT 消息中绘制(在鼠标移动时实时绘制线条)。必须成对调用,否则会泄露资源。

使用 GDI 函数

WM_PAINT 消息中,我们可以使用各种 GDI 函数来绘制。

case WM_PAINT: {
    hdc = BeginPaint(hWnd, &ps);
    // 1. 绘制文本
    TextOut(hdc, 50, 50, "你好,Windows API世界!", 18);
    // 2. 创建和选择画笔
    HPEN hPen = CreatePen(PS_SOLID, 3, RGB(255, 0, 0)); // 红色实线画笔,宽度为3
    HPEN hOldPen = (HPEN)SelectObject(hdc, hPen); // 选择新画笔,并保存旧的
    // 3. 绘制线条
    MoveToEx(hdc, 50, 100, NULL); // 移动到 (50, 100)
    LineTo(hdc, 250, 200);       // 画线到 (250, 200)
    // 4. 创建和选择画刷
    HBRUSH hBrush = CreateSolidBrush(RGB(0, 0, 255)); // 蓝色实心画刷
    HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);
    // 5. 绘制矩形
    Rectangle(hdc, 50, 250, 250, 350);
    // 6. 恢复原始对象并删除创建的对象
    SelectObject(hdc, hOldPen);
    SelectObject(hdc, hOldBrush);
    DeleteObject(hPen);
    DeleteObject(hBrush);
    EndPaint(hWnd, &ps);
    break;
}

一个简单的画板程序

这个程序允许你按住鼠标左键并拖动来画线。

// 在全局变量中添加一个标志位
bool g_isDrawing = false;
int g_lastX = 0, g_lastY = 0;
// 在 WndProc 中添加消息处理
case WM_LBUTTONDOWN: {
    g_isDrawing = true;
    g_lastX = LOWORD(lParam);
    g_lastY = HIWORD(lParam);
    break;
}
case WM_MOUSEMOVE: {
    if (g_isDrawing) {
        hdc = GetDC(hWnd); // 在非 WM_PAINT 中使用 GetDC
        HPEN hPen = CreatePen(PS_SOLID, 2, RGB(0, 0, 0));
        HPEN hOldPen = (HPEN)SelectObject(hdc, hPen);
        MoveToEx(hdc, g_lastX, g_lastY, NULL);
        LineTo(hdc, LOWORD(lParam), HIWORD(lParam));
        SelectObject(hdc, hOldPen);
        DeleteObject(hPen);
        ReleaseDC(hWnd, hdc); // 记得释放 DC
        g_lastX = LOWORD(lParam);
        g_lastY = HIWORD(lParam);
    }
    break;
}
case WM_LBUTTONUP: {
    g_isDrawing = false;
    break;
}

第五部分:控件与资源

纯 API 创建控件很繁琐,但它是理解 GUI 工作原理的最佳方式。

创建控件

使用 CreateWindowEx 函数可以创建标准控件,第一个参数通常是控件的类名(如 "BUTTON", "EDIT")。

// 在 WinMain 中,创建窗口后,添加以下代码来创建一个按钮
HWND hButton = CreateWindowEx(
    0,                           // 扩展样式
    "BUTTON",                    // 控件类名
    "点击我",                     // 按钮文本
    WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // 样式
    10, 10, 100, 30,            // 位置和大小
    hWnd,                        // 父窗口句柄
    (HMENU)1,                    // 控件ID (一个唯一的值)
    hInstance,                   // 实例句柄
    NULL                         // 参数
);

消息处理与控件通信

控件通过向父窗口发送消息来与父窗口通信,我们需要在 WndProc 中处理这些消息。

  • WM_COMMAND: 当控件被操作(如按钮被点击、菜单被选择)时,会向父窗口发送此消息。
  • wParam 的低 16 位是控件的 ID,高 16 位是通知码(如 BN_CLICKED 表示按钮被点击)。
// 在 WndProc 的 switch 语句中添加
case WM_COMMAND: {
    int wmId = LOWORD(wParam); // 获取控件ID
    // 分析菜单选择:
    switch (wmId) {
    case 1: // 我们为按钮设置的ID
        MessageBox(hWnd, "按钮被点击了!", "提示", MB_OK);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    break;
}

使用资源文件 (.rc)

硬编码控件和字符串不方便,资源文件(.rc)可以让你用类似脚本的语法定义资源(图标、位图、对话框、菜单等)。

  1. 在 Visual Studio 中,右键项目 -> "添加" -> "资源..." -> "对话框",创建一个新对话框。
  2. 在对话框编辑器中添加一个静态文本框和编辑框。
  3. 保存后,Visual Studio 会自动生成一个 .rc 文件和一个对应的 .h 头文件(如 resource.h)。
  4. 在代码中,你可以使用 DialogBox 函数来加载并显示这个对话框。

第六部分:进阶主题与学习资源

Unicode 与 MBCS

现代 Windows 应用程序应使用 Unicodewindows.h 中定义了 TCHAR 宏和 TEXT 宏,让你的代码可以轻松地在 Unicode 和 MBCS (多字节字符集) 之间切换。

  • char -> TCHAR
  • strlen -> _tcslen
  • "Hello" -> TEXT("Hello")

现代 Windows 编程

直接使用 C 风格的 Windows API 虽然经典,但非常繁琐,微软一直在提供更现代的 C++ 方式:

  • Windows SDK (C++/WinRT): 官方推荐的现代 C++ API,基于 C++17 的投影技术,它提供了类型安全、异步操作等现代 C++ 特性,是开发 UWP 和 Win32 应用的未来方向。
  • ATL (Active Template Library): 一个轻量级的 C++ 模板库,用于简化 COM 编程和窗口创建。
  • MFC (Microsoft Foundation Classes): 一个更重量级的 C++ 类库,封装了大部分 Windows API,提供了文档/视图架构等高级特性。

对于初学者,掌握纯 API 是基础;对于大型项目,C++/WinRT 或 MFC 是更高效的选择。

推荐书籍与在线资源

  • 书籍:
    • 《Windows 程序设计》(Programming Windows, Charles Petzold): 圣经级别的著作,必读!它详细讲解了 Win32 API 的方方面面,虽然使用的是 C 语言,但原理是永恒的。
    • 《Windows 核心编程》(Windows via C/C++, Jeffrey Richter): 更深入地讲解了 Windows 内部机制,如进程、线程、内存管理等,适合进阶。
  • 在线资源:
    • Microsoft Docs: 最权威的 API 文档,搜索任何 API 函数,都能找到详细的说明、参数和示例代码。
    • The Old New Thing: Raymond Chen 的博客,充满了关于 Windows 历史和内部机制的精彩故事和深入解释。
    • GitHub: 搜索 "win32api example" 可以找到大量开源示例项目。

这份教程为你打开了 Windows API 的大门,从创建第一个窗口开始,逐步深入到消息处理、图形绘制和控件交互,你已经掌握了 Windows 编程的核心思想,继续探索,你会发现 Windows 系统的强大和魅力所在。

分享:
扫描分享到社交APP
上一篇
下一篇