做计算机图形学实验,最卡人的往往不是数学变换,而是环境搭建。很多同学打开VS2022,想建一个MFC程序,结果发现“新建项目”里根本没有MFC应用这个模板,折腾半天连窗口都弹不出来。这篇博文,我就用VS2022把MFC程序从创建到画出三维线框模型的完整过程,一步一步掰开讲清楚。适合刚上图形学课、需要在Windows环境下交实验作业的同学,也适合想在MFC里快速验证算法原型的开发者。
先说清楚一件事:MFC虽然年纪不小,但微软官方到现在还在维护,Visual Studio 2022里依然是第一等公民。你做图形学实验,用MFC有个天然优势——文档/视图架构把“数据”和“显示”拆开了,视图类里的OnDraw函数专门负责绘图,你只需要关心“往哪画、画什么”,不用自己管理窗口消息循环。这种体验,比写裸Win32 API舒服太多。但VS2022的向导和十年前的老教程差异很大,踩坑点不少,所以我把完整流程拆成六个环节,从装组件到跑出旋转立方体,一次讲完。
1. 环境准备:把VS2022的MFC组件补齐
1.1 安装时最容易漏掉的那一项
先说安装。如果你已经装好了VS2022,但新建项目对话框里搜不到MFC模板,原因只有一个:安装时没有勾选MFC相关组件。注意,不是勾了“使用C++的桌面开发”就万事大吉,这个工作负载默认只带Windows SDK和C++编译器,MFC库和模板是独立组件,需要单独勾。
打开Visual Studio Installer,点击“修改”,在左侧“工作负载”里勾选“使用C++的桌面开发”。这一步只是基础,接着看右侧“安装详细信息”,展开“使用C++的桌面开发”,找到“适用于最新v143生成工具的C++ MFC(x86和x64)”,打勾。如果以后可能用到旧版工具集,也可以顺手把“适用于C++的MFC(x86和x64)”那几项都勾上,不占多少空间。确认后点击右下角“修改”,等安装完成。
这里我要多说一句:v143是VS2022的默认工具集版本号,你如果装了VS2019的v142工具集,MFC选项里也可能出现v142字样。一般来说,勾最新版即可,因为VS2022默认用v143编译。如果你的项目之后要迁移到别的环境,再考虑补充其他版本。
1.2 验证组件是否安装成功
安装完成后,打开VS2022,点击“创建新项目”,在模板搜索框里输入“MFC”,如果能出现“MFC应用”或“MFC DLL”模板,说明组件装好了。
如果还是搜不到,重启VS2022再试。依旧不行的话,回到Visual Studio Installer,确认刚才勾选的组件确实处于勾选状态,有些时候安装程序会因网络等问题“悄悄”失败,重新点击“修改”,保持勾选,再点“修改”触发一次修复即可。
注意:MFC组件体积不小,安装需要几分钟。如果网络条件不稳定,建议用“全部”下载方式,避免下到一半中断导致组件损坏。
2. 新建MFC项目:向导选项要选对
2.1 从模板到项目创建
模板出现后,点击“MFC应用”,进入配置界面。项目名称我用“GraphDemo”举例,不建议用中文或带空格的名称,因为MFC生成的类名和文件名会从项目名派生,中文名虽然能编译,但会出现文件路径、头文件引用、调试符号等一系列别扭问题,我有同事踩过这个坑。
位置选一个你熟悉的目录,解决方案名保持默认即可,也可以勾选“将解决方案和项目放在同一目录中”,方便管理。点击“创建”后,会弹出MFC应用程序向导。
2.2 向导里的关键选项:为什么图形学要选单文档
向导左侧有“应用程序类型”“复合文档支持”“文档模板字符串”“数据库支持”“用户界面功能”“高级功能”“生成的类”这几个标签页。对于图形学实验,核心配置如下:
应用程序类型选择“单个文档”。这是MFC的SDI(Single Document Interface)模式,主窗口内有一个视图区域,正好用来做画布。它和“多个文档”(MDI)的区别在于,SDI同时只维护一份文档,代码简单、聚焦,图形学作业基本不需要多标签页,SDI足够。
旁边的“项目样式”选择“MFC标准”,不要选“Windows资源管理器”,那个风格会多出一堆树形视图和列表视图的样板代码,和画图无关。还要勾选“使用Unicode库”吗?我先说明,向导默认生成Unicode版本,但如果你参考的老教材里的代码是char*写法,编译时会报一堆类型不匹配。我的建议是:项目属性里把“字符集”改成“使用多字节字符集”,这样和老代码兼容性最好。如果坚持Unicode,字符和字符串要用_T()宏包起来,比如_T("hello"),代码稍麻烦但更现代。这块后面单独讲。
“复合文档支持”选“无”,图形学用不到ActiveX文档容器。“数据库支持”选“无”,除非你要做数据可视化且数据来自数据库,否则不要引入ODBC支持,徒增编译时间。“用户界面功能”保持默认勾选,用系统菜单和工具栏即可。“高级功能”里的“ActiveX控件”和“自动化”都可以不勾,减小生成代码量。
2.3 生成的类设置
左侧切到“生成的类”,这里能看到向导将要生成的类。基类选择“CView”,必须确认这一点。向导默认可能选“CView”,但有些人会误选“CEditView”或“CFormView”,一旦选错,视图类就变成了文本框控件或表单视图,OnDraw的绘图逻辑就完全不对了。生成的文件名保持默认即可。
继续往下,到“文档模板字符串”页面,这里不用改,默认的“GraphDemo”会作为窗口标题。点击“完成”,项目就创建好了。
2.4 字符集和多字节字符集的选择
为什么单独拎出来讲?因为VS2022的MFC向导默认字符集是Unicode,而大量图形学教材、老博客里的示例代码是ANSI风格,比如CString str; str = "hello";在Unicode下会报错C2440,类型不兼容。解决方案有两种:
第一种,在“解决方案资源管理器”里右键项目名,选择“属性”,左侧“配置属性”→“常规”→“字符集”,改为“使用多字节字符集”。这样老代码里的char*字符串可以直接赋值给CString,出错少。
第二种,保持Unicode,代码里所有字符串都写成_T("hello"),printf系列的格式化也要用_tprintf或wprintf。这更符合现代Windows开发习惯,但对于图形学实验来说,没有必要增加学习负担。我个人的实操经验是:先改成多字节字符集,跑通实验,重心放在图形算法上。等以后真要做跨平台或国际化项目,再切换到Unicode也不迟。
3. 读一遍生成的工程:理解框架再动手
3.1 四大核心类:谁负责画图
创建完成后,解决方案资源管理器里会生成一组文件。我梳理一下最关键的四个类:
CGraphDemoApp:程序入口,负责初始化实例,相当于main函数的前奏。CMainFrame:主窗口框架,管理菜单、工具栏、状态栏,但不负责画图。CGraphDemoDoc:文档类,保存数据模型。比如画布上的曲线方程参数、多边形顶点坐标,都应该放在这里。CGraphDemoView:视图类,核心中的核心。所有的图形绘制代码都写在它里面,尤其是它的OnDraw(CDC* pDC)成员函数。
理解它们之间的协作方式:用户在窗口上输入(比如鼠标点击),视图类接收消息,视图通过GetDocument()拿到文档类指针,读取数据,然后调用Invalidate()让框架发出WM_PAINT消息,框架自动调用OnDraw,由OnDraw把数据显示在画布上。这就是“文档-视图”架构的核心逻辑。
3.2 pch.h和stdafx.h的版本差异
打开GraphDemoView.cpp,你会看到第一行是#include "pch.h"。很多照着旧教程抄代码的同学,会把这一行改成#include "stdafx.h",编译直接报错“无法打开包括文件: stdafx.h”。原因很简单:VS2022的MFC项目默认使用“预编译头”机制,头文件文件名是pch.h,而不是旧版的stdafx.h。
如果你复制了老代码且包含了stdafx.h,有两种处理方式:要么删掉老代码里的#include "stdafx.h",统一换成#include "pch.h";要么在项目属性里把预编译头关闭(C/C++ → 预编译头 → 不使用),但我不建议关闭,因为MFC框架内部大量使用了预编译头以免头文件重复编译,关闭后编译速度会明显下降。最省事的办法就是保留pch.h,并确保它在每个cpp文件的最顶层第一位。
3.3 第一次编译运行
按下快捷键F5,IDE会编译并启动程序。如果一切正常,你会看到一个窗口,菜单栏和工具栏齐全,中间是白色的空白区域,这个区域就是CGraphDemoView的客户区,也是我们接下来画图的地方。
如果第一次编译就报错,大概率是MFC库没正确链接或组件缺失。常见错误提示:
无法打开包括文件: afxwin.h:说明MFC头文件目录没配置。检查是否安装了MFC组件,以及项目“包含目录”是否包含$(VC_IncludePath)。LNK2019 无法解析的外部符号 WinMain:SDI程序入口不对。检查“链接器”→“系统”→“子系统”是否为“Windows (/SUBSYSTEM:WINDOWS)”。LINK1120 无法解析的外部命令:多数情况是MFC静态库缺失,原因是安装时只装了桌面开发工作负载,没装MFC库组件,回到第1节补装。
我见过不少同学在这里卡了整整半天,其实解决办法非常简单:装组件,重新打开项目,编译就过了。
4. 在OnDraw里写出第一个图形学成果
4.1 OnDraw的执行机制
OnDraw(CDC* pDC)是CView类里的虚函数,窗口每次收到WM_PAINT消息时,框架会准备好一个CDC设备上下文对象(也就是画笔、画刷、字体等绘图资源的“工具箱”),然后自动调用它。这个pDC指针指向当前窗口客户区的逻辑坐标系统,你不需要手动释放它,框架会处理。
注意一点:千万不要在OnDraw里调用GetDC()或ReleaseDC()。这里用的pDC是框架已经选好的,你再额外取设备上下文,不仅可能覆盖框架的裁剪区域设置,还会导致资源管理混乱。
在你的GraphDemoView.cpp里,OnDraw函数原本是空的(只有注释和获取文档指针代码),接下来我们往里填内容。
4.2 绘制第一个三角形和填充矩形
我们先画一个简单的等腰三角形,顺便把画笔和画刷的用法展示出来:
void CGraphDemoView::OnDraw(CDC* pDC) { CGraphDemoDoc* pDoc = GetDocument(); if (pDoc == NULL) return; // 设置背景为白色 CRect clientRect; GetClientRect(&clientRect); pDC->FillSolidRect(clientRect, RGB(255, 255, 255)); // 创建蓝色画笔 CPen pen(PS_SOLID, 2, RGB(0, 0, 255)); CPen* pOldPen = pDC->SelectObject(&pen); // 绘制三角形 pDC->MoveTo(200, 100); pDC->LineTo(300, 300); pDC->LineTo(100, 300); pDC->LineTo(200, 100); // 恢复旧画笔 pDC->SelectObject(pOldPen); }运行之后,窗口中央会出现一个蓝色边框的三角形。这里的SelectObject是GDI对象选择的标配操作:先选中新对象,结束时再选回旧对象,否则GDI对象会持续泄漏,程序跑久了会出现花屏或绘图失效。
接着演示填充。创建一个画刷(CBrush),选择它,然后调用Rectangle或Ellipse:
CBrush brush(RGB(255, 0, 0)); CBrush* pOldBrush = pDC->SelectObject(&brush); pDC->Rectangle(120, 120, 280, 280); pDC->SelectObject(pOldBrush);画刷填充内部颜色,画笔描绘边框。两者经常搭配使用,你还会在之后接触自定义虚线画笔CPen(PS_DASH, 1, color)和透明画刷CBrush(NULL)。
4.3 进阶:画一个旋转变换的三维线框立方体
图形学离不开坐标变换。我在实验中最常用到的例子是三维线框立方体,它把三维顶点、旋转矩阵、透视投影全部串起来,代码量不大但非常锻炼思维。
在视图类中添加三个辅助函数和成员变量。首先定义三维点和二维点结构,简单起见直接在View类的头文件里加:
struct Point3D { double x; double y; double z; Point3D(double xi, double yi, double zi) { x = xi; y = yi; z = zi; } };在CGraphDemoView类声明中添加成员函数:
Point3D RotateY(Point3D p, double angle); CPoint Project(Point3D p, CRect rc);在cpp文件末尾实现它们:
Point3D CGraphDemoView::RotateY(Point3D p, double angle) { double rad = angle * 3.14159265358979 / 180.0; Point3D result(0, 0, 0); result.x = p.x * cos(rad) + p.z * sin(rad); result.y = p.y; result.z = -p.x * sin(rad) + p.z * cos(rad); return result; } CPoint CGraphDemoView::Project(Point3D p, CRect rc) { // 简单透视投影:把立方体中心放在窗口中心,z方向向屏幕内部延伸 double perspective = 300.0; double factor = perspective / (perspective + p.z); CPoint screenPt; screenPt.x = rc.CenterPoint().x + (int)(p.x * factor); screenPt.y = rc.CenterPoint().y - (int)(p.y * factor); return screenPt; }这里的投影逻辑是:视点位于z轴正向远处,物体在视点前方,factor = 视距 / (视距 + z)。当z为正时,factor小于1,物体更小,产生近大远小的透视效果。这正是图形学里最基础的针孔相机模型的简化版本。
然后在OnDraw中定义立方体的8个顶点,并按其棱线连接关系绘制:
void CGraphDemoView::OnDraw(CDC* pDC) { CGraphDemoDoc* pDoc = GetDocument(); if (pDoc == NULL) return; CRect clientRect; GetClientRect(&clientRect); pDC->FillSolidRect(clientRect, RGB(255, 255, 255)); // 立方体顶点(边长为100,中心在原点) Point3D vertices[8] = { Point3D(-50, -50, -50), Point3D(50, -50, -50), Point3D(50, 50, -50), Point3D(-50, 50, -50), Point3D(-50, -50, 50), Point3D(50, -50, 50), Point3D(50, 50, 50), Point3D(-50, 50, 50) }; // 12条棱边索引方式 int edges[12][2] = { {0,1},{1,2},{2,3},{3,0}, {4,5},{5,6},{6,7},{7,4}, {0,4},{1,5},{2,6},{3,7} }; double angle = 30.0; // 绕Y轴旋转30度 CPen pen(PS_SOLID, 1, RGB(0, 0, 255)); CPen* pOldPen = pDC->SelectObject(&pen); for (int i = 0; i < 12; i++) { Point3D p1 = RotateY(vertices[edges[i][0]], angle); Point3D p2 = RotateY(vertices[edges[i][1]], angle); CPoint sp1 = Project(p1, clientRect); CPoint sp2 = Project(p2, clientRect); pDC->MoveTo(sp1); pDC->LineTo(sp2); } pDC->SelectObject(pOldPen); }运行后你会看到一个立方体线框模型,蓝色线条,带透视效果,已经很像图形学教材里的样子了。从这里你可以继续扩展:加入旋转X轴、绕Z轴变换,或者改成球体、茶壶等复杂模型。
5. 交互与动画:让图形动起来
5.1 鼠标交互实现橡皮筋画线
图形学实验里经常要用鼠标画线。MFC里最常见的交互方式是把临时图形画在客户区上,拖动鼠标时显示一条“橡皮筋”一样的线,松开后固定。这需要处理三个鼠标消息:按下左键、移动鼠标、抬起左键。
首先在View类中添加成员变量:
BOOL m_bDrawing; CPoint m_startPoint; CPoint m_endPoint;在构造函数中初始化m_bDrawing = FALSE。
用类向导(或者手动消息映射)添加三个响应函数。方法是:右键CGraphDemoView类,选择“属性”,点击“消息”小图标,找到WM_LBUTTONDOWN、WM_MOUSEMOVE、WM_LBUTTONUP,分别添加处理函数OnLButtonDown、OnMouseMove、OnLButtonUp。
三个函数实现逻辑如下:
void CGraphDemoView::OnLButtonDown(UINT nFlags, CPoint point) { m_bDrawing = TRUE; m_startPoint = point; m_endPoint = point; CView::OnLButtonDown(nFlags, point); } void CGraphDemoView::OnMouseMove(UINT nFlags, CPoint point) { if (m_bDrawing) { // 用CClientDC在当前窗口客户区绘图 CClientDC dc(this); // 先画一个矩形空白擦除上一条线,简单粗暴 CRect rcClient; GetClientRect(&rcClient); dc.FillSolidRect(rcClient, RGB(255, 255, 255)); CPen pen(PS_DOT, 1, RGB(255, 0, 0)); CPen* pOldPen = dc.SelectObject(&pen); dc.MoveTo(m_startPoint); dc.LineTo(m_endPoint); dc.SelectObject(pOldPen); m_endPoint = point; } CView::OnMouseMove(nFlags, point); } void CGraphDemoView::OnLButtonUp(UINT nFlags, CPoint point) { if (m_bDrawing) { m_bDrawing = FALSE; m_endPoint = point; // 把最终线段存到文档数据中 CGraphDemoDoc* pDoc = GetDocument(); if (pDoc) { // 这里可以将线段端点存入一个数组,然后在OnDraw中统一绘制 } Invalidate(); // 触发OnDraw重绘最终效果 } CView::OnLButtonUp(nFlags, point); }注意,这里在鼠标移动时用CClientDC dc(this)直接绘图,而不是调用Invalidate。因为OnDraw是“完整重绘”逻辑,如果在鼠标移动时频繁调用Invalidate,会导致窗口闪烁和性能下降。橡皮筋本身就是临时画面,现场画、现场擦,最终在抬起时彻底刷新一次,这是从业者常用的做法。
这里的粗糙点在于:每次mousemove都全量刷新客户区,对简单图形没问题,如果图形复杂会闪。改进方案是用SetROP2(R2_XORPEN)实现异或绘制,或者保存背景位图做位图恢复,这个属于进阶技巧,本文先不展开。
5.2 定时器实现旋转动画
要让立方体转起来,需要定时器驱动每帧更新角度并触发重绘。在View类的OnInitialUpdate或窗口创建后启动定时器:
void CGraphDemoView::OnInitialUpdate() { CView::OnInitialUpdate(); SetTimer(1, 30, NULL); // 每30毫秒触发一次,相当于33fps }同时添加WM_TIMER的消息处理函数:
void CGraphDemoView::OnTimer(UINT_PTR nIDEvent) { if (nIDEvent == 1) { m_rotateAngle += 3.0; if (m_rotateAngle >= 360.0) m_rotateAngle -= 360.0; Invalidate(); // 请求重绘 } CView::OnTimer(nIDEvent); }在View类头文件中添加成员变量double m_rotateAngle;,构造函数里初始化为0。然后把OnDraw中的double angle = 30.0;替换成double angle = m_rotateAngle;。再次运行,立方体就会绕Y轴匀速旋转。
不要忘记在窗口销毁时释放定时器,在View类中添加WM_DESTROY的处理函数:
void CGraphDemoView::OnDestroy() { KillTimer(1); CView::OnDestroy(); }定时器周期30毫秒,是图形学动画里比较合适的数值。如果改成10毫秒,旋转会显得更流畅但CPU占用升高;改成100毫秒则会看到明显卡顿。通过调整这个参数,你能直观感受到帧率和流畅度的关系。
6. 常见问题与排查技巧实录
6.1 编译链接问题速查表
我整理了图形学MFC实验中最常遇到的几个编译错误,以及对应的解决办法:
| 错误信息 | 常见原因 | 解决方案 |
|---|---|---|
| 无法打开包括文件: afxwin.h | MFC组件未安装 | 运行安装程序补装MFC库组件 |
| 无法打开包括文件: pch.h | 项目配置问题或头文件引用顺序不对 | 检查每个cpp文件第一行是否为#include "pch.h" |
| LNK2019 无法解析的外部符号 WinMain | 子系统设置错误或入口函数缺失 | 链接器-系统中选Windows (/SUBSYSTEM:WINDOWS) |
| LNK2019 无法解析的外部符号 CGraphDemoApp构造函数 | 类实现文件未包含或链接库缺失 | 查看是否忘写IMPLEMENT_DYNCREATE等宏,重新生成项目 |
| C2664 无法将参数从const char[]转换为LPCTSTR | Unicode字符集下使用了窄字符串 | 改为多字节字符集,或用_T("")包裹字符串 |
| C2065 未声明的标识符 | 缺少头文件 | 按提示添加#include "pch.h"或相关MFC头文件 |
其中最容易忽略的是最后一种:很多从老教材复制的代码里用了afx_msg或ON_COMMAND,但相关头文件没包含,导致编译器报“未声明标识符”。只要把MFC标准头文件和pch.h放好位置,大部分问题就能消解。
6.2 图形画了不显示或者消失
出现“画了图但看不到”的情况,先确认是不是OnDraw里的绘制代码被条件分支挡住了。例如有些教程代码会检查文档指针是否为NULL,如果你的文档类没创建好,就直接return了,图形当然画不出来。
另一种情况是,鼠标画的临时图形在窗口最小化或拖拽后消失了。这是因为窗口重绘时只执行OnDraw,而临时画的橡皮筋没有存起来。正确的做法是把所有需要持久显示的对象存入文档类容器,OnDraw里集中重绘,临时图形只存在于鼠标消息的处理过程中。
如果图形出现严重的闪烁,尤其是频繁Invalidate时,可以启用双缓冲。标准做法是在内存中创建一个兼容DC和位图,先往位图上绘制,再一次性BitBlt到窗口:
// 在OnDraw中 CBitmap memBitmap; CBitmap* pOldBitmap; CDC memDC; memDC.CreateCompatibleDC(pDC); memBitmap.CreateCompatibleBitmap(pDC, rcClient.Width(), rcClient.Height()); pOldBitmap = memDC.SelectObject(&memBitmap); memDC.FillSolidRect(rcClient, RGB(255, 255, 255)); // ... 在memDC上绘制所有图形 ... pDC->BitBlt(0, 0, rcClient.Width(), rcClient.Height(), &memDC, 0, 0, SRCCOPY); memDC.SelectObject(pOldBitmap);这套双缓冲逻辑在图形学动画中几乎是必备的,能有效消除画面撕裂和闪烁。
6.3 高DPI屏幕下图形错位
现在很多笔记本默认150%或200%缩放,MFC程序如果没做DPI适配,鼠标坐标和绘制坐标会错位。症状是:鼠标点击的位置和实际画的点之间有一段偏移。
解决方法有两种。第一种是在程序入口或项目清单中声明DPI感知。在项目属性里,链接器→清单文件→其他选项,添加/DEPENDENTASMVERSION设置不太直观,更简单的方法是调用API:
// 在CGraphDemoA:InitInstance开头加一句 SetProcessDPIAware();SetProcessDPIAware()是最快的适配方案,但只对当前进程生效,而且必须在窗口句柄创建前调用。第二种更完整的是在Manifest里声明dpiAware=True,并使用WM_DPICHANGED消息处理窗口尺寸变化。对于图形学实验,个人建议先调用SetProcessDPIAware,画面坐标就能和鼠标坐标对齐了。
6.4 GDI对象泄漏问题
OnDraw每次刷新都会调用一次,如果你在里面new了画笔、画刷,却不恢复旧对象,程序运行一段时间后就会出现绘图异常,甚至整个界面变成空白块。这就是GDI对象泄漏。
严格规范是:所有SelectObject选中的对象用完了,都要用SelectObject(pOldPen)还回去。凡是new出来的画笔画刷,优先考虑栈对象,让析构函数自动释放。比如前面的例子我都用CPen pen(...)局部对象,函数退出自动销毁,这是最安全的方式。
如果你怀疑程序有泄漏,可以在任务管理器里监控“GDI对象”数量:运行程序前记一下数值,反复刷新窗口,如果数值持续上涨,说明泄漏了。这个指标比内存泄漏更容易暴露问题,因为微软的调试工具不一定能全自动追踪GDI句柄。
6.5 调试图形学算法的实用技巧
图形学代码调试最痛苦的是“运行不报错但画面不对”。比如立方体变形、线条乱飞,这种问题靠断点很难定位,因为一帧画上百条线,每步都在算坐标。我的经验是三步走:
第一,在Project函数里临时输出几个关键点的坐标到屏幕或调试窗口,对比自己手算好的数值,确认数学变换是否正确。比如旋转90度后,原(50,0,0)点应当变成(0,0,-50),如果结果不一致,问题出在旋转矩阵的符号或角度单位上。
第二,先用2D图形验证交互逻辑,再上3D模型。画一条直线、一个圆,确认坐标投射没问题,再加旋转和投影。
第三,把图形数据导出成文本文件,用外部工具绘制校验。比如把立方体的12条棱的端点坐标写入txt,用任意图形库读出来显示一遍,能立刻判断是MFC代码问题还是算法逻辑问题。
做图形学实验踩过几次坑之后,我的习惯是:先让一个简单的三角形在OnDraw里跑通,再往上加鼠标交互和定时器动画,不要一上来就写齐所有变换。MFC的文档视图架构看似绕,但它把“数据”和“显示”拆得很清楚,你后面接OpenGL或DirectX时,只要把绘制代码从GDI换成相应API就行。希望这份步骤能帮你少走几步弯路,顺利把实验跑起来。