GDI+ (Graphics Device Interface Plus) 是微软提供的图形设备接口的升级版本,它提供了更强大、更易用的 2D 图形、成像和字体功能,虽然 GDI+ 的原生 API 是为 C++ 设计的,但它也完全可以在 C 语言中使用。

下面我将从核心概念、环境搭建、基本用法到高级应用,为你提供一个全面的指南。
核心概念
在开始编码之前,理解 GDI+ 的几个核心概念至关重要:
-
GDI+ 初始化和终止:
- GDI+ 是一个 COM 组件,不能直接调用,你必须先初始化它,使用完毕后必须终止它,否则会导致内存泄漏和程序不稳定。
GdiplusStartup: 初始化 GDI+ 环境。GdiplusShutdown: 清理并终止 GDI+ 环境。
-
GDI+ 对象:
(图片来源网络,侵删)- GDI+ 中的图形元素(如画笔、画刷、字体、图像)都是通过结构体来表示的,
Gdiplus::Pen、Gdiplus::SolidBrush。 - 这些对象在使用时都需要创建,使用完毕后必须销毁,以释放它们占用的资源,这是 C 语言中使用 GDI+ 最容易出错的地方。
- GDI+ 中的图形元素(如画笔、画刷、字体、图像)都是通过结构体来表示的,
-
Graphics 对象:
- 这是所有绘图操作的基础,它代表一个可绘制的表面,比如一个窗口的客户端区域、一个打印机页面或一个位图图像。
- 你需要从设备上下文(HDC)创建一个
Graphics对象,然后所有的绘图方法(如画线、画矩形)都通过这个Graphics对象来调用。
-
句柄:
- 在 C 语言中,我们主要与 Windows API 交互,所以我们会频繁使用 Windows 句柄,如
HWND(窗口句柄) 和HDC(设备上下文句柄),GDI+ 的许多函数都需要这些句柄作为输入。
- 在 C 语言中,我们主要与 Windows API 交互,所以我们会频繁使用 Windows 句柄,如
环境搭建
要在 C 语言项目中使用 GDI+,你需要做以下准备:
-
包含头文件: 在你的 C 源文件中,包含 GDI+ 的头文件,你需要包含
gdiplus.h,并且为了使用 GDI+ 的常量(如Color::Red),还需要包含gdipluscolor.h。
(图片来源网络,侵删)#include <windows.h> #include <gdiplus.h> #include <gdipluscolor.h> // 用于颜色常量
-
链接 GDI+ 库: 你需要告诉链接器链接 GDI+ 的库文件
gdiplus.lib。-
在 Visual Studio 中: 右键点击你的项目 -> 属性 -> 配置属性 -> 链接器 -> 输入 -> 附加依赖项,在其中添加
gdiplus.lib。 -
在命令行编译时: 使用
/link选项:cl your_code.c /link gdiplus.lib
-
-
定义 GDI+ 初始化结构体:
GdiplusStartup函数需要一个GdiplusStartupInput结构体和一个GdiplusStartupOutput结构体(后者通常为 NULL),我们通常在全局或函数作用域定义一个ULONG_PTR类型的token变量来存储 GDI+ 的令牌。
C 语言中使用 GDI+ 的基本步骤
下面是一个完整的、最简单的示例,它创建一个窗口并在窗口上绘制一个红色的矩形,这个例子涵盖了所有核心步骤。
示例代码:绘制一个红色矩形
#include <windows.h>
#include <gdiplus.h>
#include <gdipluscolor.h>
#include <stdio.h>
// 必须在全局或函数作用域定义
ULONG_PTR gdiplusToken;
// 窗口过程函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// 1. 从 HDC 创建 Graphics 对象
Gdiplus::Graphics graphics(hdc);
// 2. 创建一个画刷 (SolidBrush)
Gdiplus::SolidBrush brush(Gdiplus::Color::Red);
// 3. 使用 Graphics 对象和画刷绘制一个矩形
// (x, y, width, height)
graphics.FillRectangle(&brush, 50, 50, 200, 100);
// 4. 销毁创建的对象(非常重要!)
// 注意:Gdiplus::Graphics 对象在超出作用域时会自动销毁,
// 但显式调用 DeleteObject 是一个好习惯,尤其是在复杂代码中。
// 对于由 GDI+ 内部管理的对象(如从HDC创建的Graphics),通常不需要手动删除。
// 但对于我们自己创建的Pen, Brush等,必须删除。
delete &brush;
EndPaint(hwnd, &ps);
break;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
// --- 1. 初始化 GDI+ ---
Gdiplus::GdiplusStartupInput gdiplusStartupInput;
Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
// --- 2. 注册窗口类 ---
const wchar_t CLASS_NAME[] = L"GDIPlus Window Class";
WNDCLASS wc = { };
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
// --- 3. 创建窗口 ---
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
L"GDI+ C Language Demo", // Window text
WS_OVERLAPPEDWINDOW, // Window style
CW_USEDEFAULT, CW_USEDEFAULT, // Position (x, y)
800, 600, // Size (width, height)
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
NULL // Additional application data
);
if (hwnd == NULL) {
return 0;
}
ShowWindow(hwnd, nCmdShow);
// --- 4. 消息循环 ---
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// --- 5. 终止 GDI+ (在程序退出前) ---
Gdiplus::GdiplusShutdown(gdiplusToken);
return 0;
}
代码解析:
-
WinMain中的初始化与终止:- 在程序入口
WinMain的最开始,我们调用GdiplusStartup来“启动”GDI+。 - 在消息循环结束后,程序即将退出时,我们调用
GdiplusShutdown来“关闭”GDI+,释放所有资源。
- 在程序入口
-
WM_PAINT消息处理:- 当窗口需要重绘时(窗口被遮挡后又显示出来),系统会发送
WM_PAINT消息。 BeginPaint获取一个设备上下文HDC,这是进行 GDI 绘图的基础。Gdiplus::Graphics graphics(hdc);:这是连接 GDI 和 GDI+ 的桥梁,我们用HDC创建了一个Graphics对象,后续所有 GDI+ 的绘图操作都通过这个graphics对象进行。Gdiplus::SolidBrush brush(Gdiplus::Color::Red);:创建一个红色的实心画刷,注意,这是一个 C++ 风格的对象,在 C 语言中,我们同样可以创建和使用它。graphics.FillRectangle(&brush, ...);:调用Graphics对象的FillRectangle方法,使用我们创建的画刷来绘制一个填充矩形。delete &brush;:至关重要! 我们手动创建的SolidBrush对象,在使用完毕后必须用delete销毁,否则会造成内存泄漏。Graphics对象通常由HDC创建,其生命周期由 GDI+ 管理,一般不需要手动delete。
- 当窗口需要重绘时(窗口被遮挡后又显示出来),系统会发送
常用 GDI+ 绘图操作
掌握了基本流程后,你可以尝试更多的绘图操作。
绘制线条
// 在 WM_PAINT 中 Gdiplus::Graphics graphics(hdc); Gdiplus::Pen pen(Gdiplus::Color::Blue, 5.0f); // 颜色为蓝色,宽度为5 graphics.DrawLine(&pen, 10, 10, 300, 300); delete &pen; // 记得删除
绘制文本
// 在 WM_PAINT 中 Gdiplus::Graphics graphics(hdc); Gdiplus::FontFamily fontFamily(L"Arial"); Gdiplus::Font font(&fontFamily, 24, Gdiplus::FontStyleRegular, Gdiplus::UnitPoint); Gdiplus::SolidBrush brush(Gdiplus::Color::Black); graphics.DrawString(L"Hello GDI+!", -1, &font, Gdiplus::PointF(20, 150), &brush); delete &font; delete &brush;
加载并绘制图像
// 在 WM_PAINT 中
Gdiplus::Graphics graphics(hdc);
// 注意:L"image.png" 是宽字符串,文件路径要正确
Gdiplus::Image image(L"image.png");
if (image.GetLastStatus() == Gdiplus::Ok) {
graphics.DrawImage(&image, 0, 0, image.GetWidth(), image.GetHeight());
}
// Image 对象也需要删除
delete ℑ
C 语言与 C++ 的差异及注意事项
在 C 语言中使用 GDI+,你实际上是在调用一个 C++ 编写的库,这带来了一些需要注意的地方:
-
对象生命周期管理:
- 这是最关键的一点! 任何通过
new或 GDI+ 提供的工厂函数创建的对象(如Pen,Brush,Font,Image),你都必须负责在不再使用时用delete将其销毁,忘记delete会导致内存泄漏。 - 像
Graphics这种从 Windows 句柄(HDC)创建的对象,其生命周期由 GDI+ 内部管理,通常不需要手动delete。
- 这是最关键的一点! 任何通过
-
命名空间:
- GDI+ 的所有类和函数都在
Gdiplus命名空间下,在 C++ 中,你可以用using namespace Gdiplus;来简化,但在 C 语言中,你必须每次都完整地写出Gdiplus::,Gdiplus::Graphics。
- GDI+ 的所有类和函数都在
-
字符串处理:
- GDI+ 的字符串函数(如
DrawString)和文件路径(如Image构造函数)通常使用宽字符(wchar_t),即L"..."格式的字符串,在处理 Windows 路径时,确保使用正确的宽字符 API。
- GDI+ 的字符串函数(如
在 C 语言中使用 GDI+ 是完全可行的,并且功能非常强大,核心在于:
- 正确初始化和终止:
GdiplusStartup和GdiplusShutdown必须成对出现。 - 正确管理对象生命周期:创建的
Pen,Brush,Font,Image等对象,用完后必须delete。 - 理解
Graphics对象的作用:它是所有绘图操作的载体,由HDC创建。 - 处理宽字符:注意字符串和路径的格式。
虽然 C++ 的封装让 GDI+ 的使用更安全(RAII 可以自动管理对象生命周期),但在 C 语言中,只要严格遵守上述规则,你依然可以高效、稳定地利用 GDI+ 的强大功能来创建复杂的图形界面。
