- acrxGetApiVersion 返回值编码为 (major<<16)|minor,据此拆分大/小版本 (实测 R22.0=0x160000、R23.0=0x170000)。 - 同一大版本内二进制兼容(R24.0/R24.1/R24.2 可共用 ARX;2019/R23.0 编的 ARX 可跑在 2020/R23.1 上),故默认只比大版本,避免为每个小版本各编一份。 - 唯一例外 R24.3:换了 MSVC toolset,与 R24.0~R24.2 不兼容, 对 major==24 再按 minor>=3 分组判定。 - 同步更新 CODEBUDDY.md 与 ai.memory 说明。
4.4 KiB
4.4 KiB
name, description, type
| name | description | type |
|---|---|---|
| MCP 基座 c++_dll 加载与执行的坑 | CadProject 基座(cad_mcp_frame)加载/执行 c++_dll 工具的两个非显而易见坑——运行时 acrxGetApiVersion 有状态不可用于版本比对、写数据库需文档锁——及「同一 DLL 只加载一次」的模块缓存决定 | project |
MCP 基座 c++_dll 加载与执行的坑
来源:2026-10-09 排查「配置 26 个工具、DLL 导出 24 个,却只注册 9 个」的结论。 相关提交:
fix(mcp_frame): 修复 c++_dll 工具大量注册失败与写库被文档锁拦截。
坑 1:运行时 acrxGetApiVersion() 不能用于版本比对
- 事实:
acrxGetApiVersion()在基座与插件里都是rxapi.lib(成员libinit.obj)的acrxGetApiVersionImpl,有状态:它读/写一个进程内全局,并与入参寄存器 ECX 做 XOR 后 回写;首次调用返回硬编码0x170000(即(ARX<<16)|SUB_ARX,对应 R230),之后每次调用 返回值都被污染、随调用次数变化。 - 后果:AutoCAD 以 ARX 加载基座时会先调用一次该函数;此后基座再调用得到的是脏值,
而 plugins DLL(
LoadLibrary加载、无人调用过)首次返回正确值 → 两者不等 → 绝大多数 DLL 工具被判为「版本不匹配」而被FreeLibrary跳过(表现为只注册 9/26)。 - How to apply:基座侧版本一律用编译期常量
ARX/SUB_ARX;不要把运行时acrxGetApiVersion()的返回值当版本用。实现见mcp_configParser.cpp::LoadDllTool。
返回值编码 + 「只比大版本」的兼容判定
- 返回值 =
(major << 16) | minor(大版本=高 16 位,小版本=低 16 位)。实测(反汇编各版本 SDK 的rxapi.lib):R22.0 →0x160000(0x16=22)、R23.0 →0x170000(0x17=23); 故 R23.1 →0x170001,R24.0 →0x180000。 - 判定规则:默认只比大版本——同一大版本内二进制兼容(如 R24.0/R24.1/R24.2 可共用同一
ARX;AutoCAD 2019=R23.0 编的 ARX 可跑在 2020=R23.1 上)。唯一例外是 R24.3:它换了
MSVC toolset,与 R24.0~R24.2 不兼容,故对
major==24再按minor>=3分一次组。 - 若「大小版本全比」,会误拒用同大版本其它小版本 SDK 编的 DLL,逼着为每个小版本各编一份,浪费。
坑 2:DLL 工具写数据库必须先锁文档
- 事实:工具执行链
TcpServerLoop → PostMessage(WM_MCP_EXECUTE_TASK) → ExecuteTaskInMainThread运行在 AutoCAD 主线程的窗口消息处理里,此时处于「应用上下文」。在应用上下文对数据库 做kForWrite打开会返回eLockViolation(表现为openWriteSpace拿不到模型空间,报cannot open target space for write);读操作不受影响,所以之前一直没暴露。 - How to apply:凡会写库的 DLL 工具都要锁文档。现已在
mcp_Tool_DllProxy::Execute(mcp_tool.cpp)统一加acDocManager->lockDocument(pDoc, AcAp::kWrite)/unlockDocument——一处覆盖所有 DLL 工具(含highlight_entities),且不波及走sendStringToExecute的 LISP 工具。 新增写库工具无需各自加锁,也不要在工具内部重复加锁。 (同级envi-code工程在对话框里写库也是lockDocument(pDoc, AcAp::kWrite)这个写法。)
架构决定:同一 DLL 只加载一次(模块缓存)
mcp_configParser.cpp用g_loadedDllModules(规范化路径 → 句柄)缓存:同一 DLL 全进程 只LoadLibrary一次,后续工具复用句柄并跳过版本校验;mcp_Tools::clear()时统一FreeLibrary。- Why:一个 DLL 常导出多个工具,原实现每个工具都
LoadLibrary+ 版本校验,既重复,又放大 了坑 1(每次校验都调用一次有状态的版本函数)。 - How to apply:
mcp_Tool_DllProxy已不再持有/释放模块句柄,只负责销毁 DLL 内的工具 对象。若改回「每个工具各持一个引用」,必须保证LoadLibrary/FreeLibrary次数配平, 否则模块会被提前卸载、代理析构时调用已卸载代码而崩溃。
诊断日志
LoadDllTool仅在首次加载时写一行arx_version=X dll_version=Y到<模块目录>/mcp_load.log,每次加载清空重写(mcpLoadReset)。属临时诊断,稳定后可删。