☰
EasyX+Direct2D混合渲染:Dev-C++工程配置与鼠标交互
2026/10/27 3:33:34 网站建设 项目流程

先说结论:Dev-C++ 配 EasyX 再叠一层 Direct2D,这件事本身不复杂,复杂的是"谁负责画哪一层"没想清楚。我最早是在一个校园小工具项目里碰到这个需求——用 EasyX 搭界面快得像作弊,initgraph 一开、putimage 一贴,鼠标事件也顺手;但一画到几百个带抗锯齿的圆角矩形和曲线,GDI 立刻原形毕露,锯齿边缘和文字毛边让人没法交差。Direct2D 恰好补上这块短板,几何抗锯齿、渐变、文字排版都是一流。问题在于:这两个东西天生不是一路人,一个走 GDI 的窗口 DC 和内存位图,一个走自己的渲染目标和设备上下文,硬凑到一起就是闪烁、偏移、黑边三件套。

这篇东西写给已经会用 EasyX 画点基本图形、也被 GDI 的抗锯齿折磨过的人,同时给刚接触 Direct2D、还在 Dev-C++ 里折腾链接参数的同学一份能直接抄的工程骨架。全文围绕"怎么让 Direct2D 的输出和 EasyX 的画布和平共处"这条主线,把方案选型的理由、编译期和运行期的坑、鼠标驱动图元的完整交互流程,一层层拆开讲清楚。

1. 先把路线想清楚:三种接法的取舍

动手写第一行代码之前,我最建议你花十分钟把可选路线过一遍。很多人卡在"代码写完了但屏幕一片黑",根因往往不是语法错,而是选了一条在 EasyX 环境下根本走不通的路。

1.1 路线一:HwndRenderTarget 直接绑窗口

这是绝大多数 Direct2D 入门教程的写法:创建ID2D1HwndRenderTarget,把 EasyX 用GetHWnd()拿到的窗口句柄喂进去,然后BeginDraw/EndDraw一气呵成。这条路线看起来最省事,实际上在混合场景里最容易出事。

原因在于ID2D1HwndRenderTarget底层走的是 DXGI 交换链和呈现(Present)机制,而 EasyX 的putimage、FlushBatchDraw走的是 GDI 的 BitBlt。两套机制对同一块窗口客户区的"所有权"理解是不一样的:交换链呈现时会把整块区域重新提交,GDI 画上去的内容被覆盖;反过来 EasyX 双缓冲刷屏时,又会把 Direct2D 刚呈现的像素整片抹掉。表现出来就是画面疯狂闪烁,或者其中一层永远看不见。

更麻烦的是分层窗口和桌面合成的影响,WS_EX_LAYERED一旦加上,UpdateLayeredWindow和 DXGI 的配合又是一堆额外规则。结论很明确:只要 EasyX 还要在窗口上画东西,就不要让 Direct2D 也直接往窗口上呈现。

1.2 路线二:DC 渲染目标绑内存位图

ID2D1DCRenderTarget是这条路线的核心。它允许你把 Direct2D 的输出绑定到任意一个设备上下文上,包括内存 DC。而 EasyX 的IMAGE对象内部恰好就是一张 DIB 段,GetImageHDC(&img)能直接拿到它对应的内存 DC。

这意味着什么?意味着 Direct2D 画出来的像素,和 EasyX 画出来的像素,落在同一块内存里。既然是同一块内存,就不存在谁覆盖谁的问题,天然合成、天然一致、天然没有闪烁。画完之后再用putimage一次性贴到窗口,整个流程干净得像切豆腐。

我后来所有类似的混合需求都走这条路。它的代价是需要自己管理BindDC的时机,IMAGE 尺寸一变就得重新绑定;好处是彻底规避了呈现模型的冲突,调试起来也直观——IMAGE里的内容可以直接存成图片看。

1.3 路线三:独立图层加 AlphaBlend 叠加

还有一种是折中方案:准备两张IMAGE,一张当背景由 EasyX 画,另一张当"透明图层"由 Direct2D 画,最后用AlphaBlend把图层叠到背景上。好处是层次清晰,前景可以整体做透明度动画;坏处是 alpha 通道的处理会变成新的麻烦源头。

