From 0b4ffdec0cb03d752a09f67b6ceb3ff930f83ce1 Mon Sep 17 00:00:00 2001 From: LittleGuo Date: Fri, 9 Oct 2026 22:37:31 +0800 Subject: [PATCH] =?UTF-8?q?chore(memory):=20=E6=B2=89=E6=B7=80=E5=9F=BA?= =?UTF-8?q?=E5=BA=A7=20c++=5Fdll=20=E5=8A=A0=E8=BD=BD/=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E7=9A=84=E6=8E=92=E6=9F=A5=E7=BB=93=E8=AE=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 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「尚未入库」的过时说明。 --- ai.memory/MEMORY.md | 1 + ai.memory/feedback_preferences.md | 4 +- ai.memory/project_mcp_frame_dll_loader.md | 51 +++++++++++++++++++++++ ai.memory/reference_environment.md | 4 +- 4 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 ai.memory/project_mcp_frame_dll_loader.md diff --git a/ai.memory/MEMORY.md b/ai.memory/MEMORY.md index f6882fc..a6cf1bf 100644 --- a/ai.memory/MEMORY.md +++ b/ai.memory/MEMORY.md @@ -5,5 +5,6 @@ > 组织方式:本文件是索引(一行一条),各主题内容放在独立文件中。 - [CADProject 图纸审查 Agent 工作](project_dwg_review_agent.md) — 基于 MCP 框架的图纸审查工具集与逐图框审查流程 +- [MCP 基座 c++_dll 加载与执行的坑](project_mcp_frame_dll_loader.md) — acrxGetApiVersion 有状态不可比对、写库需文档锁、同一 DLL 只加载一次 - [用户协作偏好](feedback_preferences.md) — 调试用 Debug/R230、模型 deepseek-flash、DLL 集中在 cad_mcp_plugins 等 - [环境与外部资源](reference_environment.md) — 构建/运行要点、CAD SDK 路径、测试脚本 diff --git a/ai.memory/feedback_preferences.md b/ai.memory/feedback_preferences.md index 06a5970..9588ffa 100644 --- a/ai.memory/feedback_preferences.md +++ b/ai.memory/feedback_preferences.md @@ -60,8 +60,8 @@ type: feedback 提交并推送到远程服务器(`cad-agent.git`),以便 AI 在其它机器上接手记忆。 - **Why:** 用户希望记忆可跨机器共享/接管。 - **How to apply:** 不要建议把 `ai.memory/` 加入 `.gitignore`;提交/推送时正常包含它。 - 截至 **2026-10-08** 用户表示「今天就算了」,故暂未加入/推送——后续时机由用户决定。 -- 背景:`CadProject` 已独立为单独仓库(远程 `cad-agent.git`);`ai.memory/` 目前仍是未跟踪状态。 +- 现状:`ai.memory/` **已入库**(提交 `chore(memory): 新增 ai.memory 记忆库`),随 `home-ai-dev` + 正常提交并推送。(`CadProject` 已独立为单独仓库,远程 `cad-agent.git`。) ## 【重要】新建的 C++ 源文件必须存为 **UTF-8 with BOM** diff --git a/ai.memory/project_mcp_frame_dll_loader.md b/ai.memory/project_mcp_frame_dll_loader.md new file mode 100644 index 0000000..d230c50 --- /dev/null +++ b/ai.memory/project_mcp_frame_dll_loader.md @@ -0,0 +1,51 @@ +--- +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`)。属临时诊断,稳定后可删。 diff --git a/ai.memory/reference_environment.md b/ai.memory/reference_environment.md index f008844..b97b48f 100644 --- a/ai.memory/reference_environment.md +++ b/ai.memory/reference_environment.md @@ -34,6 +34,7 @@ type: reference | `test_autoagent.py` | 拉一次视口截图存 `test_screenshot.png` | | `countOfCircle.py` | 伪造 `zoom_window_normalized` 调用,验证 TCP 链路 | | `agent_loop.py` | 完整 Agent 循环(多回合),需 openai 包与 API Key | +| `verify_annotate.py` | 批注工具运行时验证(绘制/校验/改属性/清理);`--keep` 保留批注 | ## 两个构建目标 @@ -46,7 +47,8 @@ type: reference - `lisp_inline`:`{占位符}` 替换后 `sendStringToExecute`(**异步**)。 - `lisp_file`:`(load "文件" nil)` 后执行 `call_template`。 -- `c++_dll`:`LoadLibrary` + `acrxGetApiVersion` 版本校验 + 工厂/销毁函数(**同步**,可返回图像)。 +- `c++_dll`:`LoadLibrary`(**同一 DLL 只加载一次**)+ 工厂/销毁函数(**同步**,可返回图像)。 + 版本校验与「写库需文档锁」两个坑见 [MCP 基座 c++_dll 加载与执行的坑](project_mcp_frame_dll_loader.md)。 ## 远程仓库 -- 2.54.0