#include "mcp_ConfigParser.h" #include "cadsdk.h" #include #include "mcp_tool.h" #include "mcp_utils.h" #include "ghc/filesystem.hpp" #include "rxregsvc.h" #include #include #include #include #include namespace { // 已加载的插件 DLL 缓存:规范化路径 -> 模块句柄。 // 同一个 DLL 只 LoadLibrary 一次;后续工具直接复用句柄,并跳过版本校验。 std::map g_loadedDllModules; // 统一卸载缓存中的所有 DLL。必须在所有 mcp_Tool_DllProxy 销毁之后再调用, // 否则 DLL 已卸载而代理仍持有其工具对象指针,析构时会崩溃。 void unloadAllDllModules() { for (auto &kv : g_loadedDllModules) { if (kv.second) { FreeLibrary(kv.second); } } g_loadedDllModules.clear(); } // 诊断日志:仅记录版本校验时的两个数值(排查版本不一致问题) // 每次重新加载工具时清空一次,便于只观察本次的结果。 void mcpLoadReset() { std::wstring lp = (mcp_utils::getModelDirPath() / L"mcp_load.log").wstring(); std::ofstream ofs(lp.c_str(), std::ios::trunc); } void mcpLoadLog(const std::string &msg) { std::wstring lp = (mcp_utils::getModelDirPath() / L"mcp_load.log").wstring(); std::ofstream ofs(lp.c_str(), std::ios::app); if (ofs.is_open()) { ofs << msg << "\n"; } } } #pragma region mcp_ConfigParser bool mcp_ConfigParser::LoadAndRegister(const std::wstring &jsonFilePath, mcp_Tools ®istry) { mcpLoadReset(); std::ifstream ifs(jsonFilePath); if (!ifs.is_open()) { return false; } nlohmann::json config; try { ifs >> config; } catch (...) { return false; } // 防御非法 JSON fs::path pluginPath = mcp_utils::relative2absolute(config["global_settings"].value("plugin_directory", "./mcp_plugins")); fs::path lispPath = mcp_utils::relative2absolute(config["global_settings"].value("lisp_directory", "./mcp_lisp")); if (!fs::exists(lispPath)) AfxMessageBox(CString(_T("[MCP Server]Warning: The LISP script directory does not exist; the associated tools will not load!\n")) + pluginPath.c_str()); if (!fs::exists(pluginPath)) AfxMessageBox(CString(_T("[MCP Server]Warning: The plugin DLL directory does not exist; the associated tools will not load!\n")) + pluginPath.c_str()); for (const auto &toolConfig : config["tools"]) { std::string type = toolConfig["backend"].value("type", ""); if (type == "lisp_inline") { auto pTool = std::make_shared(); pTool->SetConfig(toolConfig); registry.Register(pTool); } else if (type == "lisp_file" && fs::exists(lispPath)) { auto pTool = std::make_shared(lispPath); pTool->SetConfig(toolConfig); registry.Register(pTool); } else if (type == "c++_dll" && fs::exists(pluginPath)) { LoadDllTool(toolConfig, pluginPath, registry); } } return true; } void mcp_ConfigParser::LoadDllTool(const nlohmann::json &toolConfig, const std::wstring &pluginDir, mcp_Tools ®istry) { fs::path dllPath = mcp_utils::relative2absolute(toolConfig["backend"].value("dll_name", "") , fs::path(pluginDir)); std::string factoryName = toolConfig["backend"].value("factory_function", ""); if (!fs::exists(dllPath)) { AfxMessageBox(CString(_T("[MCP Server]Warning: The DLL file does not exist, the associated tools will not load!\n")) + dllPath.c_str()); return; } CString verStr; verStr.Format(_T("Major ver:%d, Minor ver:%d"), ARX, SUB_ARX); CString ver_errMsg; ver_errMsg.Format( CString(_T("[MCP Server]Error: Failed to load the DLL. ")) + CString(_T("Please check whether the version of the ObjectARX SDK used by ")) + CString("the DLL matches the current ARX version(%s).\n"), verStr); // 规范化 key(绝对路径,统一小写),用于判断该 DLL 是否已经加载过 std::wstring key = dllPath.wstring(); std::transform(key.begin(), key.end(), key.begin(), [](wchar_t c) { return (wchar_t)::towlower(c); }); HMODULE hMod = NULL; bool firstLoad = false; auto it = g_loadedDllModules.find(key); if (it != g_loadedDllModules.end()) { // 该 DLL 已加载:直接复用句柄,不再重复 LoadLibrary,也不再做版本校验 hMod = it->second; } else { hMod = LoadLibrary(dllPath.c_str()); if (!hMod) { AfxMessageBox(ver_errMsg); return; } g_loadedDllModules[key] = hMod; firstLoad = true; } // 版本校验:仅在该 DLL 首次加载时执行一次 if (firstLoad) { typedef int (*GetApiVersionFunc)(); GetApiVersionFunc getVerFn = (GetApiVersionFunc)GetProcAddress(hMod, "acrxGetApiVersion"); if (!getVerFn) { // 连这个函数都没导出,说明根本不是合法的 ObjectARX/MCP 插件 AfxMessageBox(CString( CString(_T("The ObjectARX version of the DLL could not be determined.")) + CString(_T("Please ensure that the acrxGetApiVersion function is exported.")) )); FreeLibrary(hMod); g_loadedDllModules.erase(key); return; } // 基座自身版本取「编译期常量」,它稳定可靠; // 不能依赖运行时的 acrxGetApiVersion()——该函数由 rxapi.lib 中的有状态实现提供, // 其返回值会随调用次数变化(首次为正确值,之后被进程内全局污染),无法用于比对。 // 返回值编码为 (major << 16) | minor:高 16 位=大版本,低 16 位=小版本。 const int arx_version = (ARX << 16) | SUB_ARX; const int arx_major = ARX; const int arx_minor = SUB_ARX; const int dll_version = getVerFn(); const int dll_major = (dll_version >> 16) & 0xFFFF; const int dll_minor = dll_version & 0xFFFF; mcpLoadLog("arx_version=" + std::to_string(arx_version) + " dll_version=" + std::to_string(dll_version) + " (arx " + std::to_string(arx_major) + "." + std::to_string(arx_minor) + " / dll " + std::to_string(dll_major) + "." + std::to_string(dll_minor) + ")"); // 兼容性判定: // - 同一大版本内通常二进制兼容(如 R24.0/R24.1/R24.2 可共用同一 ARX),故默认只比大版本; // - 唯一例外是 R24.3:它换了 MSVC toolset,与 R24.0~R24.2 不兼容,需单独分组比较。 bool compatible = false; if (dll_major == arx_major) { if (arx_major == 24) compatible = ((arx_minor >= 3) == (dll_minor >= 3)); // 24.3+ 与 24.0~24.2 互不兼容 else compatible = true; } if (!compatible) { // 平台不兼容,强行加载必定闪崩,果断踢掉整个模块! FreeLibrary(hMod); g_loadedDllModules.erase(key); AfxMessageBox(ver_errMsg); return; } } typedef mcp_Tool *(*CreateFunc)(); typedef void (*DestroyFunc)(mcp_Tool *); std::string destroyName = "Destroy_" + factoryName; CreateFunc createFn = (CreateFunc)GetProcAddress(hMod, factoryName.c_str()); DestroyFunc destroyFn = (DestroyFunc)GetProcAddress(hMod, destroyName.c_str()); if (createFn && destroyFn) { mcp_Tool *pRealTool = createFn(); if (pRealTool) { // 给真正的 DLL 实例也注入配置,以防第三方需要在内部读取 std::string cfgStr = toolConfig.dump(); pRealTool->SetConfigAbiSafe(cfgStr.c_str()); // 实例化代理类,接管工具对象的生命周期(模块句柄由 g_loadedDllModules 统一管理) auto pProxy = std::make_shared(pRealTool, destroyFn); // 代理类继承自基类,也需要配置来处理 GetName 和 ToLLM pProxy->SetConfig(toolConfig); registry.Register(pProxy); } // 工厂返回空:只跳过本工具,不卸载模块(同一个 DLL 的其它工具可能仍然可用) } // 工厂/销毁函数缺失:同样只跳过本工具,不卸载模块 } #pragma endregion mcp_ConfigParser #pragma region mcp_Tools // 注册工具 void mcp_Tools::Register(std::shared_ptr pTool) { if (pTool != nullptr) { m_tools[pTool->GetName()] = pTool; } } // 组装所有工具的描述,返回 JSON 数组 (用于 tools/list) nlohmann::json mcp_Tools::ToLLM() const { nlohmann::json arr = nlohmann::json::array(); for (const auto &pair : m_tools) { arr.push_back(pair.second->ToLLM()); } return arr; } // 路由分发执行指令 (用于 tools/call) nlohmann::json mcp_Tools::Call(const std::string &name, const nlohmann::json &args) { auto it = m_tools.find(name); if (it != m_tools.end()) { return it->second->Execute(args); } // 找不到工具时,返回标准的 JSON-RPC 错误对象 return mcp_Tool_Utility::make_error(-32601, L"Specified tool not found: " + mcp_utils::utf8ToWString(name)); } void mcp_Tools::print() { for (const auto &pair : m_tools) { acutPrintf(_T("Tool: %s Already registered.\n"), mcp_utils::utf8ToWString(pair.first.c_str()).c_str()); } } void mcp_Tools::clear() { m_tools.clear(); // 先销毁所有代理(代理不再 FreeLibrary,仅释放 DLL 内部工具对象) unloadAllDllModules(); // 所有代理销毁完毕后,再统一卸载缓存的 DLL 模块 } #pragma endregion mcp_Tools