Direct2D 渲染到DXGI_FORMAT_B8G8R8A8_UNORM加D2D1_ALPHA_MODE_PREMULTIPLIED时,输出的是预乘 alpha,而 EasyX 的AlphaBlend期望的是普通 alpha。搞错了就是边缘发黑或者整体偏暗。这个方案我会在第 6 节展开,作为进阶用法,但主线我还是推荐路线二,因为它需要的小心脏最少。

1.4 一条容易被忽略的硬约束

不管走哪条路线,有一条约束必须提前确认:BindDC绑定的 DC,其位图必须是 DIB 段(CreateDIBSection 创建的那种),而不能是普通的兼容位图。EasyX 的IMAGE满足这个前提,这是路线二能成立的根本原因。如果你试过绑定一个CreateCompatibleBitmap出来的 DC,会拿到D2DERR_WRONG_STATE或者干脆什么都画不出来。

另外,IMAGE的像素格式是 32 位 BGRA(带一个几乎不用的 alpha 字节),所以像素格式必须填DXGI_FORMAT_B8G8R8A8_UNORM。填成R8G8B8A8会出现红蓝互换,画面变成诡异的青紫色——这个错误我见过不止一次,症状特别典型。

2. Dev-C++ 这边的工程配置

Dev-C++ 是个轻量到有点简陋的 IDE,好处是绿色、启动快、考试和教学环境里到处都是;坏处是它对第三方库的管理基本靠手工。EasyX 加 Direct2D 这种组合,配置环节翻车的概率比写代码还高。

2.1 编译器版本和库架构必须对齐

EasyX 官方安装器主要面向另一套开发环境,在 Dev-C++ 里你需要的是 MinGW 版本的libeasyx.a和配套的graphics.h。这里第一个大坑就是架构匹配:Dev-C++ 默认可能编 32 位程序(TDM-GCC 4.9.2 那套),而你能下载到的libeasyx.a有可能是 64 位的。混用之后的报错长这样:

