2fa27b1 只带上了文档,未纳入源文件。此处补上 mcp_ConfigParser.cpp 的实际改动:
- acrxGetApiVersion 返回值按 (major<<16)|minor 拆分大/小版本;
- 默认只比大版本(同一大版本内二进制兼容);
- 例外 R24.3 与 R24.0~R24.2 因 MSVC toolset 不同,按 minor>=3 分组判定。
278 lines
8.8 KiB
C++
278 lines
8.8 KiB
C++
#include "mcp_ConfigParser.h"
|
|
#include "cadsdk.h"
|
|
#include <windows.h>
|
|
#include "mcp_tool.h"
|
|
#include "mcp_utils.h"
|
|
#include "ghc/filesystem.hpp"
|
|
#include "rxregsvc.h"
|
|
#include <fstream>
|
|
#include <cstdint>
|
|
#include <map>
|
|
#include <algorithm>
|
|
#include <cwctype>
|
|
|
|
namespace
|
|
{
|
|
// 已加载的插件 DLL 缓存:规范化路径 -> 模块句柄。
|
|
// 同一个 DLL 只 LoadLibrary 一次;后续工具直接复用句柄,并跳过版本校验。
|
|
std::map<std::wstring, HMODULE> 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<mcp_Tool_LispInline>();
|
|
pTool->SetConfig(toolConfig);
|
|
registry.Register(pTool);
|
|
}
|
|
else if (type == "lisp_file" && fs::exists(lispPath))
|
|
{
|
|
auto pTool = std::make_shared<mcp_Tool_LispFile>(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<mcp_Tool_DllProxy>(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<mcp_Tool> 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
|
|
|
|
|