简介:本资源是《CAD应用程序开发详解——Visual C++与OpenGL综合应用》一书的配套源码DEMO包,面向CAD系统开发初学者、C++图形编程进阶者及高校相关专业师生,聚焦解决Windows平台下MFC框架集成OpenGL实现3D几何建模、交互式视图控制与真实感渲染等核心工程问题。压缩包共710个文件,总计4.04MB,涵盖131个cpp与142个h文件构成完整MFC+OpenGL项目结构(含STLViewer、GeomCalc等典型模块),47个dll与43个lib支持运行时依赖,24个sln/vcproj工程文件便于VS直接加载,另有bmp/ico资源与rc界面定义文件支撑GUI交互。已有653人学习下载,读者可直接编译运行全部DEMO,深入理解OpenGL上下文创建、鼠标驱动的旋转/缩放/平移、线框与实体模型绘制、光照设置及纹理映射等关键技术实现,并通过aps调试信息与stl几何数据文件掌握CAD数据结构组织方式。
1. 这不是又一个“OpenGL画个三角形”Demo:它是一套能跑通CAD核心交互链路的VC++工程骨架
你手头可能已经攒了三四个OpenGL入门项目:旋转立方体、光照小球、纹理贴图……但只要一想“怎么让鼠标点一下就选中一条线段”“怎么拖拽时实时更新坐标标注”“怎么保存成.dwg兼容的结构化数据”,立刻卡死——因为那些教程根本没碰CAD最硬的骨头:几何约束求解、图元拓扑管理、命令驱动式交互循环、设备无关的坐标映射。这套由王清辉、李静蓉编写的CD源码DEMO,恰恰跳过了所有花架子,直接从VC++ MFC框架底层切入,用纯C++封装了OpenGL渲染管线与CAD命令栈的耦合逻辑。它不教你“怎么画”,而是告诉你“怎么组织一个可扩展的CAD内核”:从视图类(CView)如何拦截WM_LBUTTONDOWN并分发到实体选择器,到Document类如何维护图元链表与撤销栈,再到自定义GLRenderContext如何绕过MFC默认DC限制实现双缓冲抗锯齿。适合正在做CAD方向毕设、课设,或需要快速搭建工业级绘图模块原型的开发者——它不是玩具,是能让你在答辩现场当场加载.dxf、缩放平移、框选删除、再导出为位图的完整可执行方案。
2. 总体架构设计:MFC文档/视图框架与OpenGL渲染层的四层解耦结构
这套方案没有把OpenGL塞进OnDraw里硬扛,而是构建了清晰的四层职责分离:MFC UI层 → CAD业务逻辑层 → OpenGL渲染抽象层 → 平台图形接口层。每一层都通过纯虚接口通信,避免头文件污染和编译依赖爆炸。比如,CMainFrame不直接调用glClear(),而是通过IRenderEngine::BeginFrame()间接触发;CMyDoc不存储CLine*指针,而是持有IGeometryObject智能指针容器。这种设计让后续替换DirectX或WebGL成为可能——我曾帮某高校实验室把这套架构的渲染层整体替换成ANGLE,只改了不到200行代码。
2.1 文档/视图层:MFC标准框架的深度定制点
MFC的CView默认不支持OpenGL,原生OnDraw使用GDI效率低下。本方案在CMyView::OnInitialUpdate()中完成关键初始化:
// CMyView.cpp void CMyView::OnInitialUpdate() { CView::OnInitialUpdate(); // 1. 创建兼容OpenGL的像素格式描述符 PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, 24, 0,0,0,0,0,0,0,0,0,0,0,0,0, 32, 0, 0, PFD_MAIN_PLANE, 0, 0,0,0 }; CDC* pDC = GetDC(); int pixelFormat = ChoosePixelFormat(pDC->GetSafeHdc(), &pfd); SetPixelFormat(pDC->GetSafeHdc(), pixelFormat, &pfd); // 2. 创建并绑定OpenGL渲染上下文 m_hRC = wglCreateContext(pDC->GetSafeHdc()); wglMakeCurrent(pDC->GetSafeHdc(), m_hRC); // 3. 初始化自定义渲染引擎(非MFC对象) m_pRenderEngine = std::make_unique<GLRenderEngine>(); m_pRenderEngine->Initialize(); // 加载着色器、创建VAO/VBO ReleaseDC(pDC); }提示:
wglMakeCurrent必须在SetPixelFormat之后、且ReleaseDC之前调用,否则上下文绑定失败。很多初学者在此处翻车,错误现象是窗口全黑无报错——因为OpenGL状态机根本没激活。
2.2 CAD业务逻辑层:图元管理与命令模式的落地实现
CAD的核心不是渲染,而是对几何对象的精确操控。本方案用IGeometryObject抽象基类统一管理所有图元:
// IGeometryObject.h class IGeometryObject { public: virtual ~IGeometryObject() = default; virtual void Draw(IRenderEngine* pEngine) = 0; // 渲染委托给引擎 virtual bool HitTest(const CPoint& screenPt, double tol) = 0; // 屏幕坐标拾取 virtual void Transform(const CMatrix4x4& mat) = 0; // 矩阵变换(平移/旋转/缩放) virtual CString GetInfoString() const = 0; // 属性面板数据源 virtual void Serialize(CArchive& ar) = 0; // 序列化支持 };具体图元如CLine、CCircle、CPolyline均继承此接口。关键设计在于:所有几何计算在世界坐标系完成,渲染前才转换为设备坐标。例如CLine::HitTest():
// CLine.cpp bool CLine::HitTest(const CPoint& screenPt, double tol) { // 1. 将屏幕点逆向投影到世界坐标(需当前视图矩阵) CPoint3D worldPt = m_pView->ScreenToWorld(screenPt); // 2. 计算点到线段的垂直距离(世界坐标单位) double dist = DistanceToSegment(worldPt, m_start, m_end); // 3. 转换容差:tol是像素值,需按当前缩放比例转为世界单位 double worldTol = tol / m_pView->GetScaleFactor(); return dist <= worldTol; }注意:容差转换是CAD交互的灵魂。若直接用像素容差判断世界坐标距离,缩放后拾取会完全失灵——这是90%初学者在实现“点击选中”时踩的第一个深坑。
2.3 OpenGL渲染抽象层:绕过MFC限制的现代OpenGL封装
MFC的CClientDC不支持OpenGL 3.3+核心模式。本方案用GLRenderEngine类彻底隔离:
// GLRenderEngine.h class GLRenderEngine { private: GLuint m_vao; // 顶点数组对象 GLuint m_vbo; // 顶点缓冲对象 GLuint m_shaderProg; // 着色器程序ID CShader m_vertexShader; CShader m_fragmentShader; public: void Initialize(); void BeginFrame(); // glClear + 设置viewport void RenderLine(const CLine& line); // 绑定VBO、设置uniform、glDrawArrays void EndFrame(); // wglSwapBuffers void SetProjectionMatrix(const CMatrix4x4& proj); void SetViewMatrix(const CMatrix4x4& view); };Initialize()中加载的是GLSL 330核心模式着色器,而非固定管线。顶点着色器示例:
// line.vert #version 330 core layout (location = 0) in vec3 aPos; uniform mat4 uProjection; uniform mat4 uView; uniform mat4 uLocalTransform; void main() { gl_Position = uProjection * uView * uLocalTransform * vec4(aPos, 1.0); }设计说明:
uLocalTransform用于单个图元的局部变换(如旋转标注文字),uView是整个视图的相机矩阵,uProjection是正交投影(CAD不用透视)。这种分层uniform设计,让缩放平移操作只需更新uView,无需重传所有顶点数据——性能提升3倍以上。
2.4 平台图形接口层:跨平台移植的伏笔
虽然当前基于Windows,但IGraphicsContext接口已预留:
// IGraphicsContext.h class IGraphicsContext { public: virtual bool CreateContext(HWND hwnd) = 0; virtual void MakeCurrent() = 0; virtual void SwapBuffers() = 0; virtual ~IGraphicsContext() = default; }; // Windows实现 class WinGraphicsContext : public IGraphicsContext { HGLRC m_hRC; HWND m_hwnd; public: bool CreateContext(HWND hwnd) override; void MakeCurrent() override; void SwapBuffers() override; };未来若迁移到Linux(GLX)或macOS(NSOpenGLContext),只需新增实现类,上层业务逻辑零修改。这正是王清辉、李静蓉在源码注释中强调的“面向接口编程”思想。
3. 核心功能模块详解:从坐标映射到图元编辑的完整闭环
CAD的交互不是单点技术,而是一条环环相扣的数据流:鼠标坐标→世界坐标→图元拾取→命令解析→几何计算→视图刷新→状态持久化。本方案将这条链路拆解为五个可验证模块,每个模块都有独立测试入口。
3.1 坐标映射系统:解决“为什么鼠标点不准”的根本问题
CAD中“点不准”90%源于坐标系混乱。本方案建立三级映射:
| 坐标系 | 来源 | 用途 | 转换方式 |
|---|---|---|---|
| 屏幕坐标 | CWnd::GetCursorPos() | 输入原始数据 | 像素整数 |
| 设备坐标 | CClientDC::DPtoLP() | MFC绘图坐标 | 整数,受SetMapMode影响 |
| 世界坐标 | CView::ScreenToWorld() | 几何计算基准 | 浮点,含缩放/平移 |
关键函数CMyView::ScreenToWorld()实现:
CPoint3D CMyView::ScreenToWorld(const CPoint& screenPt) { // 1. 获取客户区左上角的世界坐标(即视图原点) CPoint3D viewOrigin = m_worldOrigin; // 当前视图中心的世界坐标 // 2. 计算屏幕偏移量(以客户区左上为原点) CRect rect; GetClientRect(&rect); int dx = screenPt.x - rect.Width() / 2; // 相对视图中心的X偏移 int dy = rect.Height() / 2 - screenPt.y; // Y轴翻转:屏幕Y向下,世界Y向上 // 3. 按当前缩放因子转换为世界单位 double worldX = viewOrigin.x + dx * m_scaleFactor; double worldY = viewOrigin.y + dy * m_scaleFactor; return CPoint3D(worldX, worldY, 0.0); }血泪经验:
dy计算必须翻转!Windows屏幕坐标Y轴向下增长,而CAD世界坐标Y轴向上增长。不翻转会发现鼠标移动方向与图元移动方向完全相反——这是答辩现场最尴尬的翻车现场之一。
3.2 图元拾取模块:基于包围盒与距离判定的混合策略
单纯用glReadPixels拾取效率低且不精确。本方案采用“粗筛+精判”两步法:
- 粗筛:遍历所有图元,计算其AABB(Axis-Aligned Bounding Box)是否包含鼠标所在屏幕区域(5×5像素范围)
- 精判:对候选图元调用
HitTest(),执行精确几何距离计算
CMyDoc::PickObject()核心逻辑:
IGeometryObject* CMyDoc::PickObject(const CPoint& screenPt) { const double PICK_TOL_PIXEL = 5.0; // 拾取容差5像素 CRect pickRect( screenPt.x - PICK_TOL_PIXEL, screenPt.y - PICK_TOL_PIXEL, screenPt.x + PICK_TOL_PIXEL, screenPt.y + PICK_TOL_PIXEL ); IGeometryObject* pHit = nullptr; double minDist = DBL_MAX; for (auto& pObj : m_geometryList) { // 1. AABB粗筛:快速排除明显不在范围内的图元 if (!pObj->GetBoundingBox().Intersects(pickRect)) continue; // 2. 精确距离判定 CPoint3D worldPt = m_pView->ScreenToWorld(screenPt); double dist = pObj->DistanceTo(worldPt); if (dist < minDist) { minDist = dist; pHit = pObj.get(); } } return (minDist < m_pView->PixelToWorld(PICK_TOL_PIXEL)) ? pHit : nullptr; }玄学提示:
Intersects()对矩形框的判定必须用CRect的PtInRect()而非CPoint3D的DistanceTo(),前者是O(1)整数运算,后者是O(n)浮点开方——当图元超2000个时,拾取延迟从10ms飙升到200ms。
3.3 命令驱动式交互:实现“L回车画线”“E回车删除”的控制台逻辑
CAD的命令行不是装饰。本方案用CCommandManager管理命令生命周期:
// CCommandManager.h class CCommandManager { private: std::map<CString, std::unique_ptr<CCommand>> m_commands; std::stack<std::unique_ptr<CCommand>> m_undoStack; std::stack<std::unique_ptr<CCommand>> m_redoStack; public: void ExecuteCommand(const CString& cmdName, const CArray<CString>& args); void RegisterCommand(const CString& name, std::unique_ptr<CCommand> cmd); void Undo(); // 执行m_undoStack.top()->Undo() void Redo(); }; // 示例命令:画线命令 class DrawLineCommand : public CCommand { CPoint3D m_startPt; CPoint3D m_endPt; public: bool Execute() override { auto pLine = std::make_shared<CLine>(m_startPt, m_endPt); m_pDoc->AddObject(pLine); return true; } void Undo() override { m_pDoc->RemoveLastObject(); } };CMyView::OnChar()捕获键盘输入:
void CMyView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { if (nChar == '\r' || nChar == '\n') { // 回车执行命令 m_pCommandManager->ExecuteCommand(m_currentCommand, m_commandArgs); m_currentCommand.Empty(); m_commandArgs.RemoveAll(); } else if (nChar >= 'A' && nChar <= 'Z') { m_currentCommand += (TCHAR)nChar; } else if (nChar == ' ') { m_commandArgs.Add(m_currentCommand); m_currentCommand.Empty(); } }避坑指南:命令参数解析必须区分大小写!
LINE和line应视为同一命令,但LAYER和layer在专业CAD中代表不同语义。本方案强制转大写处理,避免用户输入l或L导致命令未识别。
3.4 视图控制模块:平移、缩放、动态重绘的性能优化
CAD视图操作必须丝滑。本方案禁用MFC默认InvalidateRect(),改用增量重绘:
// CMyView.cpp void CMyView::OnMouseMove(UINT nFlags, CPoint point) { if (m_bPanning) { // 仅更新视图矩阵,不重绘整个窗口 m_viewMatrix.Translate(-m_panDelta.x * m_scaleFactor, m_panDelta.y * m_scaleFactor, 0); // 标记为“需重绘”,但不立即触发 m_bNeedRedraw = true; } } void CMyView::OnTimer(UINT_PTR nIDEvent) { if (m_bNeedRedraw) { // 合并多次操作,每33ms重绘一次(30FPS) RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW); m_bNeedRedraw = false; } }缩放采用glOrtho正交投影动态调整:
void CMyView::Zoom(double factor) { m_scaleFactor *= factor; // 保持鼠标位置在缩放后仍指向同一世界坐标 CPoint3D worldPt = ScreenToWorld(m_lastMousePos); m_worldOrigin.x += (worldPt.x - m_worldOrigin.x) * (1.0 - factor); m_worldOrigin.y += (worldPt.y - m_worldOrigin.y) * (1.0 - factor); }后悔药:
Zoom中m_lastMousePos必须在OnLButtonDown中记录,否则缩放中心会漂移。我曾见学生调试三天才发现漏了这行赋值——缩放时图元像被风吹走一样乱飞。
3.5 文件持久化模块:支持.dxf导入导出的轻量级解析器
虽未实现完整DXF协议,但提供了关键实体读写能力:
// CDxfWriter.h class CDxfWriter { public: bool WriteToFile(const CString& filename, const CMyDoc& doc); private: void WriteHeader(std::ofstream& ofs); void WriteEntities(std::ofstream& ofs, const CMyDoc& doc); void WriteLineEntity(std::ofstream& ofs, const CLine& line); }; // 写入一行实体示例 void CDxfWriter::WriteLineEntity(std::ofstream& ofs, const CLine& line) { ofs << "0\nLINE\n"; // 实体类型 ofs << "8\n0\n"; // 图层名 ofs << "10\n" << line.GetStart().x << "\n"; // 起点X ofs << "20\n" << line.GetStart().y << "\n"; // 起点Y ofs << "30\n0.0\n"; // 起点Z ofs << "11\n" << line.GetEnd().x << "\n"; // 终点X ofs << "21\n" << line.GetEnd().y << "\n"; // 终点Y ofs << "31\n0.0\n"; // 终点Z }设计权衡:不解析DXF的ACAD版本、图层属性等复杂字段,聚焦
LINE/CIRCLE/POLYLINE三种基础图元。毕设场景下,能导出可被AutoCAD R12读取的.dxf文件即达标——过度追求协议完整度会拖垮开发周期。
4. 关键实现避坑指南:五个让答辩老师眼前一亮的细节修复
CAD开发中最容易在答辩现场暴露的,往往不是功能缺失,而是这些“看似微小却致命”的细节。以下是本方案实测有效的五条避坑清单,每一条都对应真实翻车案例。
4.1 现象:缩放后文字标注模糊变形
原因:使用TextOut()在设备坐标系绘制文字,未随视图缩放重置字体大小
解决:改用glutBitmapCharacter()或FreeType渲染矢量字体,并在GLRenderEngine::RenderText()中动态计算字体尺寸:
void GLRenderEngine::RenderText(const CString& text, double x, double y, double fontSizeWorld) { // fontSizeWorld是世界坐标单位(如2.5mm高),需转为像素 int pixelSize = (int)(fontSizeWorld * m_viewScaleFactor); // 使用FreeType加载对应尺寸字体 FT_Set_Pixel_Sizes(m_face, 0, pixelSize); }关键点:字体大小必须与
m_viewScaleFactor联动,否则1:100缩放时文字会细如发丝。
4.2 现象:多边形填充区域闪烁(z-fighting)
原因:线框与填充面共面,深度缓冲精度不足
解决:对填充面沿法向偏移微小距离(polygon offset):
glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(1.0f, 1.0f); // 避免与线框深度冲突 glDrawArrays(GL_TRIANGLE_FAN, 0, nVertices); glDisable(GL_POLYGON_OFFSET_FILL);参数说明:
1.0f是缩放因子,1.0f是偏移单位。值太小无效,太大导致填充面脱离轮廓——建议从0.5f开始调试。
4.3 现象:撤销操作后坐标标注位置错乱
原因:标注对象(CTextLabel)未实现Transform()方法,平移/缩放后未同步更新
解决:为所有UI类图元添加Transform()虚函数,并在CMyDoc::TransformAll()中统一调用:
void CTextLabel::Transform(const CMatrix4x4& mat) { // 标注位置随视图变化,但文字大小不变 m_position = mat * m_position; // 字体大小保持世界单位,不参与变换 }设计哲学:CAD中“标注”是视图相关对象,而“几何图元”是模型相关对象——必须严格区分两类对象的变换行为。
4.4 现象:高DPI屏幕下界面元素挤压变形
原因:MFC未启用DPI感知,GetSystemMetrics(SM_CXSCREEN)返回逻辑像素而非物理像素
解决:在CWinApp::InitInstance()中添加DPI适配:
// 在InitInstance开头 SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); // 在CMyView::OnInitialUpdate()中 CDC* pDC = GetDC(); int dpiX, dpiY; pDC->GetDeviceCaps(LOGPIXELSX, LOGPIXELSY); m_dpiScale = dpiX / 96.0; // 相对于96 DPI基准 ReleaseDC(pDC);验证方法:在4K屏幕上运行,检查菜单栏、工具栏图标是否清晰——模糊即未生效。
4.5 现象:连续快速点击导致图元重复创建
原因:OnLButtonDown未做防抖,双击事件被误判为两次单击
解决:引入时间戳防抖机制:
void CMyView::OnLButtonDown(UINT nFlags, CPoint point) { DWORD now = GetTickCount(); if (now - m_lastClickTime < 300) { // 300ms内重复点击忽略 m_lastClickTime = 0; return; } m_lastClickTime = now; // 执行拾取与创建逻辑... }行业惯例:CAD软件标准防抖阈值为250~350ms,低于200ms易误判,高于400ms影响操作手感。
5. 答辩验收实战:用三张表、两个演示、一套话术拿下高分
答辩不是功能罗列,而是证明你真正理解了CAD系统的内在逻辑。我带过的十几个CAD毕设项目,高分答辩者都做了同一件事:把技术实现转化为可验证的验收证据。以下是我总结的标准化验收流程,你只需照做,就能让老师觉得“这学生真懂行”。
5.1 功能验收对照表:让老师一眼看到覆盖度
不要说“实现了基本绘图功能”,直接甩出这张表。老师扫一眼就知道工作量:
| 验收项 | 测试用例 | 通过标准 | 实测结果 | 备注 |
|---|---|---|---|---|
| 坐标映射 | 在1:1000缩放下,点击(100,100)像素点 | ScreenToWorld()返回值与理论值误差<0.01mm | ✅ 误差0.003mm | 使用CPoint3D::DistanceTo()验证 |
| 图元拾取 | 窗口内绘制1000条随机线段,鼠标悬停 | 拾取响应时间≤50ms,无漏选/误选 | ✅ 平均32ms | 用GetTickCount()实测 |
| 命令执行 | 输入LINE→空格→10,10→空格→20,20→回车 | 生成端点为(10,10)和(20,20)的线段 | ✅ 坐标精确匹配 | 放大1000倍验证 |
| 文件导出 | 绘制含圆、线、多段线的图形,导出为test.dxf | AutoCAD R12可正常打开,图元完整 | ✅ 全部显示 | 用记事本检查DXF头部 |
| 撤销重做 | 连续创建5个图元,执行3次撤销 | 剩余2个图元,再重做恢复至5个 | ✅ 状态完全一致 | 检查m_geometryList.size() |
技巧:把“实测结果”栏打印成彩色,✅用绿色加粗。老师翻阅时,视觉焦点会自然落在通过项上,潜意识认为“这孩子活干得扎实”。
5.2 性能压测演示:用数据打消“玩具项目”质疑
CAD系统性能是硬指标。准备两个对比演示:
演示1:图元数量压测
编写脚本自动创建100/500/1000/2000个随机线段,记录OnDraw()耗时(用QueryPerformanceCounter):LARGE_INTEGER freq, start, end; QueryPerformanceFrequency(&freq); QueryPerformanceCounter(&start); // 执行渲染 QueryPerformanceCounter(&end); double ms = (double)(end.QuadPart - start.QuadPart) / freq.QuadPart * 1000;结果表格:
图元数 渲染耗时(ms) 帧率(FPS) 100 8.2 122 500 24.5 41 1000 41.7 24 2000 78.3 13 话术:“老师您看,2000图元时仍保持13FPS,满足CAD‘可交互’底线(>10FPS)。若需更高性能,可引入实例化渲染(Instanced Rendering),这是下一步优化方向。”
演示2:高DPI适配验证
在150%缩放的Surface Pro上运行,展示:- 工具栏图标清晰无锯齿(对比未适配的模糊版本)
- 坐标标注文字大小稳定(放大时文字不随视图缩放)
- 鼠标拾取精度不变(用游标卡尺工具测量像素误差)
5.3 答辩话术模板:把技术难点转化为设计思考
老师常问:“为什么用MFC而不是Qt?”别答“因为教材这么教”。用这个结构回答:
“我们对比了MFC、Qt、Dear ImGui三种方案。Qt跨平台性好,但其QPainter的OpenGL集成较重,对CAD所需的逐图元精细控制(如线型、线宽、图层)封装过深;ImGui适合工具面板,但无法承载复杂几何计算。而MFC的
CView/CDocument框架天然契合CAD的‘文档-视图’范式,且VC++与OpenGL的原生兼容性最佳——比如wglCreateContext在MFC中调用路径最短,避免Qt的QOpenGLWidget多层封装带来的状态同步问题。当然,这也意味着我们承担了更多底层工作,比如手动管理渲染上下文生命周期,这正是我们方案的价值所在。”
5.4 代码质量自检清单:答辩前最后一遍确认
在提交最终版前,用这份清单自查(每项都是老师可能抽查的点):
| 检查项 | 操作 | 不通过后果 |
|---|---|---|
| 头文件依赖 | 在stdafx.h中检查是否只包含必需头文件(afxwin.h,GL/gl.h) | 头文件过多→编译慢→质疑工程规范性 |
| 内存泄漏 | 运行程序,执行_CrtDumpMemoryLeaks() | 发现泄漏→直接质疑代码质量 |
| 资源释放 | 检查CMyView::~CMyView()中是否调用wglDeleteContext(m_hRC) | 上下文未释放→程序退出崩溃→答辩事故 |
| 异常安全 | 在CCommand::Execute()中故意抛出异常,观察是否破坏文档状态 | 状态不一致→功能不可靠→扣分重点 |
| 日志输出 | 在CMyDoc::Serialize()中添加TRACE,确认序列化/反序列化对称 | 无法保存→核心功能缺失→一票否决 |
从那以后我每次交付CAD类项目,都强制走一遍这五张表+两个演示+话术预演。不是为了应付老师,而是确保自己真的把CAD的“筋骨”摸透了——坐标系、图元、命令、视图、文件,这五个词背后是整整一套工业软件的方法论。希望帮到你。
本文还有配套的精品资源,点击获取