skipping incompatible .../libeasyx.a when searching for -leasyx undefined reference to `initgraph'

看到skipping incompatible就别再折腾代码了,直接换库。检查办法很简单,在命令行跑一下objdump -f libeasyx.a | head,看file format那一行是pei-i386还是pei-x86-64,前者配 32 位编译器,后者配 64 位。

我的建议是:新开的工程统一用 64 位的 MinGW-w64 编译器。Direct2D 和 DWrite 在 64 位下头文件更完整,而且大画布(比如 1920×1080 的 IMAGE,一张就是 8MB)在 32 位进程里堆空间更容易吃紧。

2.2 头文件和链接参数的完整清单

假设你的目录结构是这样的:

D:\devwork\easyx-d2d\ ├── include\ │ ├── graphics.h │ └── easyx.h ├── lib\ │ └── libeasyx.a └── main.cpp

在 Dev-C++ 里打开「项目 → 项目属性」,两个地方要改。

包含目录,加一行D:\devwork\easyx-d2d\include。库目录,加一行D:\devwork\easyx-d2d\lib。

然后到「参数 → 链接器」,把下面这一串贴进去:

-leasyx -ld2d1 -ldwrite -lgdi32 -lole32 -luuid -luser32 -lshlwapi

逐个说明一下为什么要这些:

参数作用不加会怎样
-leasyxEasyX 本体所有initgraph、IMAGE相关符号全部未定义
-ld2d1Direct2DD2D1CreateFactory未定义
-ldwriteDirectWrite 文字DWriteCreateFactory未定义
-lgdi32GDI 基础BitBlt、CreateDIBSection未定义
-lole32COM 基础CoInitialize相关未定义
-luuidGUID 常量部分 IID 符号缺失
-luser32窗口与消息GetClientRect未定义

-lshlwapi不是必须的,但如果你后面想加"导出为 PNG"的功能会用到路径处理函数,先加上省得再回来改。

注意:链接顺序在 GCC 下是有意义的,被依赖的库要放在后面。-leasyx放最前面是因为它可能引用 gdi32 的符号,反过来的话链接器扫一遍就过去了。

2.3 一个能跑起来的最小骨架

配置完先别急着画 Direct2D,跑通这个骨架确认环境和库都没问题:

#include <graphics.h> #include <stdio.h> int main() { initgraph(800, 600); setbkcolor(WHITE); cleardevice(); IMAGE img(320, 240); SetWorkingImage(&img); setbkcolor(RGB(230, 240, 255)); cleardevice(); SetWorkingImage(NULL); putimage(100, 100, &img); outtextxy(100, 60, L"easyx ok"); FlushBatchDraw(); _getch(); closegraph(); return 0; }

这段能出画面,说明libeasyx.a的架构、头文件路径、链接参数三项全对。在确认这一步之前,不要往里面加任何 Direct2D 代码,否则出了错你根本分不清是库的问题还是 D2D 的问题。这是我踩过好几次才养成的习惯。

顺带说一个 Dev-C++ 的小细节:它的编译输出窗口经常刷得太快,错误信息一闪而过。建议在「工具 → 环境选项」里把"编译时隐藏所有警告"关掉,或者干脆在项目目录下开个命令行用mingw32-make手动编,报错看得更清楚。

3. 核心实现:把 Direct2D 画进 EasyX 的画布

这一节是全文的重头戏。整套流程可以概括成四步:建工厂 → 建 DC 渲染目标 → 绑定到IMAGE的 HDC → 每帧 BeginDraw/EndDraw。

3.1 工厂、渲染目标与 BindDC 的完整初始化

先看代码,再逐行解释为什么这么写。

#include <graphics.h> #include <d2d1.h> #include <dwrite.h> #include <stdio.h> #pragma comment(lib, "d2d1.lib") // 仅部分环境有效,Dev-C++ 下请用链接参数 static ID2D1Factory* g_pFactory = NULL; static ID2D1DCRenderTarget* g_pRT = NULL; static ID2D1SolidColorBrush* g_pBrush = NULL; bool D2D_Init(IMAGE& canvas) { // 1. 创建工厂,单线程就够,别用 MULTI_THREADED HRESULT hr = D2D1CreateFactory( D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory), NULL, (void**)&g_pFactory); if (FAILED(hr)) { printf("D2D1CreateFactory failed: 0x%08X\n", hr); return false; } // 2. 手工填渲染目标属性,不依赖 d2d1helper.h D2D1_RENDER_TARGET_PROPERTIES props; ZeroMemory(&props, sizeof(props)); props.type = D2D1_RENDER_TARGET_TYPE_DEFAULT; props.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM; props.pixelFormat.alphaMode = D2D1_ALPHA_MODE_IGNORE; props.dpiX = 96.0f; props.dpiY = 96.0f; props.usage = D2D1_RENDER_TARGET_USAGE_NONE; props.minLevel = D2D1_FEATURE_LEVEL_DEFAULT; hr = g_pFactory->CreateDCRenderTarget(&props, &g_pRT); if (FAILED(hr)) { printf("CreateDCRenderTarget failed: 0x%08X\n", hr); return false; } // 3. 绑定到 IMAGE 的内存 DC HDC hdc = GetImageHDC(&canvas); RECT rc = { 0, 0, canvas.getwidth(), canvas.getheight() }; hr = g_pRT->BindDC(hdc, &rc); if (FAILED(hr)) { printf("BindDC failed: 0x%08X\n", hr); return false; } // 4. 抗锯齿和默认画刷 g_pRT->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); g_pRT->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE); D2D1_COLOR_F c = { 0.1f, 0.1f, 0.12f, 1.0f }; g_pRT->CreateSolidColorBrush(c, &g_pBrush); return true; }

几个关键点值得展开。

为什么用D2D1_FACTORY_TYPE_SINGLE_THREADED。多线程工厂会有额外的加锁开销,而且要求所有渲染目标都遵守线程规则。我们的场景是单线程消息循环驱动的,单线程工厂足够,性能还更好。真正需要多线程渲染(比如后台生成缩略图)的时候再另说。

为什么手写结构体而不用D2D1::RenderTargetProperties()这类辅助函数。这些 helper 定义在d2d1helper.h里,MinGW 各版本对它的支持并不一致,有些精简发行版根本没带这个文件,一#include就报找不到。手写ZeroMemory加逐字段赋值虽然啰嗦,但兼容性拉满,而且字段含义一目了然。实测下来,这个选择能省掉至少一半的"编译不过"求助。

__uuidof在 GCC 下的表现。MinGW-w64 通过__CRT_UUID_DECL宏实现了__uuidof,绝大多数情况可用。如果你用的编译器比较老,报__uuidof was not declared,就改成显式传 IID:

static const IID IID_ID2D1Factory_Local = { 0x06152247, 0x6f50, 0x465a, { 0x92, 0x45, 0x11, 0x8b, 0xfd, 0x3b, 0x60, 0x07 } }; hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, IID_ID2D1Factory_Local, NULL, (void**)&g_pFactory);

这段 IID 我在多个环境上验证过,可以直接抄。

BindDC里的 RECT 一定要和 IMAGE 尺寸严格一致。传{0,0,800,600}但 IMAGE 是 1024×640,画出来的内容会被裁掉右下角;反过来传大了,Direct2D 会往 IMAGE 缓冲区之外写,轻则花屏重则崩溃。稳妥写法就是上面那样用getwidth()/getheight()取实际值。

3.2 DPI 设置:一个能毁掉整晚的细节

props.dpiX和props.dpiY这两个字段,我建议你永远显式设成 96。原因是 Direct2D 的坐标系用的是 DIP(设备无关像素),而 DIP 到物理像素的换算率就是 DPI/96。如果填 0,Direct2D 会自动取系统的桌面 DPI。

在缩放 125% 或者 150% 的显示器上,这意味着你写的D2D1_RECT_F{0, 0, 100, 100}实际会画成 150×150 像素。画面整体放大一圈,和旁边 EasyX 画的图形完全对不上——因为 EasyX 的 GDI 绘制用的是物理像素坐标,不受 DPI 缩放影响。

我第一次遇到这个现象时愣了半天,明明代码里写的是同一个坐标,怎么就错位了。后来发现 EasyX 画的矩形在左上角,Direct2D 画的一模一样代码却在右下角,才反应过来是 DPI 的锅。显式设 96 之后,Direct2D 的 1 个单位就等于 1 个物理像素,和 GDI 完全对齐。

如果哪天你确实需要高 DPI 适配,正确做法是监听WM_DPICHANGED,按新 DPI 重算画布尺寸再重新BindDC,而不是让 D2D 自己去猜。

3.3 帧循环、绘制顺序与资源生命周期

完整的每帧流程是这样:

void RenderFrame(IMAGE& canvas) { // 第一层:EasyX 负责背景和静态 UI SetWorkingImage(&canvas); setbkcolor(RGB(245, 247, 250)); cleardevice(); setlinecolor(RGB(220, 226, 235)); for (int x = 0; x < canvas.getwidth(); x += 40) line(x, 0, x, canvas.getheight()); for (int y = 0; y < canvas.getheight(); y += 40) line(0, y, canvas.getwidth(), y); // 第二层:Direct2D 负责前景图元 g_pRT->BeginDraw(); D2D1_MATRIX_3X2_F mat = { 1, 0, 0, 1, 0, 0 }; g_pRT->SetTransform(mat); D2D1_COLOR_F fill = { 0.20f, 0.55f, 0.95f, 1.0f }; g_pBrush->SetColor(fill); D2D1_ELLIPSE circle = { { 400.0f, 300.0f }, 80.0f, 80.0f }; g_pRT->FillEllipse(circle, g_pBrush); D2D1_ROUNDED_RECT rr = { { 600.0f, 200.0f, 820.0f, 340.0f }, 12.0f, 12.0f }; g_pRT->DrawRoundedRectangle(rr, g_pBrush, 2.0f); HRESULT hr = g_pRT->EndDraw(); if (hr == D2DERR_RECREATE_TARGET) { // 设备丢失,重建渲染目标并重新 BindDC RebuildRenderTarget(canvas); } // 第三层:一次性上屏 SetWorkingImage(NULL); putimage(0, 0, &canvas); }

这里有三件事必须说清楚。

绘制顺序就是层次顺序。因为三者操作的是同一块像素内存,后画的必然盖住先画的。EasyX 的网格铺底在前,Direct2D 的圆和圆角矩形在后,所以图元浮在网格之上。这个模型非常好理解,也好维护——想让某个元素上浮,就把它挪到后面画。

SetWorkingImage(&canvas)和SetWorkingImage(NULL)必须成对出现。忘了复位,后面的putimage会画到IMAGE自己身上,屏幕上啥也看不见,而这个错误编译器不会提醒你。我的习惯是把它俩写在同一个函数的开头和结尾,中间不跨越任何函数调用。

EndDraw的返回值一定要检查。返回D2DERR_RECREATE_TARGET说明底层设备丢了,常见于切换显卡、系统休眠唤醒、远程桌面连接。不处理的话画面会卡死在最后一帧,鼠标还能动但画面没反应,非常迷惑。处理办法是释放所有渲染相关的资源(包括画刷、渐变、几何),重新CreateDCRenderTarget+BindDC,再把画刷重新建一遍。

关于资源释放,用一个统一函数收尾,别在main里到处Release:

void D2D_Cleanup() { if (g_pBrush) { g_pBrush->Release(); g_pBrush = NULL; } if (g_pRT) { g_pRT->Release(); g_pRT = NULL; } if (g_pFactory) { g_pFactory->Release(); g_pFactory = NULL; } }

4. 交互实现:鼠标如何驱动 Direct2D 图元

到这一步画面已经能出来了,但还只是个静态图。真正的"交互"要解决三件事:点击命中、拖拽跟随、参数实时变化。

4.1 数据与渲染分离的图元结构

我不建议把图形参数直接写在绘制函数里。稍微复杂一点的交互,都需要"图元数据"和"渲染代码"分离,否则每加一个元素就要改一遍渲染逻辑,改到最后自己都看不懂。

struct Node { float x, y, r; // 位置与半径 float vx, vy; // 速度,用于动画 bool dragging; // 是否正在被拖 float ox, oy; // 拖拽时的偏移量 D2D1_COLOR_F color; }; #define NODE_COUNT 8 static Node g_nodes[NODE_COUNT];

初始化时把它们撒在画布上,颜色用 HSL 思路算一下,视觉上会好看很多:

void Nodes_Init() { for (int i = 0; i < NODE_COUNT; ++i) { g_nodes[i].x = 120.0f + (i % 4) * 200.0f; g_nodes[i].y = 160.0f + (i / 4) * 220.0f; g_nodes[i].r = 36.0f + i * 3.0f; g_nodes[i].vx = g_nodes[i].vy = 0.0f; g_nodes[i].dragging = false; g_nodes[i].color.r = 0.25f + i * 0.06f; g_nodes[i].color.g = 0.55f - i * 0.03f; g_nodes[i].color.b = 0.85f; g_nodes[i].color.a = 1.0f; } }

渲染时遍历数组,每个节点画一个圆再加一圈描边:

void Nodes_Render() { for (int i = 0; i < NODE_COUNT; ++i) { D2D1_ELLIPSE e = { { g_nodes[i].x, g_nodes[i].y }, g_nodes[i].r, g_nodes[i].r }; g_pBrush->SetColor(g_nodes[i].color); g_pRT->FillEllipse(e, g_pBrush); } }

这种"数据结构 + 遍历渲染"的模式,扩到几十个元素也不乱。而且后面加碰撞、加连线、加缓动动画,都只是在这套数据上加逻辑,渲染代码基本不动。

4.2 命中测试与拖拽的完整逻辑

EasyX 的鼠标消息处理在新版本里用peekmessage拿ExMessage,老版本用MouseHit/GetMouseMsg。我这里用新版写法,老版本的同学把外层循环换掉即可。

void HandleInput() { ExMessage msg; while (peekmessage(&msg, EX_MOUSE | EX_KEY)) { if (msg.message == WM_KEYDOWN && msg.vkcode == VK_ESCAPE) { g_running = false; } else if (msg.message == WM_LBUTTONDOWN) { // 从后往前遍历,保证上层元素优先命中 for (int i = NODE_COUNT - 1; i >= 0; --i) { float dx = msg.x - g_nodes[i].x; float dy = msg.y - g_nodes[i].y; if (dx * dx + dy * dy <= g_nodes[i].r * g_nodes[i].r) { g_nodes[i].dragging = true; g_nodes[i].ox = dx; g_nodes[i].oy = dy; g_active = i; break; } } } else if (msg.message == WM_MOUSEMOVE) { if (g_active >= 0 && g_nodes[g_active].dragging) { g_nodes[g_active].x = msg.x - g_nodes[g_active].ox; g_nodes[g_active].y = msg.y - g_nodes[g_active].oy; } } else if (msg.message == WM_LBUTTONUP) { if (g_active >= 0) g_nodes[g_active].dragging = false; g_active = -1; } else if (msg.message == WM_MOUSEWHEEL) { if (g_active >= 0) { g_nodes[g_active].r += (msg.wheel > 0 ? 4.0f : -4.0f); if (g_nodes[g_active].r < 8.0f) g_nodes[g_active].r = 8.0f; if (g_nodes[g_active].r > 160.0f) g_nodes[g_active].r = 160.0f; } } } }

几个地方值得说说。

命中测试用的是距离平方,不是sqrt再比较。差一点点性能,但更重要的是避免了浮点开方的精度问题。圆周判断用dx*dx + dy*dy <= r*r,这是标准做法,几万次调用也不会有性能问题。

倒序遍历是为了"上层优先"。因为渲染时是正序画,后画的在上面,所以点击时要从最后画的开始找。这个细节不做,点重叠区域时会选中底下的元素,手感非常怪。

记录偏移量ox/oy而不是直接把圆心设到鼠标位置。这样拖拽时元素不会"跳"到鼠标中心,而是保持按下瞬间的相对位置,手感自然得多。这个是小细节,但体验差别巨大。

滚轮缩放要加边界钳制。半径减到 0 会让圆消失,减成负数 Direct2D 会直接报参数错误返回E_INVALIDARG。设置一个 8 到 160 的范围,既安全又合理。

4.3 渐变、路径与文字

纯色圆点看久了太单调,加上渐变和文字,画面的档次立刻不一样。

渐变需要ID2D1GradientStopCollection和ID2D1LinearGradientBrush配合:

void SetupGradient() { D2D1_GRADIENT_STOP stops[2]; stops[0].position = 0.0f; stops[0].color = D2D1_COLOR_F{ 1.00f, 0.45f, 0.20f, 1.0f }; stops[1].position = 1.0f; stops[1].color = D2D1_COLOR_F{ 0.20f, 0.55f, 1.00f, 1.0f }; ID2D1GradientStopCollection* pStops = NULL; g_pRT->CreateGradientStopCollection( stops, 2, D2D1_GAMMA_2_2, D2D1_EXTEND_MODE_CLAMP, &pStops); D2D1_LINEAR_GRADIENT_BRUSH_PROPERTIES lgp; lgp.startPoint = D2D1_POINT_2F{ 0.0f, 0.0f }; lgp.endPoint = D2D1_POINT_2F{ 0.0f, 1.0f }; g_pRT->CreateLinearGradientBrush(&lgp, pStops, NULL, &g_pGradBrush); pStops->Release(); }

D2D1_GAMMA_2_2是标准的 sRGB 伽马,出来的过渡比较自然;D2D1_EXTEND_MODE_CLAMP表示超出范围时钳制到端点颜色,而不是平铺。渐变画刷的起止点用的是相对坐标空间,所以画不同元素时要配合SetTransform移动坐标系,否则所有元素共用一套渐变方向,看着会很死板。

文字部分用 DirectWrite,抗锯齿效果比 GDI 的outtextxy好一个档次,尤其小字号:

// 初始化 DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), (IUnknown**)&g_pDWrite); g_pDWrite->CreateTextFormat( L"Consolas", NULL, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL, DWRITE_FONT_STRETCH_NORMAL, 16.0f, L"zh-cn", &g_pTextFormat); g_pTextFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING); g_pTextFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR); // 每帧绘制 wchar_t buf[128]; swprintf(buf, 128, L"node %d r=%.0f pos=(%.0f, %.0f)", g_active, g_nodes[g_active].r, g_nodes[g_active].x, g_nodes[g_active].y); D2D1_RECT_F layout = { 24.0f, 24.0f, 620.0f, 56.0f }; g_pRT->DrawTextW(buf, (UINT32)wcslen(buf), g_pTextFormat, layout, g_pBrush, D2D1_DRAW_TEXT_OPTIONS_NONE, DWRITE_MEASURING_MODE_NATURAL);

DrawTextW在 MinGW 下需要宽字符缓冲区,swprintf记得带上长度参数,不然栈溢出风险不小。

5. 常见问题与排查速查表

这一节是我把遇到过的所有问题整理成清单,按编译期和运行期分开。遇到症状直接对着查,能省掉大量试错时间。

5.1 编译期和链接期问题

报错信息根因处理办法
d2d1.h: No such file or directory编译器太老或安装不全换 MinGW-w64,或手动补头文件
undefined reference to D2D1CreateFactory没加-ld2d1链接参数补上
undefined reference to DWriteCreateFactory没加-ldwrite链接参数补上
skipping incompatible libeasyx.a库架构和编译器不匹配换对应位数的libeasyx.a
__uuidof was not declared编译器不支持该扩展改用显式 IID 常量
d2d1helper.h: No such file精简发行版缺 helper全部手写结构体,别 include
g_pRT->FillEllipse报重载歧义参数

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询