Files
cad-agent/ai.memory/project_mcp_frame_dll_loader.md
T
gjm 0b4ffdec0c chore(memory): 沉淀基座 c++_dll 加载/执行的排查结论
- 新增 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「尚未入库」的过时说明。
2026-10-09 22:37:31 +08:00

3.6 KiB
Raw Blame History

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)。属临时诊断,稳定后可删。