UP | HOME

Win32 API 窗口程序

目录

1 前言

之前因为一些原因放弃了学习 Win32 API 来编写图形程序, 虽然还是不打算深入学习 Win32 API, 但还是感觉有必要对 Win32 API 进行一定的了解和学习。

2 基本内容

2.1 类型定义

Win32 API 预先定义了一些类型, 编写程序之前了解这些是很有必要的。

  • 整数类型:
Data type Size Signed?
BYTE 8 bits Unsigned
DWORD 32 bits Unsigned
INT32 32 bits Signed
INT64 64 bits Signed
LONG 32 bits Signed
LONGLONG 64 bits Signed
UINT32 32 bits Unsigned
UINT64 64 bits Unsigned
ULONG 32 bits Unsigned
ULONGLONG 64 bits Unsigned
WORD 16 bits Unsigned
  • 布尔类型:

    Win32 API 中, 布尔类型为 BOOL, 只有两个值, 分别为 FALSETRUE.

    需要注意的是, Win32 API 中的 BOOL 是整数类型, 不能和 C++ 中的 bool 互换。

  • 指针类型:

    Win32 APi 中预定义了许多如 P-LP- 形式的指针类型。 其中 P 代表 pointer, LP 代表 long pointer.

    RECT*  rect;  // RECT 类型指针
    LPRECT rect;  // RECT 类型指针 LP-
    PRECT  rect;  // RECT 类型指针 P-
    
  • 精确指针类型:

    以下类型的指针的大小是确定的, 32 位机上是 32 位, 64 位机上是 64 位。 当程序被编译时,指针的大小便被固定下来。 也就是说, 在 32 位机上编译的程序, 指针大小便固定在 32 位。即使是在 64 位机上。

    • DWORD_PTR
    • INT_PTR
    • LONG_PTR
    • ULONG_PTR
    • UINT_PTR

2.2 匈牙利命名法

Win32 API 中采用 匈牙利命名法, 每个变量名由一个或多个小写字母开始。 这些字母有助于记忆变量的类型和用途。

DWORD dwNumber;  // dw -- DWORD
WORD wNumber;    // w -- worDWORD

2.3 使用字符串

Windows 系统的使用者遍布全世界, 仅仅使用 ANSI 字符是满足不了需求的。

因此, Win32 API 定义了新的字符类型以支持 Unicode. 其中宽字符 WCHAR 源自 C++wchar_t.

typedef wchar_t WCHAR;

声明宽字符或宽字符串时, 应该在前面添加 L.

wchar_t a = L'a';
wchar_t *str = L"hello";

除了 WCHAR 的类型:

Typedef Definition
CHAR char
PSTR or LPSTR char*
PCSTR or LPCSTR const char*
PWSTR or LPWSTR wchar_t*
PCWSTR or LPCWSTR const wchar_t*

Win32 API 还提供了两组 API 来缓解 UnicodeANSI 的转化。 并通过宏来统一这两组 API.

#ifdef UNICODE
#define SetWindowText  SetWindowTextW
#else
#define SetWindowText  SetWindowTextA
#endif

这是一个例子, SetWindowTextA 用于 ANSI, SetWindowTextW 用于 Unicode, 通过宏统一为 SetWindowText.

虽然统一了 API, 但是字符的定义还是很麻烦, 因此 Win32 API 还定义了宏 TCHARTEXT 来统一字符类型:

Macro Unicode ANSI
TCHAR wchar_t char
TEXT("x") L"x" "x"

这样一来, 下面的代码是等效的:

SetWindowText(TEXT("My Application"));  // Unicode or ANSI

SetWindowTextW(L"My Application");      // Unicode.

SetWindowTextA("My Application");       // ANSI.

计算字符串的长度是常用的一个功能, Win32 API 通过宏统一了它们:

#ifdef _UNICODE
#define _tcslen     wcslen
#else
#define _tcslen     strlen
#endif

可以发现, 部分宏通过 UNICODE 定义, 部分通过 _UNICODE 定义。 因此在编写 Unicode 程序的时候, 这两个宏都应该定义。

2.4 应用程序入口: WinMain

C 语言的入口函数是 main, 而 Win32 Api 程序的入口函数应该是 WinMain.

这个函数的定义格式应该是:

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow);

参数解释:

  • hInstance: 实例句柄, 操作系统通过这个值来标识应用程序
  • hPrevInstance: 16 系统的遗留, 现在默认置为 0
  • pCmdLine: 包含作为 Unicode 字符串的命令行参数
  • nCmdShow: 一个标志,说明主应用程序窗口是最小化,最大化还是正常显示

2.5 关于句柄

句柄Windows 中应该是一个比较重要的概念, 当初在学习的时候把我绕了一圈还晕乎乎的。

