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

52 lines
3.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: MCP 基座 c++_dll 加载与执行的坑
description: CadProject 基座(cad_mcp_frame)加载/执行 c++_dll 工具的两个非显而易见坑——运行时 acrxGetApiVersion 有状态不可用于版本比对、写数据库需文档锁——及「同一 DLL 只加载一次」的模块缓存决定
type: 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`)。属临时诊断,稳定后可删。