ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MFC+OpenGL CAD二次开发:经典源码实战与图形学底层解析

MFC+OpenGL CAD二次开发:经典源码实战与图形学底层解析 简介这套配套源码DEMO源自王清辉、李静蓉合著的《CAD应用程序开发详解——Visual C与OpenGL综合应用》面向有一定C基础、希望深入掌握CAD图形编程的开发者和工科学生。包内共710个文件包括142个h头文件与131个cpp源文件、43个lib库和47个dll动态库、以及25个sln工程和vcproj项目文件另有stl、rc资源与bmp/ico等图形资源完整支撑从几何建模、OpenGL渲染到MFC界面集成的学习实践。压缩包仅4.04MB轻量易下载。资源从基础绘图命令如glBegin/glVertex到光照纹理、碰撞检测等高级主题均配有可运行的DEMO能帮助读者对照书中章节逐行理解代码实现快速掌握在Visual C环境下搭建CAD系统的核心流程。已有651人浏览学习适合作为自学或课程设计的辅助资料。1. 项目概述一本书与一个时代的技术样本拿到这本书和配套的CD源码时我第一反应是“老”Visual C 6.0时代的东西距今已经二十多年。但真正翻完源码、跑起来Demo之后我得说一句公道话这套资料对今天做CAD二次开发、图形算法、甚至是三维渲染底层研究的人来说依然有很强的参考价值。原因很简单软件会过时但图形学的底层逻辑不会变MFC的框架骨感但清晰OpenGL的核心管线在当时的写法与今天的兼容模式一脉相承。标题里的“王清辉、李静蓉”是两位在CAD/CAM领域深耕多年的学者这本书的特质是“学院派实战”——既有理论推导又有可以直接编译的工程代码。对于想搞懂“CAD系统内部怎么画图”“鼠标拾取、视图变换、几何求交这些操作到底怎么实现”的人来说这是一份难得的、不用去看国外砖头书就能上手的教材式源码。这篇稿子我想做的不是给你复述目录而是从一个实际做过CAD工具开发的人角度把这本书里最有价值的几个模块拆开来讲包括MFC下OpenGL环境的搭建逻辑、核心图形交互的实现思路、源码DEMO的编译运行要点以及我实际跑这些代码时踩过的一堆坑。顺便把那些最容易让人卡住的细节——比如像素格式设置、双缓冲机制、坐标变换顺序——单独拎出来说透。2. 整体设计与思路拆解为什么是Visual C搭配OpenGL2.1 CAD开发环境下MFCOpenGL组合的必然性在Windows平台上做CAD类软件九十年代末到两千年初最主流的技术组合就是“MFC做外壳 OpenGL做内核渲染”。这个组合在今天看来老气但逻辑上非常合理MFC提供的是窗口、菜单、对话框、文档视图架构这些“应用骨架”而OpenGL负责真正的“画图”。CAD需要的不只是画个圆柱体做个动画而是精确的坐标输入、实体选中、正交/透视切换、视图旋转平移缩放这些功能在当时的DirectX下几乎没法做因为DirectX更偏向游戏实时渲染对CAD动态高亮、线框模式支持很弱。而OpenGL从设计之初就带有一套几何渲染管线和选择机制可以做到像素级精确。所以这本书的核心架构是CDemoMFC单文档程序作为宿主在View类中完成OpenGL渲染上下文的创建把WM_SIZE、WM_ERASEBKGND、WM_LBUTTONDOWN这类Windows消息加工成OpenGL需要的画布重置、背景保护、拾取响应事件。这个框架思路即使放到今天移植到Qt或者Win32原生窗口上也基本能照搬。2.2 源码DEMO目录结构的潜台词CD源码解压之后你会看到典型的多工程结构——一般包含一个主工程和一个或多个辅助工程比如基础图形类库点、线、面、变换矩阵、解析几何算法库求交、求距、投影、以及界面交互模块。我第一次跑的时候直接打开.dsw文件用VC6.0编译会有点老但我用Visual Studio 2019也能打开并重新编译成功原因在于它本身没有用到太多只能在VC6里用的特殊API。这本书的用心之处在于每个DEMO都对应书中的一章你能从代码里直观看到“从零开始封装点类、矢量类、矩阵类”的过程。这是很多现代教材直接拿现成库一笔带过的地方而实际工作里恰恰是这些基础的数学类和数据结构决定了一个CAD工具的稳定性和扩展性。书里用的坐标变换、齐次矩阵、叉积点积求交这些代码拿到现在做BIM插件、CAM刀路算法、3D打印切片软件依然可以直接改改数据结构就复用。2.3 为什么现在还要看“老代码”我理解很多刚入行的朋友一看到MFC就觉得过时想直接从C加上Unity或者Three.js起步。但有一个现实问题工业级CAD/BIM软件的开发Windows桌面端依然是主战场而Windows桌面端性能最优的路径依然是C直接调图形API。MFC作为外壳虽然老旧但它帮我们省掉了大量Win32消息循环、菜单资源、文档视图中那些繁琐的手写代码让我们把注意力放到核心算法上。更重要的是这本书源码里展示的渲染流程和几何算法是“裸写”的没有任何引擎帮你遮遮掩掩。你能看到一条线段是怎么先做坐标变换、再投影到屏幕、再走OpenGL管线绘制出来的你能看到一个拾取操作底层是拿像素窗口遍历名字栈做命中的。这种源码级别的通透感对建立图形学直觉特别有帮助。3. 核心细节解析与实操要点环境搭建与关键技术3.1 MFC下初始化OpenGL的完整操作这是所有OpenGL单文档应用的第一步也是最容易翻车的一步。在MFC里你不能直接拿设备上下文DC调用OpenGL函数必须先设置像素格式再创建渲染上下文HGLRC然后把它和当前DC关联起来。这套流程在线程里走一遍窗口才能真正成为OpenGL的画布。实操中请记住这个顺序先给窗口类注册样式时加上CS_OWNDC标记避免每次重绘都重设DC然后在View类的OnCreate里拿到客户区DC调用ChoosePixelFormat选择一个合适的像素格式再SetPixelFormat设置进去最后wglCreateContext创建渲染上下文再wglMakeCurrent把它设为当前。这本书的代码在PreCreateWindow里已经做了样式处理你只需要确保继承CView并复写这几个方法即可。一个常见的错误是忘记设置双缓冲。CAD交互需要平滑刷屏单缓冲会在鼠标拖动观察视角时闪得让人眼瞎。像素格式描述符PIXELFORMATDESCRIPTOR里你要把dwFlags设为PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER同时把iPixelType设为PFD_TYPE_RGBA颜色位数按自己的需求设为24位或32位。深度位数建议至少24位否则复杂模型遮挡关系会乱。3.2 消息映射的轻重缓急哪些必须处理哪些可以忽略用MFC做OpenGL应用View类里有几个消息必须处理不处理程序能跑但画面会出现各种灵异现象。第一个是WM_ERASEBKGND必须返回TRUE阻止系统擦除背景。因为MFC在重绘前会把窗口背景填成白色如果你不拦截这个每次OpenGL交换缓冲区前就会被系统刷一层白闪烁极其严重。很多新手在这儿卡很久以为是OpenGL没画对实际上只是没拦截背景擦除。第二个是WM_SIZE窗口大小一变就要调用glViewport更新视口并按新的宽高比重新计算投影矩阵。CAD窗口允许用户拖拽任何尺寸投影比例一旦不匹配图形就被拉变形。书里的代码把视口更新放在OnSize里同时调用了Invalidate触发重绘这是标准做法。第三个是WM_LBUTTONDOWN、WM_MOUSEMOVE、WM_LBUTTONUP这一套用于拾取和旋转视角。拾取用的是OpenGL的选择模式也就是在鼠标点击时把渲染模式从GL_RENDER切换到GL_SELECT然后绘制一个仅包含名字栈的“命中测试版本”场景。这个过程不需要真正把图形画到屏幕上而是GPU在后台计算哪些图元落在鼠标附近的小窗口内。这本书给出了完整的拾取实现代码包括触摸容差的计算。3.3 重头戏视图变换与透视投影的数学直觉CAD视图操作的核心是“把一个3D世界展示在2D屏幕上的同时还让人眼舒服”这里面有个概念尤其重要模型视图矩阵ModelView和投影矩阵Projection的分离。OpenGL里的坐标变换是分两步走的。第一步通过模型视图矩阵把世界坐标变成“眼睛坐标”。这个矩阵实际上干了两个事把模型挪到眼前模型变换把相机摆好对准目标视图变换。第二步通过投影矩阵把眼睛坐标变成裁剪坐标这一步决定了你看的是透视近大远小用gluPerspective还是正交尺寸不变用glOrtho。CAD里经常要切换这两种模式看整体效果用透视做精确测量和二维草图则用正交。这本书代码里分别实现了这两种投影的切换函数甚至把切换时的参数联动也做了处理。实操中我建议你把矩阵运算单独封装成类不要在渲染函数里随便改写矩阵栈否则调一会儿就乱了。书里给的矩阵类是压栈出栈的标准实现但你要注意OpenGL的矩阵操作是列优先的数学教材里通常写行优先两者在乘法顺序上是相反的最容易搞反。最常见的错误就是明明绕Y轴转了30度结果模型绕Z轴转了这多半就是矩阵乘序没处理好。4. 实操过程与核心环节实现从源码到可运行的DEMO4.1 拿到CD源码的第一件事编译顺序与工程配置解压源码后不要急着双击工程文件就F5先看目录结构。一般会有类似“Chapter04_CoordinateTransform”“Chapter06_ModelRender”这种子目录每个子目录是一个独立Demo。先跑最简单的那个比如基础图形绘制或线框模型显示的示例跑通之后再去碰拾取和渲染的复杂示例。打开工程文件之后检查两组关键配置。第一组是工程“设置链接”选项卡里是否包含了opengl32.lib、glu32.lib、glaux.lib新环境里一般用不到glaux直接用freeglut或glew替代。第二组是C/C预处理器定义是否加入了WIN32、_DEBUG/_RELEASE、_WINDOWS这些标准定义。这本书自带源码在VC6.0下是开箱即用的但你在VS2015以上环境编译时会遇到两个常见问题一是MFC头文件版本不一致可能需要把#include afxwin.h等头文件的位置调整到stdafx.h里二是glaux库在一些高版本SDK里被移除需要去掉相关引用或用freeglut替换。为了省事我建议你直接新建一个MFC SDI工程然后把书里的View类、几何算法类文件复制进来。比直接改原工程要干净得多因为新版VS对旧工程做迁移时生成的中间文件容易残留反而消耗时间。我自己是把全书用到的自定义几何类点、向量、矩阵、几何体单独建了一个静态库工程这样Demo程序的可视化代码和几何算法彻底分离后续再跑别的章节的Demo时直接链这个静态库就行省去大量重复编译时间。4.2 成功编译后第一次运行的预期效果这一步很关键我告诉你会看到什么。运行后屏幕上会出现一个灰度或黑色的客户区里面有一个坐标系三段彩色坐标轴或一个简单线框物体可以用鼠标左键拖动旋转视角按住Shift加鼠标拖动可以平移滚轮负责缩放。如果是拾取示例鼠标点击图形表面时该图元会高亮显示或者窗口标题会输出你拾取到的几何对象名称。如果屏幕上什么都没有先别慌按顺序检查确认OnCreate里像素格式设置成功wglMakeCurrent调用成功检查OnSize里是否调用了glViewport并把投影矩阵重新算了一遍最后看绘图函数是否在OnDraw里被调用。还有一个特别容易被忽略的是否忘了调用SwapBuffers。双缓冲模式下你画的每一帧必须通过SwapBuffers(m_hDC)提交到屏幕上不然画面永远停留在缓冲区里你以为程序没画其实画了只是没交换。4.3 核心渲染循环的代码拆解书里的一段基准渲染代码大致长这样void CCADView::RenderScene() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(m_eyeX, m_eyeY, m_eyeZ, m_targetX, m_targetY, m_targetZ, 0.0, 1.0, 0.0); glPushMatrix(); glRotatef(m_rotX, 1.0f, 0.0f, 0.0f); glRotatef(m_rotY, 0.0f, 1.0f, 0.0f); // 绘制几何模型 DrawModel(); glPopMatrix(); glFlush(); SwapBuffers(m_hDC); }这里有几个细节值得琢磨。第一gluLookAt的参数代表相机位置、目标点、上方向。CAD应用的相机一般在目标点斜上方所以m_eyeZ通常比m_targetZ大一些m_eyeX和m_eyeY根据旋转角度做动态变化。如果你的视点固定不动CAD的旋转实际上是在反向旋转模型这里很多初学者容易绕进去。第二glPushMatrix和glPopMatrix成对出现这是为了把旋转隔离在一段作用域里。如果你不压栈弹栈后面再画别的物体时变换会叠加导致图形跑到意想不到的位置。第三SwapBuffers是双缓冲提交的关键步骤。放在整个绘制最后一旦漏掉所有画面都停留在后台缓冲屏幕上始终是白屏。第四关于抗锯齿和光照。这本书的多数Demo没有开光照只用颜色区分面但这不意味着光照不重要。CAD为了看清模型结构多数情况下会同时提供线框模式和着色模式着色模式下需要设置光源位置和材质属性。你看书里的源码扩展部分就能看到对应实现建议你把光照打开以后对比一下模型立体感提升的效果这对理解三角面法向量计算很有帮助。4.4 坐标拾取实现思路别被OpenGL的选择模式吓到拾取操作在CAD里是高频基础操作。用户点击屏幕上的某个点程序需要判断他点到了哪个物体。这本书给出的做法是进入OpenGL的选择模式调用glRenderMode(GL_SELECT)进入选择模式。调用glInitNames和glPushName建立名字栈。在绘制每个物体前glLoadName给它分配一个唯一ID。调用gluPickMatrix在鼠标附近创建一个拾取矩阵再乘以投影矩阵。绘制场景不会真正显示到屏幕但会记录名字栈命中情况。调用glRenderMode(GL_RENDER)退出选择模式返回命中记录GLuint数组。遍历命中记录取出最小深度值对应的名字作为拾取结果。这个模式的关键在于拾取不是自己写射线求交算法而是借用了OpenGL近裁剪平面形成一个极小的视锥体凡是被这个视锥体罩住的图元都算命中。原理虽然简单但代码写起来细节多比如gluPickMatrix的x和y坐标必须是窗口坐标且y方向还要注意OpenGL坐标原点在左下角而Windows窗口原点在左上角这个差异不处理好鼠标怎么样都拾取不到正确目标。我建议你把拾取功能封装成一个单独的方法内部维护名字和物体ID的映射表这样后期扩展成属性查询、删除编辑时直接复用。我不太推荐你用这个方案做超大场景的拾取因为它本质上是CPU回读方式场景规模大了效率会明显下降但几千个图元的教学Demo完全够用。4.5 键盘与鼠标互动的几个扩展技巧书上把鼠标旋转视角写得很基础但实际使用体验还有优化空间。一个细节是旋转灵敏度。鼠标移动几个像素就应该让模型转几度这个比例如果太大转起来像抽风太小的又让人着急。通常我会在OnMouseMove里记录上次鼠标位置和当前位置的差值差值与一个常量相乘产生旋转角度。这个常量别写死放到成员变量里方便不同屏幕DPI下调整。另一个细节是缩放操作。滚轮消息WM_MOUSEWHEEL拿到的是120的倍数你把角度转为距离变化量更新相机位置或者模型缩放比例再Invalidate重绘即可。注意缩放时不要让相机距离目标点太近或太远否则会产生透视裁剪。更好的做法是把缩放改成更改相机到目标的距离而不是直接改模型缩放因为模型缩放会连带把线宽、点大小也缩了看起来非常别扭。5. 常见问题与排查技巧实录5.1 典型编译错误与运行异常速查表我把我在实际运行这套源码时遇到的高频问题整理成了一个表你如果卡住了直接照着排查。问题现象可能原因解决方案链接错误无法解析glBegin/glEnd系列符号opengl32.lib没链接在工程设置里添加opengl32.lib、glu32.lib到附加依赖项链接错误glaux相关函数无法解析高版本SDK没有glaux.lib移除glaux相关代码用freeglut或直接调用标准OpenGL函数替代模型绘制函数窗口白屏/黑屏且无任何图形WM_ERASEBKGND没拦截或没调用SwapBuffers在View类里重写OnEraseBkgnd返回TRUE检查绘图函数末尾是否调用SwapBuffers(m_hDC)图形一拖动就疯狂闪烁单缓冲模式或背景擦除没有拦截使用双缓冲像素格式PFD_DOUBLEBUFFER并在OnEraseBkgnd中直接返回TRUE鼠标点击无法拾取到物体坐标系未转换Windows窗口坐标y轴和OpenGL坐标y轴方向相反拾取前用getSystemMetrics拿到窗口客户区高度用height - y转换鼠标y坐标图形变形严重圆形变椭圆glViewport与投影矩阵宽高比不一致OnSize里用新的客户区尺寸同时更新glViewport和gluPerspective的宽高比模型显示为纯黑色光照开启但没有设置法向量或光源位置不对检查法向量计算是否正确光源位置要设在视觉范围内禁止把法向量全部设为零向量5.2 多监视器与DPI缩放的隐藏杀手这个坑我当年在VC6时代没碰到过但在现在的高分屏上特别明显。Windows的DPI缩放会改变窗口客户区的逻辑大小和实际像素大小的对应关系而OpenGL的glViewport接收的是物理像素。如果你用默认的启用DPI缩放方式运行程序在2K屏上可能出现模型显示缩放比例不对、拾取偏移半个屏幕的情况。最简单的解法是在程序入口处调用SetProcessDPIAware()声明进程自己处理DPI让窗口尺寸按物理像素计算。这个方法其实在微软官方文档里有但不少教材都不提导致很多人升级电脑后同样的代码效果变得莫名其妙。另一个相关的细节是如果你的开发机是多显示器且两块屏幕缩放比例不同窗口跨屏拖动时MFC可能会发送两次WM_SIZE第二次的客户区尺寸才是最终尺寸。你可以用OpenGL的标准做法在OnSize里先判断m_HWND是否为NULL确保渲染上下文初始化完成以后再更新视口。5.3 老代码在新版Visual Studio里的迁移实操如果你没有VC6.0环境直接拿新版VS2022打开旧工程文件过程会稍微曲折但可以搞定。我用的具体步骤是新建一个空的MFC单文档工程工程名随意复制原书源码里的View类、文档类、几何算法类和资源文件.rc、.ico、.bmp等到新工程目录在新工程里通过“添加现有项”把复制的.cpp和.h文件加入工程对View类的构造函数和消息映射做小范围调整因为新版MFC的宏定义和旧版有些差异最常见的问题就是消息映射条目格式需要更新为新版风格最后检查资源和图标路径。这个方法比较推荐比直接做旧工程升级兼容性更好。如果你还是想直接用旧工程文件我的建议是按顺序安装“C MFC for latest v143 build tools”工作负载然后在项目属性里把平台工具集改成Visual Studio 2015 - v140或更高版本。书里的代码本身没有用到太多VC6专属特性所以迁移成功率很高。最麻烦的反而是头文件路径和字符集设置。如果你在编译时碰到大量关于char和wchar_t转换的警告直接把项目的字符集设为“使用多字节字符集”可以一次性解决。5.4 调试图形问题的独门方法图形程序调试是出了名的难受因为你没法在glDrawArrays中间打断点去看渲染结果。我的习惯做法是在一个调试版本里添加一个“离屏纹理导出”功能渲染完一帧后用glReadPixels把当前缓冲区内容读出来保存成BMP文件再再外部用看图工具检查。这个方法可以直观判断到底是程序没画还是画了又被其他操作覆盖还是颜色通道取值异常。另一个方法是分段绘制法把场景里的物体一个一个单独绘制先画坐标轴确认坐标变换没问题再画第一个物体确认几何数据没问题再加第二个物体检查相对位置关系。每次只加一个变量问题就能很快定位。千万不要一上来就全场景一起调试几十个物体里面你根本不知道是哪个环节出了问题。6. 深挖两个扩展点从课程Demo到真实工具6.1 从“绘制模型”到“参数化建模”的跃迁路径书的示例大多是把几何数据硬编码在程序里这对于验证渲染和交互逻辑够用但离真实CAD工具的形态还有距离。真实工具需要一个数据模型层能创建、修改、删除几何实体能按参数实时重建模型能在拓扑结构层面维护点和面的关系。如果你想把书里的Demo升级成一个小型参数化建模器我建议从实体类设计改起。不要直接用Point3D这些裸数据类做UI交互而是建立一个继承体系从抽象基类Entity出发派生LineEntity、CircleEntity、PolygonEntity、MeshEntity等子类每个子类除了持有几何属性外还要实现自己的Draw(RenderContext*)和Pick(Ray)方法。这样视图层的渲染循环只需要遍历所有Entity指针列表各自调用Draw无论模型多复杂核心渲染函数都不用改。更深一步你可以引入实体间的拓扑关系比如边的两个端点、面的边界环、实体的壳和体这是从“图形引擎”走向“建模内核”的分水岭。书的源码里有一些基础的点线面拓扑但比较原始。如果你想往真实CAD方向走去看开源内核OpenCASCADE的BRep数据结构会更系统但这本书至少能帮你在自己写的C代码里理解这些数据结构的来源。6.2 OpenGL管线老方法如何映射到现代渲染架构如果按现代GPU渲染管线的思路这本书里的固定管线APIglBegin/glEnd、glVertex3f、glPushMatrix都可以被Shader程序替代但我想强调一个观点固定管线思路并没有失效它只是被封装进了“数学库渲染器”的底层。现代OpenGL核心剖面Core Profile确实不再支持glBegin/glEnd但书中的矩阵运算和坐标变换思想可以直接映射到glm库。你把glLoadIdentity()换成mat4(1.0)把gluLookAt换成glm::lookAt把glRotatef换成glm::rotate事情并没有本质区别只是“矩阵计算从GPU侧移到了CPU侧再经由Uniform传给Shader”。这本书源码的价值恰恰在于它把矩阵的来龙去脉展现了而现代框架可不会告诉你齐次坐标为什么需要最后一列的1。如果你有精力和兴趣一个很好的训练项目是把这本书的某个Demo用OpenGL 3.3Shader重写一遍。你会发现渲染性能大幅提升的同时代码组织也更工程化了——Vertex Array Object管理顶点数据Shader编译链接成ProgramUniform变量传递模型视图矩阵和材质参数。做完这个迁移你再看OpenGL面试题里那些看似抽象的管线细节会有一种豁然开朗的感觉。7. 从Demo到产品级代码从业者的几点心得写完这么多技术细节我想再啰嗦几句这些年做实操项目的体感。把这本书的Demo跑通只是迈出第一步。真正的CAD工具开发瓶颈往往不在图形渲染本身而在数据组织、内存管理、交互鲁棒性。渲染用的OpenGL代码其实只占一个成熟CAD工具代码量的很小一部分大量时间花在如何表示几何模型、如何做撤销重做、如何处理用户输入的各种非理性操作上。我见过不少同行拿到这本书的源码兴奋地跑了几天然后遇到移植问题就放弃了。这里给你一个明确建议别追求一次把所有章节的代码都跑通哪怕只把坐标变换和拾取这两个核心模块彻底吃透、重写成自己的代码风格就已经值回票价了。一是因为这两个模块是所有CAD交互的基础二是因为它们教会你的不是某个API怎么用而是“如何用C把图形学概念落地成可靠的数据结构”。还有一个日常开发特别容易忽略的理念图形API的错误检查不能依赖“眼睛看得顺不舒服”来判断。一定要养成在每次关键调用后检查glGetError的习惯。中文博客上能找到很多OpenGL错误码的对照表但真正让人头疼的是glGetError返回的错误是累积的报的可能是几十帧之前的错误。所以我的做法是在交换缓冲区之前做一个检查点确保这一帧没引入新错误。最后分享一个“偏门”但实用的技巧调试CAD类程序时把鼠标位置和视图矩阵参数显示在窗口标题栏上。这个方法虽然土但比任何调试器都直观高效。当旋转、缩放、拾取出现问题时你能立刻从标题栏看到眼坐标、目标点、旋转角度的实时变化一眼就能看出参数有没有按预期更新。这本书的Demo没提供这个功能但你在自己的代码里花十分钟加上去后面调试效率能翻倍。这套资料确实不属于20世纪但图形学的基础知识不会过期。如果你能把书里的代码真正吃透再看现在市面上那些基于现代OpenGL的CAD教程、甚至图形引擎源码都会觉得底子扎实很多。这种来自老派教材的“慢功夫”在今天快餐式的学习氛围里其实更显得珍贵。本文还有配套的精品资源点击获取
返回列表