- 新增 ai.memory/project_mcp_frame_dll_loader.md: 1) 运行时 acrxGetApiVersion() 有状态、不可用于版本比对; 2) DLL 工具写数据库需文档锁(应用上下文 → eLockViolation); 3) 同一 DLL 只加载一次(模块缓存)及代理不再 FreeLibrary 的原因。 - 更新索引 MEMORY.md;reference_environment.md 的 c++_dll 说明改为「只加载一次」 并指向新文件,测试脚本表补 verify_annotate.py。 - 修正 feedback_preferences.md 中 ai.memory「尚未入库」的过时说明。
3.6 KiB
3.6 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<<16)|SUB_ARX;不要把运行时acrxGetApiVersion()的返回值当版本用。实现见mcp_configParser.cpp::LoadDllTool。
坑 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)。属临时诊断,稳定后可删。