现在, 感觉 句柄 这个概念要容易理解的多了。

首先需要清楚的是: Windows 操作系统内核中有一个全局句柄表。而每个进程都有自己的一个句柄表。

Windows 通过 句柄 来标识 内核对象. Win32 API 编程中, 很多函数都需要用到一个对象的 句柄. 我们通过这个 句柄 来访问这个对象。

简单来说, 句柄 就是一把钥匙, 我们通过这把钥匙来访问内存中的 内核对象. 如果我们失去了这把钥匙,我们便失去了访问那个 内核对象 的能力。

调用 Win32 API 创建的对象会返回那个对象的 句柄, 拿到钥匙的我们便可以通过其他 Win32 API 来操作那个对象。

3 简单的窗口程序

#include <windows.h>

#pragma comment(lib, "gdi32.lib")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "Shell32.lib")

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    PSTR pCmdLine, int nCmdShow) {

    // 依次为窗口类名、窗口句柄、消息结构、窗口类
    static TCHAR szAppName[] = TEXT("WAS");
    HWND hwnd;
    MSG msg;
    WNDCLASS wndclass;

    // 窗口类初始化
    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = WndProc;  // 绑定消息处理过程
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wndclass.lpszMenuName = NULL;
    wndclass.lpszClassName = szAppName;

    // 注册窗口
    if (!RegisterClass(&wndclass)) {
        MessageBox(NULL, TEXT("This progrma requires Windows NT !"),
                   szAppName, MB_ICONERROR);
        return 0;
    }

    // 创建窗口并获取窗口句柄
    hwnd = CreateWindow(szAppName,    // window class name
                        TEXT("Title"),    // window caption(标题)
                        WS_OVERLAPPEDWINDOW,    // window style
                        CW_USEDEFAULT,    // initial x position
                        CW_USEDEFAULT,    // initial y position
                        CW_USEDEFAULT,    // initial x size
                        CW_USEDEFAULT,    // initial y size
                        NULL,    // parent window handle
                        NULL,    // window menu handle
                        hInstance,    // program instance handle
                        NULL);    // creation parameters

    // 显示窗口并更新
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    // 消息处理
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
    static int cxClient, cyClient;
    HDC hdc;
    PAINTSTRUCT ps;

    switch (message) {
    case WM_SIZE:
        cxClient = LOWORD(lParam);
        cyClient = HIWORD(lParam);
        return 0;

    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        EndPaint(hwnd, &ps);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, message, wParam, lParam);
}

这是一个简单的窗口程序, 运行后的样子应该类似于:

win32.png

对于头部的 #pragma comment 是编译这个程序依赖的静态库, 如果使用 VS, 可以省略。

代码理解部分只有对程序基本流程的解释, Win32 API 接口的参数和程序的具体编写并没有详细的描述。 如果需要了解可以看一看 官方教程.

3.1 代码理解

虽然是一个简单的窗口程序, 但是 Win32 窗口程序的基本框架和这个程序是基本相同的, 基本流程就是:

  1. 创建初始化窗口类
  2. 注册窗口类
  3. 创建窗口并获取窗口句柄
  4. 显示窗口
  5. 窗口消息处理

解释:

创建初始化窗口类
窗口类 WNDCLASS 用于定义窗口的样式。 如窗口的 ICON, 窗口的背景色等
注册窗口类
虽然创建初始化了窗口类, 但是操作系统还不知道这个窗口类, 因此需要将这个窗口类注册到操作系统, 然后创建窗口时就可以根据这个注册了的窗口类创建窗口了。
创建窗口并获取窗口句柄
创建窗口的 Win32 APICreateWindow. 这个函数的第一个参数为 窗口类 的名称。创建的窗口便是基于注册的这个窗口类的。 创建好窗口后, CreateWindow 会返回创建的窗口的 句柄, 后面的操作便根据这个句柄进行。
显示窗口
创建窗口后并不会立即显示, 而是在调用 ShowWindow 后进行显示。
窗口消息处理

Windows 的消息机制是编写 Win32 API 程序的重要一环。

while (GetMessage(&msg, NULL, 0, 0)) {
  TranslateMessage(&msg);
  DispatchMessage(&msg);
}

GetMessage 从消息队列中获取消息存入 msg 中。 msg 的类型是 MSG, 用于储存获取的消息。

TranslateMessage 对消息进行简单的处理, 转化为消息处理过程可辨认的类型。

DispatchMessage 调用这个程序的消息处理进程过程这个消息。

消息处理过程

Windows 消息处理过程对获取的消息进行处理。

过程的定义类似于:

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)

消息处理过程中, 通过 switch...case... 的方式选择需要处理的消息, 其他消息通过 DefWindowProc 进行默认的处理。

4 参考链接

版权声明:本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可