Win32 编程基础入门及实例
Win32 API(应用程序编程接口)是用于开发 Windows 操作系统应用程序的核心接口集。尽管现代 Windows 开发已经有了许多更高级的框架和库,如 .NET Framework、WPF、UWP 等,但 Win32 API 仍然是这些技术的基础,并且在需要高性能、低级别系统访问的场景中依然不可替代。本文将介绍 Win32 编程的基础知识,并通过实例展示如何开发 Win32 应用程序。
1. Win32 编程概述
1.1 什么是 Win32 API
Win32 API 是 Windows 操作系统提供的一组 C 语言函数和数据结构,用于开发 Windows 应用程序。它包括以下几个主要部分:
用户界面:窗口管理、消息处理、对话框、控件等
图形设备接口 (GDI):绘图、颜色管理、字体等
系统服务:文件操作、内存管理、进程和线程等
网络:Socket 编程、网络协议等
安全:访问控制、加密等
1.2 Win32 应用程序的基本结构
一个典型的 Win32 应用程序通常包含以下几个核心部分:
WinMain 函数:应用程序入口点
窗口过程 (Window Procedure):处理窗口消息
消息循环:从消息队列获取和分派消息
资源:图标、菜单、对话框等
1.3 开发环境搭建
开发 Win32 应用程序需要以下工具:
Visual Studio:微软官方 IDE,提供完整的 Win32 开发支持
Windows SDK:包含头文件、库文件、工具和文档
2. 创建第一个 Win32 应用程序
2.1 最小化的 Win32 程序
我们先来看一个简单的 Win32 程序框架:
cpp
#include <windows.h>
// 窗口过程函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// 在窗口中心绘制文本
RECT rect;
GetClientRect(hwnd, &rect);
DrawText(hdc, L"Hello, Win32!", -1, &rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
}
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
// 程序入口点
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
// 注册窗口类
const wchar_t CLASS_NAME[] = L"Sample Window Class";
WNDCLASS wc = {};
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
RegisterClass(&wc);
// 创建窗口
HWND hwnd = CreateWindowEx(
0, // 扩展样式
CLASS_NAME, // 窗口类名
L"My First Win32 Window", // 窗口标题
WS_OVERLAPPEDWINDOW, // 窗口样式
// 位置和大小
CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
NULL, // 父窗口
NULL, // 菜单
hInstance, // 实例句柄
NULL // 额外参数
);
if (hwnd == NULL) {
return 0;
}
// 显示窗口
ShowWindow(hwnd, nCmdShow);
// 消息循环
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
2.2 关键部分解析
2.2.1 窗口过程 (WindowProc)
窗口过程是一个回调函数,Windows 系统通过它向应用程序发送消息。每个窗口都有一个关联的窗口过程,用于处理该窗口接收到的所有消息。
cpp
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
参数说明:
hwnd
:窗口句柄,标识消息的目标窗口
uMsg
:消息标识符(如 WM_PAINT、WM_DESTROY 等)
wParam
和 lParam
:消息特定的参数
2.2.2 WinMain 函数
WinMain 是 Windows 应用程序的入口点,相当于控制台程序中的 main 函数。
cpp
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
参数说明:
hInstance
:当前应用程序实例的句柄
hPrevInstance
:在现代 Windows 中总是 NULL(历史遗留参数)
lpCmdLine
:命令行参数
nCmdShow
:窗口的显示方式(最大化、最小化等)
2.2.3 消息循环
消息循环负责从系统消息队列获取消息并分派给适当的窗口过程:
cpp
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
每个 Win32 应用程序必须有这样一个消息循环,用于处理系统发送的输入、绘图等消息。
3. Win32 应用程序的基础组件
3.1 窗口和消息处理
Windows 操作系统是以消息驱动的,各种用户交互和系统事件都会生成消息,发送到相应的窗口过程进行处理。
3.1.1 常见窗口消息
以下是一些常见的窗口消息:
cpp
case WM_CREATE: // 窗口创建
// 初始化窗口
return 0;
case WM_CLOSE: // 窗口关闭
// 询问用户是否确定关闭
if (MessageBox(hwnd, L"确定要关闭窗口吗?", L"提示", MB_OKCANCEL) == IDOK) {
DestroyWindow(hwnd);
}
return 0;
case WM_DESTROY: // 窗口销毁
PostQuitMessage(0);
return 0;
case WM_PAINT: // 窗口需要重绘
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// 绘制代码
EndPaint(hwnd, &ps);
}
return 0;
case WM_COMMAND: // 菜单、按钮等命令
// 菜单ID通过LOWORD(wParam)获取
switch (LOWORD(wParam)) {
case ID_FILE_EXIT:
DestroyWindow(hwnd);
return 0;
}
break;
case WM_SIZE: // 窗口大小改变
// 新窗口宽度和高度
int width = LOWORD(lParam);
int height = HIWORD(lParam);
// 处理窗口大小改变
return 0;
3.1.2 子类化控件
子类化是一种重写控件默认行为的技术,通过替换控件的窗口过程来实现:
cpp
// 原始按钮窗口过程
WNDPROC g_OldButtonProc = NULL;
// 新的按钮窗口过程
LRESULT CALLBACK NewButtonProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_LBUTTONDOWN:
// 自定义处理左键按下
MessageBox(NULL, L"按钮被点击!", L"子类化示例", MB_OK);
break;
}
// 调用原始窗口过程处理其他消息
return CallWindowProc(g_OldButtonProc, hwnd, uMsg, wParam, lParam);
}
// 在创建按钮后子类化
HWND hwndButton = CreateWindow(
L"BUTTON", L"点击我", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
50, 50, 100, 30, hwndParent, (HMENU)ID_BUTTON, hInstance, NULL
);
// 保存原始窗口过程并设置新的窗口过程
g_OldButtonProc = (WNDPROC)SetWindowLongPtr(hwndButton, GWLP_WNDPROC, (LONG_PTR)NewButtonProc);
3.2 GDI 绘图基础
GDI (Graphics Device Interface) 是 Windows 中的绘图子系统,提供了在窗口或设备上绘制图形和文本的功能。
3.2.1 基本绘图
cpp
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// 设置绘图模式和颜色
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, RGB(0, 0, 255)); // 蓝色文本
// 创建画笔和画刷
HPEN hPen = CreatePen(PS_SOLID, 2, RGB(255, 0, 0)); // 红色画笔
HBRUSH hBrush = CreateSolidBrush(RGB(0, 255, 0)); // 绿色画刷
// 保存原画笔和画刷
HPEN hOldPen = (HPEN)SelectObject(hdc, hPen);
HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);
// 绘制矩形
Rectangle(hdc, 50, 50, 200, 150);
// 绘制椭圆
Ellipse(hdc, 250, 50, 400, 150);
// 绘制线条
MoveToEx(hdc, 50, 200, NULL);
LineTo(hdc, 400, 200);
// 恢复原画笔和画刷
SelectObject(hdc, hOldPen);
SelectObject(hdc, hOldBrush);
// 释放资源
DeleteObject(hPen);
DeleteObject(hBrush);
// 绘制文本
RECT rect = {50, 250, 400, 300};
DrawText(hdc, L"Hello, GDI!", -1, &rect, DT_CENTER);
EndPaint(hwnd, &ps);
return 0;
}
3.2.2 双缓冲绘图
为了避免绘图时闪烁,可以使用双缓冲技术:
cpp
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// 获取客户区尺寸
RECT clientRect;
GetClientRect(hwnd, &clientRect);
int width = clientRect.right - clientRect.left;
int height = clientRect.bottom - clientRect.top;
// 创建内存兼容DC和位图
HDC memDC = CreateCompatibleDC(hdc);
HBITMAP memBitmap = CreateCompatibleBitmap(hdc, width, height);
HBITMAP oldBitmap = (HBITMAP)SelectObject(memDC, memBitmap);
// 在内存DC中绘图
HBRUSH whiteBrush = (HBRUSH)GetStockObject(WHITE_BRUSH);
FillRect(memDC, &clientRect, whiteBrush);
// ... 其他绘图代码 ...
// 将内存DC的内容复制到窗口DC
BitBlt(hdc, 0, 0, width, height, memDC, 0, 0, SRCCOPY);
// 清理资源
SelectObject(memDC, oldBitmap);
DeleteObject(memBitmap);
DeleteDC(memDC);
EndPaint(hwnd, &ps);
return 0;
}
3.3 常用控件
Windows 提供了许多预定义的控件,如按钮、编辑框、列表框等。
3.3.1 创建和使用控件
cpp
// 创建按钮
HWND hwndButton = CreateWindow(
L"BUTTON", // 预定义的控件类名
L"点击我", // 按钮文本
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, // 样式
50, 50, // 位置 (x,y)
100, 30, // 大小 (width,height)
hwnd, // 父窗口句柄
(HMENU)ID_BUTTON, // 控件ID
hInstance, // 应用程序实例句柄
NULL // 附加数据
);
// 创建静态文本
HWND hwndStatic = CreateWindow(
L"STATIC",
L"这是静态文本",
WS_CHILD | WS_VISIBLE | SS_LEFT,
50, 100, 200, 20,
hwnd, (HMENU)ID_STATIC, hInstance, NULL
);
// 创建编辑框
HWND hwndEdit = CreateWindow(
L"EDIT",
L"",
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
50, 150, 200, 25,
hwnd, (HMENU)ID_EDIT, hInstance, NULL
);
// 创建复选框
HWND hwndCheckbox = CreateWindow(
L"BUTTON",
L"复选框",
WS_CHILD | WS_VISIBLE | BS_AUTOCHECKBOX,
50, 200, 100, 30,
hwnd, (HMENU)ID_CHECKBOX, hInstance, NULL
);
// 设置控件文本
SetWindowText(hwndStatic, L"新的静态文本");
// 获取控件文本
wchar_t buffer[256];
GetWindowText(hwndEdit, buffer, 256);
// 发送消息到控件
SendMessage(hwndCheckbox, BM_SETCHECK, BST_CHECKED, 0);
3.3.2 控件消息处理
cpp
case WM_COMMAND:
switch (LOWORD(wParam)) {
case ID_BUTTON:
if (HIWORD(wParam) == BN_CLICKED) {
// 按钮被点击
MessageBox(hwnd, L"按钮被点击了!", L"提示", MB_OK);
}
break;
case ID_EDIT:
if (HIWORD(wParam) == EN_CHANGE) {
// 编辑框内容变化
wchar_t buffer[256];
GetWindowText((HWND)lParam, buffer, 256);
// 处理编辑框内容
}
break;
case ID_CHECKBOX:
if (HIWORD(wParam) == BN_CLICKED) {
// 复选框状态改变
BOOL checked = (SendMessage((HWND)lParam, BM_GETCHECK, 0, 0) == BST_CHECKED);
// 处理复选框状态
}
break;
}
break;
3.4 菜单和对话框
3.4.1 创建和使用菜单
cpp
// 创建菜单
HMENU hMenu = CreateMenu();
HMENU hSubMenu = CreatePopupMenu();
// 添加菜单项
AppendMenu(hSubMenu, MF_STRING, ID_FILE_NEW, L"新建(&N)");
AppendMenu(hSubMenu, MF_STRING, ID_FILE_OPEN, L"打开(&O)");
AppendMenu(hSubMenu, MF_SEPARATOR, 0, NULL);
AppendMenu(hSubMenu, MF_STRING, ID_FILE_EXIT, L"退出(&X)");
// 添加子菜单到主菜单
AppendMenu(hMenu, MF_POPUP, (UINT_PTR)hSubMenu, L"文件(&F)");
// 设置窗口菜单
SetMenu(hwnd, hMenu);
// 处理菜单命令
case WM_COMMAND:
switch (LOWORD(wParam)) {
case ID_FILE_NEW:
// 处理"新建"菜单
break;
case ID_FILE_OPEN:
// 显示打开文件对话框
{
wchar_t filename[MAX_PATH] = {0};
OPENFILENAME ofn = {0};
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hwnd;
ofn.lpstrFilter = L"文本文件(*.txt)*.txt所有文件(*.*)*.*";
ofn.lpstrFile = filename;
ofn.nMaxFile = MAX_PATH;
ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
if (GetOpenFileName(&ofn)) {
// 处理选中的文件
}
}
break;
case ID_FILE_EXIT:
DestroyWindow(hwnd);
break;
}
break;
3.4.2 创建模态对话框
首先在资源脚本 (.rc) 文件中定义对话框模板:
apache
// 在 Resource.rc 文件中
IDD_DIALOG1 DIALOGEX 0, 0, 309, 176
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "示例对话框"
FONT 8, "MS Shell Dlg", 400, 0, 0x1
BEGIN
DEFPUSHBUTTON "确定",IDOK,198,155,50,14
PUSHBUTTON "取消",IDCANCEL,252,155,50,14
EDITTEXT IDC_EDIT1,84,23,140,14,ES_AUTOHSCROLL
LTEXT "请输入姓名:",IDC_STATIC,20,25,57,8
END
然后创建对话框过程函数:
cpp
// 对话框过程函数
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_INITDIALOG:
// 初始化对话框
return TRUE;
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDOK:
{
// 获取编辑框文本
wchar_t buffer[256];
GetDlgItemText(hwndDlg, IDC_EDIT1, buffer, 256);
// 处理输入的文本
MessageBox(hwndDlg, buffer, L"你输入的是", MB_OK);
// 关闭对话框并返回IDOK
EndDialog(hwndDlg, IDOK);
return TRUE;
}
case IDCANCEL:
// 关闭对话框并返回IDCANCEL
EndDialog(hwndDlg, IDCANCEL);
return TRUE;
}
break;
}
return FALSE;
}
// 在需要显示对话框的地方调用
INT_PTR result = DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), hwnd, DialogProc);
if (result == IDOK) {
// 用户点击了确定
} else if (result == IDCANCEL) {
// 用户点击了取消
}
3.4.3 创建非模态对话框
cpp
HWND g_hwndDialog = NULL;
// 显示非模态对话框
void ShowModelessDialog(HWND hwndParent) {
if (g_hwndDialog == NULL) {
g_hwndDialog = CreateDialog(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), hwndParent, DialogProc);
ShowWindow(g_hwndDialog, SW_SHOW);
} else {
// 对话框已经存在,激活它
SetForegroundWindow(g_hwndDialog);
}
}
// 非模态对话框过程函数
INT_PTR CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDOK:
{
wchar_t buffer[256];
GetDlgItemText(hwndDlg, IDC_EDIT1, buffer, 256);
MessageBox(hwndDlg, buffer, L"你输入的是", MB_OK);
// 关闭对话框,但不调用EndDialog
DestroyWindow(hwndDlg);
return TRUE;
}
case IDCANCEL:
DestroyWindow(hwndDlg);
return TRUE;
}
break;
case WM_DESTROY:
g_hwndDialog = NULL; // 重置对话框句柄
return TRUE;
}
return FALSE;
}
4. 进阶主题
4.1 多线程编程
Win32 API 提供了创建和管理线程的函数:
cpp
// 线程函数
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
// 线程代码
for (int i = 0; i < 10; i++) {
// 处理数据
Sleep(1000); // 休眠1秒
}
return 0; // 线程返回值
}
// 创建线程
DWORD threadId;
HANDLE hThread = CreateThread(
NULL, // 默认安全属性
0, // 默认堆栈大小
ThreadProc, // 线程函数
NULL, // 传递给线程函数的参数
0, // 立即运行线程
&threadId // 接收线程ID
);
if (hThread == NULL) {
// 创建线程失败
}
// 等待线程结束
WaitForSingleObject(hThread, INFINITE);
// 获取线程返回值
DWORD exitCode;
GetExitCodeThread(hThread, &exitCode);
// 关闭线程句柄
CloseHandle(hThread);
4.1.1 线程同步
cpp
// 创建互斥体
HANDLE hMutex = CreateMutex(NULL, FALSE, NULL);
// 在线程中使用互斥体
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
for (int i = 0; i < 10; i++) {
// 等待获取互斥体
WaitForSingleObject(hMutex, INFINITE);
// 临界区代码(访问共享资源)
// 释放互斥体
ReleaseMutex(hMutex);
Sleep(100);
}
return 0;
}
// 其他同步对象
HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); // 手动重置事件
HANDLE hSemaphore = CreateSemaphore(NULL, 2, 2, NULL); // 信号量(初始计数2,最大计数2)
CRITICAL_SECTION criticalSection; // 临界区
// 初始化临界区
InitializeCriticalSection(&criticalSection);
// 使用临界区
EnterCriticalSection(&criticalSection);
// 访问共享资源
LeaveCriticalSection(&criticalSection);
// 删除临界区
DeleteCriticalSection(&criticalSection);
4.2 文件和注册表操作
4.2.1 文件操作
cpp
// 创建或打开文件
HANDLE hFile = CreateFile(
L"example.txt", // 文件名
GENERIC_READ | GENERIC_WRITE, // 访问模式
0, // 共享模式(0表示独占)
NULL, // 安全属性
CREATE_ALWAYS, // 创建处理
FILE_ATTRIBUTE_NORMAL, // 文件属性
NULL // 模板文件句柄
);
if (hFile == INVALID_HANDLE_VALUE) {
// 打开文件失败
}
// 写入文件
const char* data = "Hello, Win32!";
DWORD bytesWritten;
WriteFile(
hFile, // 文件句柄
data, // 要写入的数据缓冲区
strlen(data), // 要写入的字节数
&bytesWritten, // 接收实际写入的字节数
NULL // 重叠结构(用于异步I/O)
);
// 移动文件指针到开头
SetFilePointer(hFile, 0, NULL, FILE_BEGIN);
// 读取文件
char buffer[100];
DWORD bytesRead;
ReadFile(
hFile, // 文件句柄
buffer, // 接收数据的缓冲区
sizeof(buffer), // 要读取的字节数
&bytesRead, // 接收实际读取的字节数
NULL // 重叠结构
);
// 确保字符串以null结尾
buffer[bytesRead] = '';
// 关闭文件句柄
CloseHandle(hFile);
4.2.2 注册表操作
cpp
// 打开注册表键
HKEY hKey;
LONG result = RegOpenKeyEx(
HKEY_CURRENT_USER, // 注册表根键
L"Software\MyApp", // 子键路径
0, // 保留,必须为0
KEY_READ | KEY_WRITE, // 访问权限
&hKey // 接收键句柄
);
if (result != ERROR_SUCCESS) {
// 打开失败,尝试创建
result = RegCreateKeyEx(
HKEY_CURRENT_USER,
L"Software\MyApp",
0,
NULL,
REG_OPTION_NON_VOLATILE,
KEY_READ | KEY_WRITE,
NULL,
&hKey,
NULL
);
if (result != ERROR_SUCCESS) {
// 创建失败
}
}
// 写入字符串值
result = RegSetValueEx(
hKey, // 键句柄
L"StringValue", // 值名称
0, // 保留,必须为0
REG_SZ, // 数据类型
(BYTE*)L"Hello World", // 数据
sizeof(L"Hello World") // 数据大小(包括结尾null)
);
// 写入DWORD值
DWORD dwValue = 12345;
result = RegSetValueEx(
hKey,
L"DWordValue",
0,
REG_DWORD,
(BYTE*)&dwValue,
sizeof(dwValue)
);
// 读取字符串值
wchar_t szBuffer[256];
DWORD dwBufferSize = sizeof(szBuffer);
DWORD dwType;
result = RegQueryValueEx(
hKey,
L"StringValue",
NULL,
&dwType,
(BYTE*)szBuffer,
&dwBufferSize
);
if (result == ERROR_SUCCESS && dwType == REG_SZ) {
// 使用szBuffer
}
// 读取DWORD值
DWORD dwReadValue;
DWORD dwValueSize = sizeof(dwReadValue);
result = RegQueryValueEx(
hKey,
L"DWordValue",
NULL,
&dwType,
(BYTE*)&dwReadValue,
&dwValueSize
);
if (result == ERROR_SUCCESS && dwType == REG_DWORD) {
// 使用dwReadValue
}
// 删除值
RegDeleteValue(hKey, L"StringValue");
// 关闭注册表键
RegCloseKey(hKey);
// 删除整个键(包括所有子键和值)
RegDeleteKey(HKEY_CURRENT_USER, L"Software\MyApp");
4.3 资源使用
4.3.1 图标和光标
cpp
// 加载图标
HICON hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MYICON));
HICON hSmallIcon = (HICON)LoadImage(hInstance, MAKEINTRESOURCE(IDI_MYICON),
IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
// 设置窗口图标
SendMessage(hwnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
SendMessage(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)hSmallIcon);
// 加载光标
HCURSOR hCursor = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_MYCURSOR));
// 设置窗口类光标
wc.hCursor = hCursor;
// 动态改变光标
SetCursor(hCursor);
4.3.2 位图操作
cpp
// 加载位图
HBITMAP hBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_MYBITMAP));
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// 创建内存DC
HDC memDC = CreateCompatibleDC(hdc);
// 选择位图到内存DC
HBITMAP oldBitmap = (HBITMAP)SelectObject(memDC, hBitmap);
// 获取位图信息
BITMAP bm;
GetObject(hBitmap, sizeof(BITMAP), &bm);
// 绘制位图
BitBlt(hdc, 10, 10, bm.bmWidth, bm.bmHeight, memDC, 0, 0, SRCCOPY);
// 或者使用透明绘制
TransparentBlt(hdc, 10, 10, bm.bmWidth, bm.bmHeight,
memDC, 0, 0, bm.bmWidth, bm.bmHeight, RGB(255, 0, 255));
// 恢复原位图并清理资源
SelectObject(memDC, oldBitmap);
DeleteDC(memDC);
EndPaint(hwnd, &ps);
return 0;
}
4.3.3 字符串资源
cpp
// 在资源文件中定义字符串
// STRINGTABLE
// BEGIN
// IDS_APP_TITLE "My Application"
// IDS_HELLO "Hello, World!"
// END
// 加载字符串资源
wchar_t buffer[256];
LoadString(hInstance, IDS_HELLO, buffer, 256);
5. 实例项目:简单的绘图应用
下面我们将创建一个简单的绘图应用,演示 Win32 编程的核心概念。
5.1 完整源代码
cpp
#include <windows.h>
#include <vector>
// 绘图点
struct Point {
int x;
int y;
};
// 绘图线条
struct Line {
std::vector<Point> points;
COLORREF color;
int thickness;
};
// 全局变量
std::vector<Line> g_lines; // 所有绘制的线条
bool g_isDrawing = false; // 是否正在绘制
COLORREF g_currentColor = RGB(0, 0, 0); // 当前颜色
int g_currentThickness = 2; // 当前线条粗细
// 窗口过程
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 绘制所有线条
void DrawLines(HDC hdc) {
for (const auto& line : g_lines) {
if (line.points.empty()) continue;
// 创建画笔
HPEN hPen = CreatePen(PS_SOLID, line.thickness, line.color);
HPEN hOldPen = (HPEN)SelectObject(hdc, hPen);
// 移动到起点
MoveToEx(hdc, line.points[0].x, line.points[0].y, NULL);
// 连接所有点
for (size_t i = 1; i < line.points.size(); i++) {
LineTo(hdc, line.points[i].x, line.points[i].y);
}
// 清理资源
SelectObject(hdc, hOldPen);
DeleteObject(hPen);
}
}
// WinMain函数 - 应用程序入口点
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
// 注册窗口类
WNDCLASS wc = {};
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = L"DrawingApp";
wc.hCursor = LoadCursor(NULL, IDC_CROSS); // 使用十字光标
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
RegisterClass(&wc);
// 创建菜单
HMENU hMenu = CreateMenu();
HMENU hColorMenu = CreatePopupMenu();
AppendMenu(hColorMenu, MF_STRING, 1001, L"黑色");
AppendMenu(hColorMenu, MF_STRING, 1002, L"红色");
AppendMenu(hColorMenu, MF_STRING, 1003, L"绿色");
AppendMenu(hColorMenu, MF_STRING, 1004, L"蓝色");
AppendMenu(hMenu, MF_POPUP, (UINT_PTR)hColorMenu, L"颜色");
AppendMenu(hMenu, MF_STRING, 1005, L"清除画布");
// 创建窗口
HWND hwnd = CreateWindowEx(
0,
L"DrawingApp",
L"简单绘图应用",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
NULL, hMenu, hInstance, NULL
);
if (hwnd == NULL) {
return 0;
}
// 显示窗口
ShowWindow(hwnd, nCmdShow);
// 消息循环
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
// 窗口过程函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
switch (wmId) {
case 1001: // 黑色
g_currentColor = RGB(0, 0, 0);
break;
case 1002: // 红色
g_currentColor = RGB(255, 0, 0);
break;
case 1003: // 绿色
g_currentColor = RGB(0, 255, 0);
break;
case 1004: // 蓝色
g_currentColor = RGB(0, 0, 255);
break;
case 1005: // 清除画布
g_lines.clear();
InvalidateRect(hwnd, NULL, TRUE);
break;
}
}
break;
case WM_LBUTTONDOWN:
{
// 开始绘制
g_isDrawing = true;
// 创建新线条
Line newLine;
newLine.color = g_currentColor;
newLine.thickness = g_currentThickness;
// 添加起始点
Point pt;
pt.x = GET_X_LPARAM(lParam);
pt.y = GET_Y_LPARAM(lParam);
newLine.points.push_back(pt);
g_lines.push_back(newLine);
}
break;
case WM_MOUSEMOVE:
if (g_isDrawing && !g_lines.empty()) {
// 添加新点
Point pt;
pt.x = GET_X_LPARAM(lParam);
pt.y = GET_Y_LPARAM(lParam);
g_lines.back().points.push_back(pt);
// 仅重绘需要更新的区域
RECT updateRect;
auto& lastPt = g_lines.back().points[g_lines.back().points.size() - 2];
// 计算重绘矩形,考虑线条粗细
int offset = g_currentThickness + 2;
updateRect.left = min(lastPt.x, pt.x) - offset;
updateRect.top = min(lastPt.y, pt.y) - offset;
updateRect.right = max(lastPt.x, pt.x) + offset;
updateRect.bottom = max(lastPt.y, pt.y) + offset;
InvalidateRect(hwnd, &updateRect, FALSE);
}
break;
case WM_LBUTTONUP:
// 结束绘制
g_isDrawing = false;
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// 使用双缓冲绘制
RECT clientRect;
GetClientRect(hwnd, &clientRect);
int width = clientRect.right - clientRect.left;
int height = clientRect.bottom - clientRect.top;
HDC memDC = CreateCompatibleDC(hdc);
HBITMAP memBitmap = CreateCompatibleBitmap(hdc, width, height);
HBITMAP oldBitmap = (HBITMAP)SelectObject(memDC, memBitmap);
// 清除背景
HBRUSH whiteBrush = (HBRUSH)GetStockObject(WHITE_BRUSH);
FillRect(memDC, &clientRect, whiteBrush);
// 绘制所有线条
DrawLines(memDC);
// 复制到屏幕
BitBlt(hdc, 0, 0, width, height, memDC, 0, 0, SRCCOPY);
// 清理资源
SelectObject(memDC, oldBitmap);
DeleteObject(memBitmap);
DeleteDC(memDC);
EndPaint(hwnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
5.2 主要功能
此简单绘图应用程序实现了以下功能:
用鼠标绘制自由曲线
选择绘图颜色(黑、红、绿、蓝)
清除画布
使用双缓冲消除闪烁
5.3 功能增强
可以考虑添加以下功能进一步增强应用程序:
保存和打开绘图文件
添加更多绘图工具(直线、矩形、椭圆等)
调整线条粗细
添加文本输入工具
实现撤销/重做功能
6. 总结与最佳实践
6.1 Win32 编程最佳实践
资源管理
总是配对创建和释放资源(GDI对象、句柄等)
使用 RAII(资源获取即初始化)模式管理资源生命周期
UI 响应性
避免在主线程中执行耗时操作
使用多线程处理复杂计算和I/O操作
绘图优化
使用双缓冲避免闪烁
仅重绘需要更新的区域
代码组织
将消息处理分解为小函数
使用面向对象的方法组织代码(即使使用C语言)
错误处理
检查API调用的返回值
使用 GetLastError()
获取详细错误信息
国际化
使用 Unicode 字符集(wchar_t)
从资源加载字符串,便于本地化
6.2 进一步学习资源
书籍
“Programming Windows” by Charles Petzold
“Windows Via C/C++” by Jeffrey Richter
在线资源
Microsoft Win32 API 文档
MSDN 示例代码库
Windows 开发者中心
开源项目
ReactOS(开源Windows实现)
Wine(Windows API兼容层)
6.3 现代替代方案
虽然 Win32 API 仍然是 Windows 开发的基础,但对于许多应用程序类型,以下现代框架可能是更好的选择:
WPF (Windows Presentation Foundation)
基于 .NET 的现代 UI 框架
使用 XAML 描述界面
强大的数据绑定和样式系统
UWP (Universal Windows Platform)
跨 Windows 设备的现代应用平台
更安全的沙盒环境
响应式设计和触摸优化
Win32 的 C++ 包装器
C++/WinRT
ATL (Active Template Library)
MFC (Microsoft Foundation Classes)
跨平台框架
Qt
Electron
Flutter for Windows
Win32 API 的优势在于直接访问底层 Windows 功能、最佳性能和最小依赖性,适合系统级编程和对性能有极高要求的应用程序。
Win32 编程虽然看起来复杂,但掌握其核心概念后,可以为您的 Windows 开发技能打下坚实的基础。无论您选择使用哪种现代框架,理解底层的 Win32 机制都会对深入理解 Windows 平台的工作原理大有帮助。
暂无评论内容