本文还有配套的精品资源点击获取简介这个资源包提供一个可在AutoCAD中直接编译运行的ObjectARX C工程实现最简自定义直线实体支持图形显示、鼠标点击选中、夹点拖拽修改端点位置。工程包含完整可运行代码如HPAcDbTestEntity.cpp/.h定义实体类TestEntity.cpp实现几何绘制与数据库存储逻辑rxdebug.cpp提供调试支持TestCmdCommands.cpp和TestCmd.cpp完成命令注册与入口调用。所有文件基于VC6.0传统ARX开发环境组织含.def模块定义、.dsp/.dsw项目配置、StdArx.h/AdskDMgr.h等标准头文件引用不依赖第三方框架或封装层。编译后生成ARX插件加载到AutoCAD即可执行TESTCMD命令创建该自定义直线验证其显示、选择、拖拽、重生成等基础交互行为。适合刚接触ObjectARX开发的用户理解自定义实体从注册、几何表达、显示回调、选择集响应到数据库持久化的全流程实现细节。1. 项目概述一条“活”的直线为什么值得从它开始学ObjectARX刚接触AutoCAD二次开发的朋友常被两个问题卡住一是“ARX到底要写多少东西才能让一个图形在屏幕上动起来”二是“为什么我照着文档写了类却连选都选不上”。这个问题的答案就藏在这条看似最简单的自定义直线上。它不是教科书里画在纸上的几何对象而是一个真正嵌入AutoCAD数据库、能响应鼠标事件、能参与夹点编辑、能被命令流调用的“活”实体。关键词里的ObjectARX、自定义直线、CAD插件、C开发每一个都不是虚词——ObjectARX是AutoCAD原生C开发接口自定义直线是所有复杂实体如管道、梁柱、电路走线的原子单元CAD插件是它最终交付的形态C开发则是它必须扎根的土壤。这条直线之所以“开箱即用”是因为它绕开了所有炫技式封装直接暴露ARX开发中最核心的五根骨头实体注册、几何表达、显示回调、选择集响应、数据库持久化。你不需要先搞懂AcDbObjectId怎么序列化也不用纠结AcDbDatabaseReactor怎么监听只需要编译、加载、输入TESTCMD就能看到一条线被你拖着跑。我当年第一次在AutoCAD里亲手拖拽自己写的直线时那种“它真的听我的”的实感比看十页API文档都管用。这个工程对初学者的价值不在于它多高级而在于它把ARX开发中那些被层层封装掩盖的“毛细血管”全摊开了HPAcDbTestEntity.h里定义的类继承链、TestEntity.cpp里AcDbLine::subGetGeomExtents的重写逻辑、rxdebug.cpp里如何把调试信息输出到AutoCAD命令行……它不教你“怎么写一个完美的插件”而是手把手告诉你“第一步你的代码得先让AutoCAD认出它是个东西”。2. 整体设计与思路拆解为什么是“最小可行实体”而不是“功能完备示例”2.1 核心设计哲学做减法而非堆砌很多ARX教程一上来就塞进图层管理、属性面板、Undo支持、反应器监听结果新手连编译都过不了。这个工程反其道而行之它的设计目标非常明确只实现“显示-选择-拖拽”闭环砍掉一切非必要依赖。这意味着什么意味着它不处理文字标注避免字体资源加载失败、不涉及块参照规避AcDbBlockTableRecord嵌套逻辑、不启用动态UCS省去坐标系转换陷阱。所有代码都围绕一个中心展开让AcDbObjectId指向的对象在AutoCAD视口里能被看见、被点中、被拖动。这种“最小可行实体”MVP思路是ARX开发的黄金法则。我试过把一个带属性的自定义实体直接扔给新人结果三天都在查AcDbObjectId::nullObjectId()报错原因——而这条直线编译后第一次加载就能成功创建因为它的构造函数里只干三件事调用父类AcDbLine构造、设置两个端点坐标、调用setDatabaseDefaults()。没有魔法全是扎实的基类调用。2.2 技术栈选择为何坚守VC6.0与传统ARX结构资源包里反复出现的.dsp、.dsw、StdAfx.h、AdskDMgr.h不是历史包袱而是刻意为之的“技术锚点”。VC6.0虽老但它是ObjectARX SDK官方长期支持的编译环境其项目文件结构.dsp定义编译选项.def导出函数与ARX运行时加载机制完全匹配。现代VS版本虽然能编译但需要手动配置大量宏如ACRX_NO_ACAD_LIB、调整字符集Unicode/MBCS、处理acdb18.dll等旧版库依赖反而增加入门门槛。这个工程选择“向后兼容”是为了让学习者把精力聚焦在ARX逻辑本身而非编译器战争。比如TestEntity.def文件里只有两行EXPORTS acrxEntryPoint 1这行代码的深意在于ARX模块加载时AutoCAD只认acrxEntryPoint这个入口函数它负责注册所有命令和实体。而acrxEntryPoint内部又只做两件事——调用acedRegCmds-addCommand()注册TESTCMD命令调用acrxDynamicLinker-unlockApplication()解锁应用。没有额外框架没有中间层就像拧开一个水龙头水流命令和水管实体直接连通。这种“裸金属”式开发正是理解ARX底层机制的捷径。2.3 模块职责划分谁负责“画”谁负责“动”谁负责“存”整个工程的文件组织本质是一张清晰的职责地图-HPAcDbTestEntity.h/.cpp定义实体类骨架。它继承自AcDbLine但重写了subGetGeomExtents计算图形范围、subWorldDraw世界坐标系绘制、subViewportDraw视口坐标系绘制三个核心回调。这里的关键是subWorldDraw里调用pWd-geometry().line(...)而非自己画OpenGL线段——ARX要求所有绘制必须通过AcGiWorldDraw或AcGiViewportDraw接口这是保证图形与AutoCAD渲染引擎同步的前提。-TestEntity.cpp实现几何逻辑。它不包含任何UI代码只专注数据setStartPoint()/setEndPoint()修改端点坐标getStartPoint()/getEndPoint()读取当前值subTransformBy()处理旋转缩放变换。所有坐标操作都基于AcGePoint3d这是ARX几何库的基石类型避免使用double[3]等原始数组引发内存越界。-TestCmdCommands.cpp TestCmd.cpp命令中枢。TestCmdCommands类继承AcEdCommandStackTestCmd类则实现acedCommand()调用入口。当用户输入TESTCMD时TestCmd::command()被触发它创建HPAcDbTestEntity实例调用acdbHostApplicationServices()-workingDatabase()-appendAcDbObject()将其加入数据库并返回AcDbObjectId。整个过程没有对话框没有用户交互纯粹的命令行驱动符合CAD专业用户的操作习惯。-rxdebug.cpp/h调试生命线。它封装了acedPrintf()和acutPrintf()将调试信息直接输出到AutoCAD命令行。比如在subWorldDraw开头加一句rxdebug::printf(_T(Drawing line at %f,%f,%f), pStart.x, pStart.y, pStart.z);就能实时看到绘制触发时机。这比VS断点调试更贴近真实运行环境因为ARX插件是在AutoCAD进程内运行的很多状态如当前视口、活动空间只能在运行时获取。3. 核心细节解析与实操要点从“能编译”到“懂原理”的关键跃迁3.1 实体注册与类声明为什么必须重写acrxEntryPoint和kAcDbTestEntity在HPAcDbTestEntity.h中你会看到这样的宏定义#define kAcDbTestEntity _T(HPAcDbTestEntity)这个字符串不是随便起的它是实体在AutoCAD数据库中的“身份证号”。当acrxEntryPoint被调用时它执行acrxDynamicLinker-registerAppIdleTask(MyIdleTask); acrxDynamicLinker-unlockApplication(); acrxDynamicLinker-registerObject(kAcDbTestEntity, sizeof(HPAcDbTestEntity), AcRx::kDwgFile, AcRx::kDxfFile);这里registerObject的第三个参数AcRx::kDwgFile至关重要——它告诉AutoCAD“这个类的对象可以保存到DWG文件里”。如果漏掉这一步实体在保存图纸后会丢失如果填错成AcRx::kDxfFile则只能在DXF中存在。我踩过的坑是曾把kAcDbTestEntity写成_T(TestEntity)结果加载ARX后TESTCMD命令能执行但创建的实体在特性面板里显示为“未知对象”因为AutoCAD找不到匹配的类名。修复方法很简单在HPAcDbTestEntity.cpp的静态成员初始化处确保ACRX_CONS_DEFINE_MEMBERS宏的第一个参数与头文件中定义的kAcDbTestEntity完全一致ACRX_CONS_DEFINE_MEMBERS(HPAcDbTestEntity, AcDbLine, 1);这个宏不仅注册类还生成dwgInFields/dwgOutFields序列化函数让实体能被正确读写。初学者常忽略ACRX_CONS_DEFINE_MEMBERS的第三个参数版本号其实它控制着DWG兼容性设为1表示兼容所有支持该ARX SDK的AutoCAD版本设为2则可能在旧版中无法加载。3.2 图形显示回调subWorldDraw与subViewportDraw的分工奥秘AutoCAD的显示引擎分两层世界坐标系World Draw负责几何计算与模型空间绘制视口坐标系Viewport Draw负责屏幕像素映射与布局空间绘制。HPAcDbTestEntity必须同时重写这两个函数否则会出现“模型空间能显示布局空间一片空白”的诡异现象。看subWorldDraw的典型实现Adesk::Boolean HPAcDbTestEntity::subWorldDraw(AcGiWorldDraw* pWd) const { AcGePoint3d start, end; getStartPoint(start); getEndPoint(end); pWd-geometry().line(start, end); // 关键调用AcGiGeometry::line return Adesk::kTrue; }这里pWd-geometry().line()是唯一安全的绘制方式。如果你试图用CDC或OpenGL直接绘图会破坏AutoCAD的Z缓冲和消隐计算导致直线永远在其他图形前面。而subViewportDraw则更精细Adesk::Boolean HPAcDbTestEntity::subViewportDraw(AcGiViewportDraw* pVd) const { AcGePoint3d start, end; getStartPoint(start); getEndPoint(end); // 将世界坐标转换为屏幕坐标 AcGePoint3d scrStart, scrEnd; pVd-worldToDevice(start, scrStart); pVd-worldToDevice(end, scrEnd); // 绘制抗锯齿直线 pVd-geometry().polyline(2, scrStart); return Adesk::kTrue; }注意worldToDevice()转换——这是布局空间绘制的核心。没有这步转换subViewportDraw画出的线会固定在屏幕左上角。另外polyline(2, scrStart)用两点绘制线段比line()更稳定因为line()在某些视口比例下可能因浮点精度丢失首尾点。3.3 夹点拖拽实现subGetGripPoints与subMoveGripPointsAt的联动机制让直线可拖拽本质是实现三个函数的协同-subGetGripPoints()告诉AutoCAD“我的夹点在哪”。对于直线它返回两个端点void HPAcDbTestEntity::subGetGripPoints( AcGePoint3dArray gripPoints, AcDbIntArray osnapModes, AcGeVector3dArray snapVectors, AcDbObjectId entityId) const { AcGePoint3d start, end; getStartPoint(start); getEndPoint(end); gripPoints.append(start); gripPoints.append(end); osnapModes.append(AcDb::kOsModeEnd); osnapModes.append(AcDb::kOsModeEnd); }subMoveGripPointsAt()响应拖拽动作。当用户拖动第一个夹点索引0时它修改起点拖动第二个索引1时修改终点void HPAcDbTestEntity::subMoveGripPointsAt( const AcDbIntArray indices, const AcGeVector3d offset, const AcDbVoidPtrArray extraData) { if (indices.length() 0) return; AcGePoint3d start, end; getStartPoint(start); getEndPoint(end); for (int i 0; i indices.length(); i) { if (indices[i] 0) { // 第一个夹点移动起点 start offset; setStartPoint(start); } else if (indices[i] 1) { // 第二个夹点移动终点 end offset; setEndPoint(end); } } }subGetGeomExtents()更新图形范围。每次拖拽后AutoCAD需要重新计算实体包围盒以触发重绘Adesk::Boolean HPAcDbTestEntity::subGetGeomExtents(AcDbExtents extents) const { AcGePoint3d start, end; getStartPoint(start); getEndPoint(end); extents.addPoint(start); extents.addPoint(end); return Adesk::kTrue; }这三个函数构成闭环subGetGripPoints暴露夹点 → 用户拖动触发subMoveGripPointsAt→ 修改坐标后调用subGetGeomExtents通知范围变化 → AutoCAD调用subWorldDraw重绘。我实测发现如果忘记在subMoveGripPointsAt末尾调用subGetGeomExtents拖拽后直线会“残影”因为AutoCAD不知道包围盒已变不会主动刷新。3.4 数据库存储与持久化dwgInFields/dwgOutFields的序列化契约自定义实体要能保存到DWG文件必须实现dwgInFields读取和dwgOutFields写入函数。它们不是可选的而是ARX强制的序列化契约。看HPAcDbTestEntity.cpp中的实现Adesk::UInt32 HPAcDbTestEntity::dwgOutFields(AcDbDwgFiler* pFiler) const { assertReadEnabled(); AcDbLine::dwgOutFields(pFiler); // 先调用父类序列化 pFiler-writePoint3d(getStartPoint()); // 写入起点 pFiler-writePoint3d(getEndPoint()); // 写入终点 return Acad::eOk; } Adesk::UInt32 HPAcDbTestEntity::dwgInFields(AcDbDwgFiler* pFiler) { assertWriteEnabled(); AcDbLine::dwgInFields(pFiler); // 先读取父类数据 AcGePoint3d start, end; pFiler-readPoint3d(start); pFiler-readPoint3d(end); setStartPoint(start); setEndPoint(end); return Acad::eOk; }关键点在于必须先调用父类的dwgInFields/dwgOutFields。因为AcDbLine本身也有自己的数据如线型、颜色如果跳过这步加载DWG时父类字段会是默认值如颜色为BYLAYER线型为CONTINUOUS。另一个陷阱是assertReadEnabled()和assertWriteEnabled()宏——它们在Debug模式下检查对象状态如果在错误时机如对象未完全构造调用序列化函数会直接弹出断言失败对话框。生产环境建议用#ifdef _DEBUG包裹这些断言避免干扰用户。4. 实操过程与核心环节实现从零编译到验证拖拽的完整流水线4.1 环境准备VC6.0 ObjectARX SDK的精准匹配不要试图用VS2019编译这个工程——它会失败而且失败原因极其隐蔽。正确的环境组合是VC6.0 SP6 ObjectARX 2007 SDK对应AutoCAD 2007。为什么是2007因为资源包中的.dsp文件明确指定了/MT多线程静态链接和/GX异常处理编译选项这些是VC6时代的标准。安装步骤如下1. 安装VC6.0 SP6微软已停止支持但ARX SDK官网仍提供离线包2. 下载ObjectARX 2007 SDK解压到C:\ObjectARX 20073. 在VC6中配置包含路径Tools → Options → Directories → Include files添加-C:\ObjectARX 2007\inc-C:\ObjectARX 2007\inc\acad-C:\ObjectARX 2007\inc\acdb4. 配置库路径Tools → Options → Directories → Library files添加-C:\ObjectARX 2007\lib\Win325. 关键一步在Project → Settings → C/C → Preprocessor中定义预处理器宏-ACRX_NO_ACAD_LIB禁用AutoCAD库链接避免冲突-ARX_NO_DLL强制静态链接提示如果编译时报错acdb18.dll not found说明SDK版本与AutoCAD不匹配。ObjectARX 2007 SDK只能用于AutoCAD 2007不能混用2008或2010。验证方法打开AutoCAD输入ABOUT命令查看“产品信息”中的版本号。4.2 工程编译解决.def导出与.dsp配置的经典问题打开TestEntity.dsw工作区选择TestEntity - Win32 Release配置。编译前必做三件事1.检查.def文件右键TestEntity.def→Settings确认Output file name为TestEntity.arx且Module definition file路径正确2.修正.dsp链接器设置Project → Settings → Link在Object/library modules中确保包含-acdb18.lib数据库核心-acrx18.lib运行时扩展-acgi18.lib图形接口-acad.libAutoCAD主库3.处理重复文件资源包中rxdebug.cpp和StdAfx.cpp各出现两次删除重复项只保留一份。编译时最常见的错误是LNK2001: unresolved external symbol acrxEntryPoint。这是因为.def文件未被正确识别。解决方案Project → Settings → Link → Input在Module definition file框中手动输入TestEntity.def的绝对路径如C:\ARX\TestEntity\TestEntity.def而非相对路径。4.3 ARX加载与命令验证TESTCMD的全流程执行编译成功后生成TestEntity.arx文件。加载步骤1. 启动AutoCAD 20072. 输入APPLOAD命令打开“加载/卸载应用程序”对话框3. 点击Browse定位到TestEntity.arx点击Load4. 查看命令行应显示TestEntity.arx successfully loaded.5. 输入TESTCMD回车6. 在绘图区任意位置点击两次创建一条直线7. 选中该直线观察两端出现蓝色夹点8. 拖动任一夹点直线实时变形。注意如果TESTCMD命令不存在检查TestCmdCommands.cpp中是否遗漏了acedRegCmds-addCommand()调用。标准写法是cpp void TestCmdCommands::init() { acedRegCmds-addCommand(_T(TESTCMD_GROUP), _T(TESTCMD), _T(TESTCMD), ACRX_CMD_TRANSPARENT, TestCmd::command); }这里TESTCMD_GROUP是命令组名可任意TESTCMD是命令名ACRX_CMD_TRANSPARENT表示透明命令可在其他命令执行中调用。4.4 调试技巧用rxdebug捕获运行时状态rxdebug.cpp是这个工程的灵魂工具。它重载了acedPrintf让调试信息直接出现在AutoCAD命令行。在关键位置插入日志- 在TestCmd::command()开头加rxdebug::printf(_T(TESTCMD triggered));- 在HPAcDbTestEntity::subWorldDraw中加rxdebug::printf(_T(Drawing line from %f,%f to %f,%f), start.x, start.y, end.x, end.y);- 在subMoveGripPointsAt中加rxdebug::printf(_T(Moving grip %d by %f,%f,%f), indices[i], offset.x, offset.y, offset.z);这样当你拖拽直线时命令行会实时滚动输出坐标变化比VS断点更直观。我常用这个技巧排查“拖拽无响应”问题如果命令行没输出Moving grip日志说明subMoveGripPointsAt根本没被调用问题出在夹点索引或实体注册上如果日志有输出但直线不动则检查setStartPoint/setEndPoint是否真的修改了成员变量需确认m_startPoint和m_endPoint是私有成员且被正确赋值。5. 常见问题与排查技巧实录那些文档里不会写的“血泪经验”5.1 编译错误速查表错误代码现象根本原因解决方案LNK2001: unresolved external symbol acrxEntryPoint链接失败找不到入口函数.def文件未被识别或acrxEntryPoint函数签名错误检查.def路径是否绝对确认acrxEntryPoint声明为extern C AcRx::AppRetCode acrxEntryPoint(AcRx::AppMsgCode, void*)C2065: AcDbObjectId : undeclared identifier头文件找不到类型StdAfx.h中未包含acdb.h或#include acdb.h顺序错误在StdAfx.h顶部添加#include acdb.h确保在#include aced.h之前C2664: AcDbObjectId::operator : cannot convert parameter 1 from AcDbObjectId * to const AcDbObjectId 对象ID赋值错误将指针当对象用如objId newObjId改为objId newObjIdAcDbObjectId是值类型无需取地址error RC2135: file not found: rxdebug.rc资源编译失败rxdebug.rc文件缺失或路径错误从资源包中复制rxdebug.rc到工程目录或在.dsp中移除资源编译项5.2 运行时问题排查指南问题1加载ARX后TESTCMD命令不可用- 排查步骤1. 输入ARX命令查看已加载模块列表确认TestEntity.arx在其中2. 输入?命令查看所有可用命令搜索TESTCMD3. 如果不在列表中检查TestCmdCommands::init()是否被调用——它必须在acrxEntryPoint中显式调用4. 如果init()被调用但命令仍无效用rxdebug在init()开头加日志确认函数执行。问题2直线能创建但无法选中- 根本原因实体未正确注册到数据库或subGetGsMarker未重写。- 解决方案- 在HPAcDbTestEntity.h中添加ACRX_NO_CALLED宏并重写subGetGsMarkercpp virtual Adesk::GsMarker subGetGsMarker() const { return m_gsMarker; }- 在构造函数中初始化m_gsMarker如m_gsMarker 1;这是AutoCAD图形系统识别实体的标记。问题3拖拽夹点时直线消失或错位- 常见陷阱subMoveGripPointsAt中修改坐标后未调用subGetGeomExtents更新包围盒导致AutoCAD认为实体已超出视图范围而裁剪。- 验证方法拖拽后输入REGEN命令如果直线重现证明是包围盒未更新此时在subMoveGripPointsAt末尾添加cpp AcDbExtents extents; subGetGeomExtents(extents);问题4保存DWG后自定义直线变成“未知对象”- 根本原因dwgInFields/dwgOutFields未正确实现或ACRX_CONS_DEFINE_MEMBERS宏参数错误。- 检查清单-dwgOutFields中是否调用了父类序列化-dwgInFields中是否按相同顺序读取了所有字段-ACRX_CONS_DEFINE_MEMBERS的第三个参数版本号是否与dwgOutFields的版本匹配5.3 实操心得那些让开发效率翻倍的“小动作”夹点索引调试法在subGetGripPoints中给每个夹点附加唯一ID便于追踪cpp gripPoints.append(start); osnapModes.append(AcDb::kOsModeEnd); extraData.append((void*)1); // 标记为起点 gripPoints.append(end); osnapModes.append(AcDb::kOsModeEnd); extraData.append((void*)2); // 标记为终点这样在subMoveGripPointsAt中可通过extraData判断拖动的是哪个端点比硬编码索引更健壮。坐标系转换缓存worldToDevice()调用开销较大如果subViewportDraw中需多次转换可提前缓存转换矩阵cpp AcGeMatrix3d viewXform; pVd-viewingMatrix(viewXform); // 获取视图变换矩阵 AcGePoint3d scrStart start.transformBy(viewXform);调试信息分级在rxdebug.h中定义不同级别日志cpp #define DEBUG_INFO 1 #define DEBUG_WARN 2 #define DEBUG_ERROR 3 void printf(int level, LPCTSTR format, ...);这样在发布版本中可关闭DEBUG_INFO只保留错误日志避免命令行刷屏。实体命名规范kAcDbTestEntity字符串建议包含公司缩写如kAcDbMyCompanyTestEntity避免与其他插件冲突。ARX允许同名类共存但会覆盖前一个注册的导致不可预知行为。6. 扩展思考从这条直线出发你能构建什么这条自定义直线绝不是终点而是ARX开发的“Hello World”式起点。当我第一次让它稳定拖拽后我立刻做了三件事第一在subWorldDraw中添加pWd-geometry().circle()绘制端点圆圈让夹点更醒目第二重写subGetOsnapPoints支持中点捕捉让直线能参与几何约束第三将HPAcDbTestEntity改为模板类支持传入任意AcGeCurve派生类瞬间升级为“通用曲线实体框架”。这些扩展都不需要新学概念只是把现有模块像乐高一样拼接。比如添加中点捕捉只需在subGetOsnapPoints中计算中点并追加AcGePoint3d mid (start end) * 0.5; snapPoints.append(mid); osnapModes.append(AcDb::kOsModeMid);而支持约束则需监听AcDbDatabaseReactor的objectModified事件在端点变化时触发约束求解器。你会发现所有高阶功能都是这条直线的“子集”显示是subWorldDraw的增强选择是subGetGsMarker的细化拖拽是subMoveGripPointsAt的泛化。所以别急着跳进复杂项目先把这条直线的每一行代码都敲进脑子里——当你能闭着眼写出dwgInFields的序列化逻辑当你能凭直觉判断subViewportDraw是否需要坐标转换你就真正拿到了ObjectARX开发的钥匙。这把钥匙能打开的不只是CAD插件的大门更是所有需要深度集成的专业软件二次开发之门。本文还有配套的精品资源点击获取简介这个资源包提供一个可在AutoCAD中直接编译运行的ObjectARX C工程实现最简自定义直线实体支持图形显示、鼠标点击选中、夹点拖拽修改端点位置。工程包含完整可运行代码如HPAcDbTestEntity.cpp/.h定义实体类TestEntity.cpp实现几何绘制与数据库存储逻辑rxdebug.cpp提供调试支持TestCmdCommands.cpp和TestCmd.cpp完成命令注册与入口调用。所有文件基于VC6.0传统ARX开发环境组织含.def模块定义、.dsp/.dsw项目配置、StdArx.h/AdskDMgr.h等标准头文件引用不依赖第三方框架或封装层。编译后生成ARX插件加载到AutoCAD即可执行TESTCMD命令创建该自定义直线验证其显示、选择、拖拽、重生成等基础交互行为。适合刚接触ObjectARX开发的用户理解自定义实体从注册、几何表达、显示回调、选择集响应到数据库持久化的全流程实现细节。本文还有配套的精品资源点击获取