Add metadata validation script and module documentation
- Introduced a new Python script `check_agents_metadata.py` for validating agent metadata, including YAML parsing, rating ranges, and cross-references. - Added usage instructions and exit codes for the script. - Created a new markdown file `模块目录和功能说明.md` to outline the directory structure and functionality of the modules. - Added a text file `说明此文件不可AI修改.txt` to specify that certain files should not be modified by AI, including important information about the `dstalk` framework and its modules.
This commit is contained in:
@@ -1,3 +1,10 @@
|
||||
/**
|
||||
* @file dstalk_host.h
|
||||
* @brief Host API declarations: plugin lifecycle, service registry, event bus, config, logging, memory.
|
||||
* 主机 API 声明:插件生命周期、服务注册表、事件总线、配置、日志、内存管理。
|
||||
* Copyright (c) 2026 dstalk contributors. GPLv3.
|
||||
*/
|
||||
|
||||
#ifndef DSTALK_HOST_H
|
||||
#define DSTALK_HOST_H
|
||||
|
||||
@@ -8,7 +15,7 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// === 平台导出宏 ===
|
||||
/* ---- 平台导出宏 / Platform export macros ---- */
|
||||
#ifndef DSTALK_API
|
||||
#if defined(_WIN32)
|
||||
#ifdef DSTALK_BUILD_DLL
|
||||
@@ -21,21 +28,23 @@ extern "C" {
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// === 插件导出宏 ===
|
||||
/* ---- 插件导出宏 / Plugin export macro ---- */
|
||||
#if defined(_WIN32)
|
||||
#define DSTALK_PLUGIN_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define DSTALK_PLUGIN_EXPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
// === API 版本 ===
|
||||
#define DSTALK_API_VERSION 1
|
||||
#define DSTALK_MAX_DEPS 8
|
||||
/* ---- API 版本常量 / API version constants ---- */
|
||||
#define DSTALK_API_VERSION 1 // 当前主机 API 版本,插件必须匹配 / current host API version plugins must match
|
||||
#define DSTALK_MAX_DEPS 8 // 插件可声明的最大依赖项数量 / maximum dependency entries a plugin can declare
|
||||
|
||||
// === 诊断 ===
|
||||
/* ---- 诊断回调 / Diagnostics callback ---- */
|
||||
/* 主机调用此回调用于断言失败和内部诊断 / Called by the host for assertion failures and internal diagnostics */
|
||||
typedef void (*dstalk_diag_cb)(int severity, const char* file,
|
||||
int line, const char* func, const char* message);
|
||||
|
||||
/* 断言宏: 当 expr 为假时记录错误并返回 retval / Assertion macro: logs error and returns retval if expr is false */
|
||||
#define DSTALK_ERROR_RETURN(expr, retval) do { \
|
||||
if (!(expr)) { \
|
||||
dstalk_log(DSTALK_LOG_ERROR, "[%s:%d] %s: assertion '%s' failed", \
|
||||
@@ -44,85 +53,107 @@ typedef void (*dstalk_diag_cb)(int severity, const char* file,
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/* 注册诊断回调用于内部错误报告 / Register a diagnostic callback for internal error reporting */
|
||||
DSTALK_API void dstalk_set_diag_callback(dstalk_diag_cb cb);
|
||||
|
||||
// === 事件处理器 ===
|
||||
/* ---- 事件处理器类型 / Event handler type ---- */
|
||||
/* 当已订阅的事件被触发时由主机调用 / Called by the host when a subscribed event is emitted */
|
||||
typedef void (*dstalk_event_handler_fn)(int event_type, const void* data, void* userdata);
|
||||
|
||||
// === Host 提供给插件的 API 表 ===
|
||||
/* ---- 主机 API vtable (传递给插件的 on_init) / Host API vtable (passed to plugin's on_init) ---- */
|
||||
typedef struct {
|
||||
// 服务注册/查询
|
||||
/* --- 服务注册表 / service registry --- */
|
||||
int (*register_service)(const char* name, int version, void* vtable);
|
||||
void*(*query_service)(const char* name, int min_version);
|
||||
|
||||
// 事件
|
||||
/* --- 事件总线 / event bus --- */
|
||||
int (*event_subscribe)(int event_type, dstalk_event_handler_fn handler, void* userdata);
|
||||
int (*event_emit)(int event_type, const void* data);
|
||||
void (*event_unsubscribe)(int sub_id);
|
||||
|
||||
// 配置
|
||||
/* --- 配置管理 / configuration --- */
|
||||
const char* (*config_get)(const char* key);
|
||||
int (*config_set)(const char* key, const char* value);
|
||||
|
||||
// 日志
|
||||
/* --- 日志记录 / logging --- */
|
||||
void (*log)(int level, const char* fmt, ...);
|
||||
|
||||
// 内存
|
||||
/* --- 内存管理 / memory management --- */
|
||||
void* (*alloc)(size_t size);
|
||||
void (*free)(void* ptr);
|
||||
char* (*strdup)(const char* s);
|
||||
} dstalk_host_api_t;
|
||||
|
||||
// === 插件信息结构 ===
|
||||
/* ---- 插件描述符 / Plugin descriptor ---- */
|
||||
/* 每个插件通过 dstalk_plugin_init() 导出此结构体 / Every plugin exports this via dstalk_plugin_init() */
|
||||
typedef struct {
|
||||
const char* name; // 插件名称(唯一标识)
|
||||
const char* version; // 语义化版本号,如 "1.0.0"
|
||||
const char* description; // 描述
|
||||
int api_version; // 必须 == DSTALK_API_VERSION
|
||||
const char* name; // 唯一插件标识符 / unique plugin identifier
|
||||
const char* version; // 语义版本号,如 "1.0.0" / semantic version, e.g. "1.0.0"
|
||||
const char* description; // 人类可读的描述信息 / human-readable description
|
||||
int api_version; // 必须等于 DSTALK_API_VERSION / must equal DSTALK_API_VERSION
|
||||
|
||||
// 依赖声明(以 NULL 结尾)
|
||||
/* null-terminated 依赖插件名称列表 / null-terminated list of dependency plugin names */
|
||||
const char* dependencies[DSTALK_MAX_DEPS];
|
||||
|
||||
// 生命周期回调
|
||||
/* 生命周期回调 / lifecycle callbacks */
|
||||
int (*on_init)(const dstalk_host_api_t* host);
|
||||
void (*on_shutdown)(void);
|
||||
|
||||
// 事件处理(可选)
|
||||
/* 可选: 事件总线上每个事件通过时调用 / optional: called for every event passing through the bus */
|
||||
void (*on_event)(int event_type, const void* data);
|
||||
} dstalk_plugin_info_t;
|
||||
|
||||
// === 插件入口函数 ===
|
||||
/* ---- 插件入口点 / Plugin entry point ---- */
|
||||
/* 每个共享库插件必须导出一个与此签名匹配的函数 / Every shared library plugin must export a function with this signature */
|
||||
typedef dstalk_plugin_info_t* (*dstalk_plugin_init_fn)(void);
|
||||
|
||||
// === Host 公共 API ===
|
||||
/* ========================================================================
|
||||
* 主机公共 API / Host public API
|
||||
* ======================================================================== */
|
||||
|
||||
// 初始化/销毁
|
||||
/* 使用给定的配置文件路径初始化 dstalk 主机 / Initialize the dstalk host with the given config file path */
|
||||
DSTALK_API int dstalk_init(const char* config_path);
|
||||
|
||||
/* 关闭主机: 卸载插件, 释放资源 / Shut down the host: unload plugins, free resources */
|
||||
DSTALK_API void dstalk_shutdown(void);
|
||||
|
||||
// 插件管理
|
||||
/* 从共享库路径加载插件; 返回 plugin_id, 出错返回 -1 / Load a plugin from a shared library path; returns plugin_id or -1 on error */
|
||||
DSTALK_API int dstalk_plugin_load(const char* path);
|
||||
|
||||
/* 按 id 卸载之前加载的插件 / Unload a previously loaded plugin by its id */
|
||||
DSTALK_API int dstalk_plugin_unload(int plugin_id);
|
||||
|
||||
/* 将已加载插件信息的 JSON 数组写入 *output_json (调用方释放) / Write a JSON array of loaded plugin info to *output_json (caller frees) */
|
||||
DSTALK_API int dstalk_plugin_list(char** output_json);
|
||||
|
||||
// 服务查询
|
||||
/* 按名称和最低版本号查找已注册的服务 vtable / Look up a registered service vtable by name and minimum version */
|
||||
DSTALK_API void* dstalk_service_query(const char* service_name, int min_version);
|
||||
|
||||
// 事件系统
|
||||
/* 为特定事件类型订阅处理器; 返回 subscription_id / Subscribe handler to a specific event type; returns subscription_id */
|
||||
DSTALK_API int dstalk_event_subscribe(int event_type, dstalk_event_handler_fn handler, void* userdata);
|
||||
|
||||
/* 向所有已订阅该类型事件的订阅者发送事件 / Emit an event to all subscribers of the given type */
|
||||
DSTALK_API int dstalk_event_emit(int event_type, const void* data);
|
||||
|
||||
/* 按 id 移除订阅 / Remove a subscription by its id */
|
||||
DSTALK_API void dstalk_event_unsubscribe(int subscription_id);
|
||||
|
||||
// 配置
|
||||
/* 通过键名获取配置值 (未找到返回 NULL) / Retrieve a config value by key (returns NULL if not found) */
|
||||
DSTALK_API const char* dstalk_config_get(const char* key);
|
||||
|
||||
/* 设置配置键值对; 成功返回 0 / Set a config key/value pair; returns 0 on success */
|
||||
DSTALK_API int dstalk_config_set(const char* key, const char* value);
|
||||
|
||||
// 日志
|
||||
/* 以给定严重等级记录日志消息 / Log a message at the given severity level */
|
||||
DSTALK_API void dstalk_log(int level, const char* fmt, ...);
|
||||
|
||||
// 内存
|
||||
/* 使用主机的内存分配器分配内存 / Allocate memory using the host's allocator */
|
||||
DSTALK_API void* dstalk_alloc(size_t size);
|
||||
|
||||
/* 释放之前由主机分配的内存 / Free memory previously allocated by the host */
|
||||
DSTALK_API void dstalk_free(void* ptr);
|
||||
|
||||
/* 使用主机的内存分配器复制 C 字符串 / Duplicate a C-string using the host's allocator */
|
||||
DSTALK_API char* dstalk_strdup(const char* s);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/**
|
||||
* @file dstalk_lsp.h
|
||||
* @brief Convenience C API for Language Server Protocol operations (delegates to "lsp" plugin).
|
||||
* LSP(语言服务器协议)操作的便捷 C API(委托给 "lsp" 插件)。
|
||||
* Copyright (c) 2026 dstalk contributors. GPLv3.
|
||||
*/
|
||||
|
||||
#ifndef DSTALK_LSP_H
|
||||
#define DSTALK_LSP_H
|
||||
|
||||
@@ -7,51 +14,51 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ---- LSP 服务器生命周期 ---- */
|
||||
/* ---- LSP 服务器生命周期 / LSP Server Lifecycle ---- */
|
||||
|
||||
/*
|
||||
* 启动语言服务器进程
|
||||
* server_cmd: 命令字符串,例如 "clangd" 或 "pyright --stdio" 或完整路径
|
||||
* language: 语言标识,例如 "c", "cpp", "python", "javascript", "rust"
|
||||
* returns: 0 成功, -1 失败
|
||||
* 启动语言服务器进程 / Start the language server process
|
||||
* server_cmd: 命令字符串,例如 "clangd" 或 "pyright --stdio" 或完整路径 / command string, e.g. "clangd" or "pyright --stdio" or full path
|
||||
* language: 语言标识,例如 "c", "cpp", "python", "javascript", "rust" / language identifier, e.g. "c", "cpp", "python", "javascript", "rust"
|
||||
* returns: 0 成功, -1 失败 / 0 success, -1 failure
|
||||
*/
|
||||
DSTALK_API int dstalk_lsp_start(const char* server_cmd, const char* language);
|
||||
|
||||
/*
|
||||
* 停止语言服务器
|
||||
* 发送 shutdown 请求,然后发送 exit 通知
|
||||
* 关闭管道,终止子进程
|
||||
* 停止语言服务器 / Stop the language server
|
||||
* 发送 shutdown 请求,然后发送 exit 通知 / sends shutdown request, then exit notification
|
||||
* 关闭管道,终止子进程 / closes pipes, terminates child process
|
||||
*/
|
||||
DSTALK_API void dstalk_lsp_stop(void);
|
||||
|
||||
/* ---- 文档管理 ---- */
|
||||
/* ---- 文档管理 / Document Management ---- */
|
||||
|
||||
/*
|
||||
* 在语言服务器中打开一个文档
|
||||
* uri: 文件 URI,例如 "file:///path/to/file.c"
|
||||
* content: 文件内容文本
|
||||
* language_id: 语言 ID,例如 "c", "cpp", "python", "javascript"
|
||||
* returns: 0 成功, -1 失败
|
||||
* 在语言服务器中打开一个文档 / Open a document in the language server
|
||||
* uri: 文件 URI,例如 "file:///path/to/file.c" / file URI, e.g. "file:///path/to/file.c"
|
||||
* content: 文件内容文本 / file content text
|
||||
* language_id: 语言 ID,例如 "c", "cpp", "python", "javascript" / language ID, e.g. "c", "cpp", "python", "javascript"
|
||||
* returns: 0 成功, -1 失败 / 0 success, -1 failure
|
||||
*/
|
||||
DSTALK_API int dstalk_lsp_open(const char* uri, const char* content,
|
||||
const char* language_id);
|
||||
|
||||
/*
|
||||
* 关闭语言服务器中的文档
|
||||
* uri: 文件 URI
|
||||
* returns: 0 成功, -1 失败
|
||||
* 关闭语言服务器中的文档 / Close a document in the language server
|
||||
* uri: 文件 URI / file URI
|
||||
* returns: 0 成功, -1 失败 / 0 success, -1 failure
|
||||
*/
|
||||
DSTALK_API int dstalk_lsp_close(const char* uri);
|
||||
|
||||
/* ---- 查询操作 ---- */
|
||||
/* ---- 查询操作 / Query Operations ---- */
|
||||
|
||||
/*
|
||||
* 获取诊断信息 (编译错误、警告等)
|
||||
* uri: 文件 URI
|
||||
* output: 输出参数,JSON 格式的诊断列表 (调用方通过 dstalk_free 释放)
|
||||
* returns: 0 成功, -1 失败
|
||||
* 获取诊断信息 (编译错误、警告等) / Get diagnostics (build errors, warnings, etc.)
|
||||
* uri: 文件 URI / file URI
|
||||
* output: 输出参数,JSON 格式的诊断列表 (调用方通过 dstalk_free 释放) / output param, JSON list of diagnostics (caller frees via dstalk_free)
|
||||
* returns: 0 成功, -1 失败 / 0 success, -1 failure
|
||||
*
|
||||
* JSON 输出格式示例:
|
||||
* JSON 输出格式示例 / JSON output format example:
|
||||
* [
|
||||
* {
|
||||
* "range": { "start": {"line":0,"character":0}, "end":{"line":0,"character":5} },
|
||||
@@ -63,23 +70,23 @@ DSTALK_API int dstalk_lsp_close(const char* uri);
|
||||
DSTALK_API int dstalk_lsp_diagnostics(const char* uri, char** output);
|
||||
|
||||
/*
|
||||
* 获取悬停信息 (类型、文档等)
|
||||
* uri: 文件 URI
|
||||
* line: 行号 (0-based)
|
||||
* character: 列号 (0-based, UTF-16 code units)
|
||||
* output: 输出参数,JSON 格式的悬停信息 (调用方通过 dstalk_free 释放)
|
||||
* returns: 0 成功, -1 失败
|
||||
* 获取悬停信息 (类型、文档等) / Get hover info (type, documentation, etc.)
|
||||
* uri: 文件 URI / file URI
|
||||
* line: 行号 (0-based) / line number (0-based)
|
||||
* character: 列号 (0-based, UTF-16 code units) / column number (0-based, UTF-16 code units)
|
||||
* output: 输出参数,JSON 格式的悬停信息 (调用方通过 dstalk_free 释放) / output param, JSON hover info (caller frees via dstalk_free)
|
||||
* returns: 0 成功, -1 失败 / 0 success, -1 failure
|
||||
*/
|
||||
DSTALK_API int dstalk_lsp_hover(const char* uri, int line, int character,
|
||||
char** output);
|
||||
|
||||
/*
|
||||
* 获取代码补全建议
|
||||
* uri: 文件 URI
|
||||
* line: 行号 (0-based)
|
||||
* character: 列号 (0-based, UTF-16 code units)
|
||||
* output: 输出参数,JSON 格式的补全列表 (调用方通过 dstalk_free 释放)
|
||||
* returns: 0 成功, -1 失败
|
||||
* 获取代码补全建议 / Get code completion suggestions
|
||||
* uri: 文件 URI / file URI
|
||||
* line: 行号 (0-based) / line number (0-based)
|
||||
* character: 列号 (0-based, UTF-16 code units) / column number (0-based, UTF-16 code units)
|
||||
* output: 输出参数,JSON 格式的补全列表 (调用方通过 dstalk_free 释放) / output param, JSON completion list (caller frees via dstalk_free)
|
||||
* returns: 0 成功, -1 失败 / 0 success, -1 failure
|
||||
*/
|
||||
DSTALK_API int dstalk_lsp_completion(const char* uri, int line, int character,
|
||||
char** output);
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/**
|
||||
* @file dstalk_services.h
|
||||
* @brief Service vtable definitions for all plugin-provided services (AI, Session, HTTP, etc.).
|
||||
* 所有插件提供的服务 vtable 定义(AI、会话、HTTP 等)。
|
||||
* Copyright (c) 2026 dstalk contributors. GPLv3.
|
||||
*/
|
||||
|
||||
#ifndef DSTALK_SERVICES_H
|
||||
#define DSTALK_SERVICES_H
|
||||
|
||||
@@ -7,46 +14,64 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// === AI 服务 vtable (实际服务名由插件注册: "ai.deepseek" / "ai.anthropic") ===
|
||||
/* ---- AI 服务 vtable / AI service vtable ---- */
|
||||
/* 以名称如 "ai.deepseek" 或 "ai.anthropic" 注册 / Registered under names such as "ai.deepseek" or "ai.anthropic" */
|
||||
typedef struct {
|
||||
/* 配置服务商连接 (base_url, api_key, model 等) / Configure provider connection (base_url, api_key, model, etc.) */
|
||||
int (*configure)(const char* provider, const char* base_url,
|
||||
const char* api_key, const char* model,
|
||||
int max_tokens, double temperature);
|
||||
/* 发送单轮聊天补全请求 (阻塞) / Send a single-turn chat completion (blocking) */
|
||||
dstalk_chat_result_t (*chat)(
|
||||
const dstalk_message_t* history, int history_len,
|
||||
const char* user_input,
|
||||
const char* tools_json);
|
||||
/* 通过回调实现流式令牌传输的聊天补全 / Send a chat completion with streaming tokens via callback */
|
||||
dstalk_chat_result_t (*chat_stream)(
|
||||
const dstalk_message_t* history, int history_len,
|
||||
const char* user_input,
|
||||
dstalk_stream_cb cb, void* userdata);
|
||||
/* 释放 dstalk_chat_result_t 持有的资源 / Free resources held by a dstalk_chat_result_t */
|
||||
void (*free_result)(dstalk_chat_result_t* result);
|
||||
} dstalk_ai_service_t;
|
||||
|
||||
// === Session 服务 (service name: "session") ===
|
||||
/* ---- 会话服务 vtable / Session service vtable ---- */
|
||||
/* 以服务名称 "session" 注册 / Registered under service name "session" */
|
||||
typedef struct {
|
||||
/* 将消息追加到会话历史 / Append a message to the session history */
|
||||
void (*add)(const dstalk_message_t* msg);
|
||||
/* 清除会话历史中的所有消息 / Clear all messages from the session history */
|
||||
void (*clear)(void);
|
||||
/* 将会话历史保存到文件 (JSON); 成功返回 0 / Save session history to a file (JSON); returns 0 on success */
|
||||
int (*save)(const char* path);
|
||||
/* 从文件 (JSON) 加载会话历史; 成功返回 0 / Load session history from a file (JSON); returns 0 on success */
|
||||
int (*load)(const char* path);
|
||||
/* 获取完整消息历史; out_count 接收数组长度 / Get the full message history; out_count receives the array length */
|
||||
const dstalk_message_t* (*history)(int* out_count);
|
||||
/* 返回当前会话历史的近似令牌数 / Return the approximate token count of the current session history */
|
||||
int (*token_count)(void);
|
||||
} dstalk_session_service_t;
|
||||
|
||||
// === Context 服务 (service name: "context") ===
|
||||
/* ---- 上下文服务 vtable / Context service vtable ---- */
|
||||
/* 以服务名称 "context" 注册 / Registered under service name "context" */
|
||||
typedef struct {
|
||||
/* 计算消息数组中近似的令牌数 / Count approximate tokens in an array of messages */
|
||||
size_t (*count_tokens)(const dstalk_message_t* msgs, int count);
|
||||
/* 裁剪消息历史以适应 max_tokens; out/out_count 为新分配 / Trim message history to fit within max_tokens; out/out_count are newly allocated */
|
||||
int (*trim)(const dstalk_message_t* in, int in_count,
|
||||
dstalk_message_t** out, int* out_count,
|
||||
size_t max_tokens);
|
||||
} dstalk_context_service_t;
|
||||
|
||||
// === HTTP 服务 (service name: "http") ===
|
||||
/* ---- HTTP 服务 vtable / HTTP service vtable ---- */
|
||||
/* 以服务名称 "http" 注册 / Registered under service name "http" */
|
||||
typedef struct {
|
||||
/* POST JSON 体到主机; 返回响应体和 HTTP 状态码 / POST JSON body to a host; returns response body and HTTP status code */
|
||||
int (*post_json)(const char* host, const char* port,
|
||||
const char* target, const char* body,
|
||||
const char* headers_json,
|
||||
char** response_body, int* status_code);
|
||||
/* POST 带流式响应; 令牌通过回调传递 / POST with streaming response; tokens are delivered via callback */
|
||||
int (*post_stream)(const char* host, const char* port,
|
||||
const char* target, const char* body,
|
||||
const char* headers_json,
|
||||
@@ -54,38 +79,61 @@ typedef struct {
|
||||
char** response_body, int* status_code);
|
||||
} dstalk_http_service_t;
|
||||
|
||||
// === File IO 服务 (service name: "file_io") ===
|
||||
/* ---- 文件 I/O 服务 vtable / File I/O service vtable ---- */
|
||||
/* 以服务名称 "file_io" 注册 / Registered under service name "file_io" */
|
||||
typedef struct {
|
||||
/* 读取整个文件内容到 *content; 成功返回 0 / Read entire file content into *content; returns 0 on success */
|
||||
int (*read)(const char* path, char** content);
|
||||
/* 将内容写入文件 (覆盖已有文件); 成功返回 0 / Write content to a file (overwrites if exists); returns 0 on success */
|
||||
int (*write)(const char* path, const char* content);
|
||||
} dstalk_file_io_service_t;
|
||||
|
||||
// === Config 服务 (service name: "config") ===
|
||||
/* ---- 配置服务 vtable / Config service vtable ---- */
|
||||
/* 以服务名称 "config" 注册 / Registered under service name "config" */
|
||||
typedef struct {
|
||||
/* 通过键名获取配置值; 未找到返回 NULL / Get a config value by key; returns NULL if not found */
|
||||
const char* (*get)(const char* key);
|
||||
/* 设置配置键值对; 成功返回 0 / Set a config key/value pair; returns 0 on success */
|
||||
int (*set)(const char* key, const char* value);
|
||||
/* 从 JSON 配置文件加载并合并键值对 / Load and merge key/value pairs from a JSON config file */
|
||||
int (*load_file)(const char* path);
|
||||
} dstalk_config_service_t;
|
||||
|
||||
// === Tools 服务 (service name: "tools") ===
|
||||
/* ---- 工具服务 vtable / Tools service vtable ---- */
|
||||
/* 以服务名称 "tools" 注册 / Registered under service name "tools" */
|
||||
|
||||
/* 已注册工具被调用时触发的处理器; 接收 JSON 参数, 返回 JSON 结果 / Handler invoked when a registered tool is called; receives JSON args, returns JSON result */
|
||||
typedef char* (*dstalk_tool_handler_fn)(const char* args_json);
|
||||
|
||||
typedef struct {
|
||||
/* 注册工具,包含名称、描述和 JSON Schema 参数 / Register a tool with name, description, and JSON Schema parameters */
|
||||
int (*register_tool)(const char* name, const char* desc,
|
||||
const char* params_schema,
|
||||
dstalk_tool_handler_fn handler);
|
||||
/* 取消注册之前注册的工具 / Unregister a previously registered tool */
|
||||
void (*unregister_tool)(const char* name);
|
||||
/* 获取所有已注册工具为 JSON 数组 (OpenAI 工具格式) / Get all registered tools as a JSON array (OpenAI tool format) */
|
||||
char* (*get_tools_json)(void);
|
||||
/* 按名称执行已注册工具,传入 JSON 参数 / Execute a registered tool by name with the given JSON arguments */
|
||||
char* (*execute)(const char* name, const char* args_json);
|
||||
} dstalk_tools_service_t;
|
||||
|
||||
// === LSP 服务 (service name: "lsp") ===
|
||||
/* ---- LSP 服务 vtable / LSP service vtable ---- */
|
||||
/* 以服务名称 "lsp" 注册 / Registered under service name "lsp" */
|
||||
typedef struct {
|
||||
/* 启动指定语言的 LSP 服务器进程 / Start an LSP server process for the given language */
|
||||
int (*start)(const char* server_cmd, const char* language);
|
||||
/* 停止 LSP 服务器并清理资源 / Stop the LSP server and clean up resources */
|
||||
void (*stop)(void);
|
||||
/* 在 LSP 服务器中打开文档 / Open a document in the LSP server */
|
||||
int (*open_document)(const char* uri, const char* content, const char* lang_id);
|
||||
/* 在 LSP 服务器中关闭文档 / Close a document in the LSP server */
|
||||
int (*close_document)(const char* uri);
|
||||
/* 获取文档的诊断信息 (错误、警告) 以 JSON 格式返回 / Retrieve diagnostics (errors, warnings) for a document as JSON */
|
||||
int (*get_diagnostics)(const char* uri, char** json_out);
|
||||
/* 获取指定位置的悬停信息以 JSON 格式返回 / Retrieve hover information at a given position as JSON */
|
||||
int (*get_hover)(const char* uri, int line, int col, char** json_out);
|
||||
/* 获取指定位置的代码补全建议以 JSON 格式返回 / Retrieve code completion suggestions at a given position as JSON */
|
||||
int (*get_completion)(const char* uri, int line, int col, char** json_out);
|
||||
} dstalk_lsp_service_t;
|
||||
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/**
|
||||
* @file dstalk_types.h
|
||||
* @brief Shared data types used across the dstalk host and all plugins.
|
||||
* 跨主机和所有插件共享的数据类型定义。
|
||||
* Copyright (c) 2026 dstalk contributors. GPLv3.
|
||||
*/
|
||||
|
||||
#ifndef DSTALK_TYPES_H
|
||||
#define DSTALK_TYPES_H
|
||||
|
||||
@@ -7,42 +14,42 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// 消息结构(跨插件共享)
|
||||
/* 所有插件共享的消息结构体 / Shared message structure used across plugins */
|
||||
typedef struct {
|
||||
const char* role; // "user", "assistant", "system", "tool"
|
||||
const char* content; // 消息内容
|
||||
const char* tool_call_id; // tool 响应时必填
|
||||
const char* tool_calls_json;// assistant 返回的工具调用(JSON 数组)
|
||||
const char* role; // 角色标识 / Role identifier ("user", "assistant", "system", "tool")
|
||||
const char* content; // 消息正文文本 / Message body text
|
||||
const char* tool_call_id; // 工具调用响应消息所需 / Required for tool response messages
|
||||
const char* tool_calls_json;// 助手工具调用的 JSON 数组 / JSON array of tool calls from assistant
|
||||
} dstalk_message_t;
|
||||
|
||||
// 聊天结果
|
||||
/* 聊天/补全调用返回的结果 / Result returned from a chat / completion call */
|
||||
typedef struct {
|
||||
int ok;
|
||||
const char* content; // dstalk_strdup 分配,调用方 dstalk_free
|
||||
const char* error; // dstalk_strdup 分配
|
||||
int http_status;
|
||||
const char* tool_calls_json;// dstalk_strdup 分配
|
||||
int ok; // 0=失败, 非零=成功 / 0 = failure, non-zero = success
|
||||
const char* content; // dstalk_strdup 分配; 调用方用 dstalk_free 释放 / allocated by dstalk_strdup; caller frees with dstalk_free
|
||||
const char* error; // dstalk_strdup 分配; 成功时为 NULL / allocated by dstalk_strdup; NULL on success
|
||||
int http_status; // 服务商返回的 HTTP 状态码 / HTTP status code from the provider
|
||||
const char* tool_calls_json;// dstalk_strdup 分配; 工具调用的 JSON 数组 / allocated by dstalk_strdup; JSON array of tool calls
|
||||
} dstalk_chat_result_t;
|
||||
|
||||
// 流式回调
|
||||
/* 流式令牌回调: 返回非零值提前中止流传输 / Streaming token callback: return non-zero to abort the stream early */
|
||||
typedef int (*dstalk_stream_cb)(const char* token, void* userdata);
|
||||
|
||||
// 事件类型
|
||||
/* 事件类型代码 (匿名枚举) / Event type codes (anonymous enum) */
|
||||
enum {
|
||||
DSTALK_EVENT_MESSAGE = 1, // data = dstalk_message_t*
|
||||
DSTALK_EVENT_SESSION_CLEAR,
|
||||
DSTALK_EVENT_CONFIG_CHANGED,
|
||||
DSTALK_EVENT_PLUGIN_LOADED, // data = plugin info JSON string
|
||||
DSTALK_EVENT_PLUGIN_UNLOADED,
|
||||
DSTALK_EVENT_CUSTOM = 1000, // 插件自定义事件起始值
|
||||
DSTALK_EVENT_MESSAGE = 1, // 数据为 dstalk_message_t* / data = dstalk_message_t*
|
||||
DSTALK_EVENT_SESSION_CLEAR, // 会话历史已清除 / session history cleared
|
||||
DSTALK_EVENT_CONFIG_CHANGED, // 配置键/值已更新 / configuration key/value updated
|
||||
DSTALK_EVENT_PLUGIN_LOADED, // 数据为插件信息 JSON 字符串 / data = plugin info JSON string
|
||||
DSTALK_EVENT_PLUGIN_UNLOADED, // 插件已卸载 / plugin unloaded
|
||||
DSTALK_EVENT_CUSTOM = 1000, // 插件自定义事件的基础值 / base value for plugin-defined custom events
|
||||
};
|
||||
|
||||
// 日志级别
|
||||
/* 日志严重等级 (匿名枚举) / Log severity levels (anonymous enum) */
|
||||
enum {
|
||||
DSTALK_LOG_DEBUG = 0,
|
||||
DSTALK_LOG_INFO = 1,
|
||||
DSTALK_LOG_WARN = 2,
|
||||
DSTALK_LOG_ERROR = 3,
|
||||
DSTALK_LOG_DEBUG = 0, // 详细调试消息 / verbose debug messages
|
||||
DSTALK_LOG_INFO = 1, // 信息性消息 / informational messages
|
||||
DSTALK_LOG_WARN = 2, // 警告条件 / warning conditions
|
||||
DSTALK_LOG_ERROR = 3, // 错误条件 / error conditions
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -1 +1,7 @@
|
||||
/* @file boost_json.cpp
|
||||
* @brief Boost.JSON header-only library compilation unit (single TU inclusion).
|
||||
* Boost.JSON 仅头文件库的编译单元(单翻译单元包含)。
|
||||
* Copyright (c) 2026 dstalk contributors. GPLv3.
|
||||
*/
|
||||
|
||||
#include <boost/json/src.hpp>
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/* @file config_store.cpp
|
||||
* @brief ConfigStore implementation: TOML parsing, thread-safe get/set with thread-local safety.
|
||||
* ConfigStore 实现:TOML 解析、线程安全的 get/set(基于 thread-local 安全机制)。
|
||||
* Copyright (c) 2026 dstalk contributors. GPLv3.
|
||||
*/
|
||||
|
||||
#include "config_store.hpp"
|
||||
#include "../../plugins/config/include/toml_parse.h"
|
||||
|
||||
@@ -8,6 +14,7 @@
|
||||
|
||||
namespace dstalk {
|
||||
|
||||
// 在互斥锁下加载并解析 TOML 文件到键值存储 / Load and parse a TOML file into the key-value store under mutex.
|
||||
int ConfigStore::load_file(const char* path)
|
||||
{
|
||||
if (!path) return -1;
|
||||
@@ -19,7 +26,7 @@ int ConfigStore::load_file(const char* path)
|
||||
ss << file.rdbuf();
|
||||
std::string data = ss.str();
|
||||
|
||||
// W12.2: Use shared TOML parser (de-duplicated from config_plugin.cpp)
|
||||
// W12.2: 使用共享 TOML 解析器(从 config_plugin.cpp 去重) / Use shared TOML parser (de-duplicated from config_plugin.cpp)
|
||||
toml::parse(data, [this](const std::string& key, const std::string& value) {
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
data_[key] = value;
|
||||
@@ -28,6 +35,7 @@ int ConfigStore::load_file(const char* path)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 检索配置值,返回线程本地副本以避免 c_str() 悬空 / Retrieve config value, returning a thread-local copy to avoid dangling c_str().
|
||||
const char* ConfigStore::get(const char* key) const
|
||||
{
|
||||
if (!key) return nullptr;
|
||||
@@ -35,7 +43,9 @@ const char* ConfigStore::get(const char* key) const
|
||||
auto it = data_.find(key);
|
||||
if (it == data_.end()) return nullptr;
|
||||
|
||||
// W12.2: Copy to thread-local buffer before releasing lock.
|
||||
// W12.2: 在释放锁之前复制到线程本地缓冲区 /
|
||||
// Copy to thread-local buffer before releasing lock.
|
||||
// 防止当并发 set() 触发 std::string 重新分配时 c_str() 悬空 /
|
||||
// Prevents c_str() dangling when concurrent set() on the same key
|
||||
// triggers std::string reallocation (W11.2 audit Finding 3).
|
||||
thread_local std::string tls_cached;
|
||||
@@ -43,15 +53,17 @@ const char* ConfigStore::get(const char* key) const
|
||||
return tls_cached.c_str();
|
||||
}
|
||||
|
||||
// 以 std::string 值类型检索配置(安全的值副本)/ Retrieve config value as an owned std::string (safe by-value copy).
|
||||
std::string ConfigStore::get_copy(const char* key) const
|
||||
{
|
||||
if (!key) return {};
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto it = data_.find(key);
|
||||
if (it == data_.end()) return {};
|
||||
return it->second; // copy-constructed under lock, always safe
|
||||
return it->second; // 在锁下复制构造,始终安全 / copy-constructed under lock, always safe
|
||||
}
|
||||
|
||||
// 在锁下设置配置键值对 / Set a config key-value pair under lock.
|
||||
int ConfigStore::set(const char* key, const char* value)
|
||||
{
|
||||
if (!key || !value) return -1;
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/* @file config_store.hpp
|
||||
* @brief Thread-safe key-value configuration store with TOML file loading.
|
||||
* 线程安全键值配置存储,支持 TOML 文件加载。
|
||||
* Copyright (c) 2026 dstalk contributors. GPLv3.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
@@ -6,32 +12,36 @@
|
||||
|
||||
namespace dstalk {
|
||||
|
||||
// 线程安全的键值存储,支持 TOML 配置文件 / Thread-safe key-value store backed by TOML config files.
|
||||
// 通过 mutex_ 支持并发读取,get() 返回线程本地缓冲区 / Supports concurrent reads via mutex_ and returns thread-local buffers from get().
|
||||
class ConfigStore {
|
||||
public:
|
||||
ConfigStore() = default;
|
||||
~ConfigStore() = default;
|
||||
|
||||
// Load key-value pairs from a TOML file.
|
||||
// Returns 0 on success, -1 if file not found or path is null.
|
||||
// 从 TOML 文件加载键值对 / Load key-value pairs from a TOML file.
|
||||
// 成功返回 0,文件未找到或路径为空返回 -1 / Returns 0 on success, -1 if file not found or path is null.
|
||||
int load_file(const char* path);
|
||||
|
||||
// Get config value (returns internal pointer, thread-safe).
|
||||
// W12.2: Returned pointer is now backed by a thread-local copy;
|
||||
// 获取配置值(返回内部指针,线程安全)/ Get config value (returns internal pointer, thread-safe).
|
||||
// W12.2: 返回的指针现在由线程局部副本支持,对其他线程对同一键的并发 set() 安全 /
|
||||
// Returned pointer is now backed by a thread-local copy;
|
||||
// safe against concurrent set() on the same key from other threads.
|
||||
// 调用者仍应立即使用 — 同一线程上的下一次 get() 将覆盖缓冲区 /
|
||||
// Caller should still consume immediately — next get() on same
|
||||
// thread will overwrite the buffer.
|
||||
const char* get(const char* key) const;
|
||||
|
||||
// Get a safe by-value copy of a config entry (no dangling risk).
|
||||
// Returns empty string if key not found.
|
||||
// 获取配置项的安全值副本(无悬空风险)/ Get a safe by-value copy of a config entry (no dangling risk).
|
||||
// 如果键未找到,返回空字符串 / Returns empty string if key not found.
|
||||
std::string get_copy(const char* key) const;
|
||||
|
||||
// Set config value. Returns 0 on success, -1 on null arguments.
|
||||
// 设置配置值 / Set config value. 成功返回 0,参数为空返回 -1 / Returns 0 on success, -1 on null arguments.
|
||||
int set(const char* key, const char* value);
|
||||
|
||||
private:
|
||||
mutable std::mutex mutex_;
|
||||
std::unordered_map<std::string, std::string> data_;
|
||||
mutable std::mutex mutex_; // 保护所有 data_ 访问 / Protects all data_ access
|
||||
std::unordered_map<std::string, std::string> data_; // 配置键值存储 / Config key-value store
|
||||
};
|
||||
|
||||
} // namespace dstalk
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
/* @file event_bus.cpp
|
||||
* @brief EventBus implementation: subscribe, unsubscribe, emit with reader-writer locking.
|
||||
* EventBus 实现:基于读写锁的 subscribe、unsubscribe、emit。
|
||||
* Copyright (c) 2026 dstalk contributors. GPLv3.
|
||||
*/
|
||||
|
||||
#include "event_bus.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace dstalk {
|
||||
|
||||
// 为给定事件类型注册处理器,返回订阅 ID / Register a handler for the given event type, returning a subscription id.
|
||||
int EventBus::subscribe(int event_type, EventHandler handler)
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
@@ -12,6 +19,7 @@ int EventBus::subscribe(int event_type, EventHandler handler)
|
||||
return id;
|
||||
}
|
||||
|
||||
// 通过 ID 移除订阅(如果 ID 未找到则无操作)/ Remove a subscription by id (no-op if id not found).
|
||||
void EventBus::unsubscribe(int subscription_id)
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
@@ -23,6 +31,8 @@ void EventBus::unsubscribe(int subscription_id)
|
||||
subscriptions_.end());
|
||||
}
|
||||
|
||||
// 在共享锁下将事件分发给所有匹配的订阅者 / Dispatch an event to all matching subscribers under a shared lock.
|
||||
// 返回被调用的处理器数量 / Returns the count of handlers invoked.
|
||||
int EventBus::emit(int event_type, const void* data)
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/* @file event_bus.hpp
|
||||
* @brief Publish-subscribe event bus with shared_mutex for concurrent read access.
|
||||
* 发布-订阅事件总线,使用 shared_mutex 支持并发读访问。
|
||||
* Copyright (c) 2026 dstalk contributors. GPLv3.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
@@ -10,18 +16,23 @@ namespace dstalk {
|
||||
|
||||
using EventHandler = std::function<void(int event_type, const void* data)>;
|
||||
|
||||
// 轻量级发布-订阅事件总线 / Lightweight pub-sub event bus.
|
||||
// 读取者使用 shared_lock(emit),因此多个处理器可以并发分发;
|
||||
// 写入者使用 unique_lock(subscribe / unsubscribe)。
|
||||
// Readers use shared_lock (emit) so multiple handlers can be dispatched
|
||||
// concurrently; writers use unique_lock (subscribe / unsubscribe).
|
||||
class EventBus {
|
||||
public:
|
||||
EventBus() = default;
|
||||
~EventBus() = default;
|
||||
|
||||
// 订阅事件,返回订阅ID
|
||||
// 订阅事件,返回订阅ID / Subscribe to an event, returning a subscription id
|
||||
int subscribe(int event_type, EventHandler handler);
|
||||
|
||||
// 取消订阅
|
||||
// 取消订阅 / Unsubscribe by subscription id
|
||||
void unsubscribe(int subscription_id);
|
||||
|
||||
// 发布事件
|
||||
// 发布事件 / Emit an event to all matching subscribers
|
||||
int emit(int event_type, const void* data);
|
||||
|
||||
private:
|
||||
@@ -31,9 +42,9 @@ private:
|
||||
EventHandler handler;
|
||||
};
|
||||
|
||||
mutable std::shared_mutex mutex_;
|
||||
std::vector<Subscription> subscriptions_;
|
||||
int next_id_ = 1;
|
||||
mutable std::shared_mutex mutex_; // 读写锁:emit 用 shared,subscribe/unsubscribe 用 unique / RW lock: shared for emit, unique for subscribe/unsubscribe
|
||||
std::vector<Subscription> subscriptions_; // emit 时线性扫描;对少量订阅者足够 / Linear scan on emit; ok for small subscriber counts
|
||||
int next_id_ = 1; // 单调递增订阅 ID 计数器 / Monotonic subscription id counter
|
||||
};
|
||||
|
||||
} // namespace dstalk
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* @file host.cpp
|
||||
* @brief Core host orchestrator: global singletons, dstalk_host_api_t instantiation, public C API, LSP delegation.
|
||||
* 核心主机协调器:全局单例、dstalk_host_api_t 实例化、公共 C API、LSP 委托。
|
||||
* Copyright (c) 2026 dstalk contributors. GPLv3.
|
||||
*/
|
||||
|
||||
#include "dstalk/dstalk_host.h"
|
||||
#include "config_store.hpp"
|
||||
#include "event_bus.hpp"
|
||||
@@ -15,7 +22,7 @@
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// ============================================================
|
||||
// 全局主机上下文
|
||||
// 全局主机上下文 / Global host context
|
||||
// ============================================================
|
||||
namespace {
|
||||
std::mutex g_init_mutex;
|
||||
@@ -27,8 +34,10 @@ namespace {
|
||||
dstalk::PluginLoader* g_plugin_loader = nullptr;
|
||||
static std::atomic<dstalk_diag_cb> g_diag_callback{nullptr};
|
||||
|
||||
// ---- 内部辅助 ----
|
||||
// ---- 内部辅助 / Internal helpers ----
|
||||
|
||||
// 复制 C 字符串(用 malloc 分配,调用者必须用 api_free/free 释放)
|
||||
// Duplicate a C string allocated with malloc (caller must free via api_free/free).
|
||||
char* host_strdup(const char* s) {
|
||||
if (!s) return nullptr;
|
||||
size_t len = strlen(s);
|
||||
@@ -37,6 +46,8 @@ namespace {
|
||||
return copy;
|
||||
}
|
||||
|
||||
// 核心日志实现:格式化消息,写入 stderr,并转发到诊断回调(如果已设置)。
|
||||
// Core logging implementation: formats message, writes to stderr, and forwards to diagnostic callback if set.
|
||||
void host_log_impl(int level, const char* fmt, va_list args) {
|
||||
const char* prefix = "";
|
||||
switch (level) {
|
||||
@@ -50,7 +61,7 @@ namespace {
|
||||
va_copy(args_copy, args);
|
||||
vfprintf(stderr, fmt, args);
|
||||
fprintf(stderr, "\n");
|
||||
// 转发到诊断回调
|
||||
// 转发到诊断回调 / Forward to diagnostic callback
|
||||
auto cb = g_diag_callback.load(std::memory_order_acquire);
|
||||
if (cb) {
|
||||
char buf[1024];
|
||||
@@ -60,6 +71,8 @@ namespace {
|
||||
va_end(args_copy);
|
||||
}
|
||||
|
||||
// host_log_impl 的 printf 风格便捷包装。
|
||||
// Convenience wrapper around host_log_impl for printf-style calls.
|
||||
void host_log(int level, const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
@@ -67,16 +80,22 @@ namespace {
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
// ---- Host API 表回调 ----
|
||||
// ---- Host API 表回调 / Host API table callbacks ----
|
||||
|
||||
// 将服务 vtable 按名称和版本注册到全局注册表。
|
||||
// Register a service vtable with the given name and version into the global registry.
|
||||
int api_register_service(const char* name, int version, void* vtable) {
|
||||
return g_service_registry ? g_service_registry->register_service(name, version, vtable) : -1;
|
||||
}
|
||||
|
||||
// 按名称和最低版本从全局注册表查询服务 vtable。
|
||||
// Query a service vtable by name and minimum version from the global registry.
|
||||
void* api_query_service(const char* name, int min_version) {
|
||||
return g_service_registry ? g_service_registry->query_service(name, min_version) : nullptr;
|
||||
}
|
||||
|
||||
// 通过全局事件总线订阅指定事件类型的处理函数。
|
||||
// Subscribe a handler to a given event type via the global event bus.
|
||||
int api_event_subscribe(int event_type, dstalk_event_handler_fn handler, void* userdata) {
|
||||
if (!g_event_bus || !handler) return -1;
|
||||
return g_event_bus->subscribe(event_type,
|
||||
@@ -85,22 +104,32 @@ namespace {
|
||||
});
|
||||
}
|
||||
|
||||
// 通过全局事件总路线程安全地向所有已注册处理函数发送事件。
|
||||
// Emit an event to all registered handlers via the global event bus.
|
||||
int api_event_emit(int event_type, const void* data) {
|
||||
return g_event_bus ? g_event_bus->emit(event_type, data) : -1;
|
||||
}
|
||||
|
||||
// 通过订阅 ID 取消注册之前的事件处理函数。
|
||||
// Unsubscribe a previously registered event handler by subscription ID.
|
||||
void api_event_unsubscribe(int sub_id) {
|
||||
if (g_event_bus) g_event_bus->unsubscribe(sub_id);
|
||||
}
|
||||
|
||||
// 从全局配置存储中按键名读取配置值。
|
||||
// Read a config value by key from the global config store.
|
||||
const char* api_config_get(const char* key) {
|
||||
return g_config ? g_config->get(key) : nullptr;
|
||||
}
|
||||
|
||||
// 在全局配置存储中设置配置键值对。
|
||||
// Set a config key/value pair in the global config store.
|
||||
int api_config_set(const char* key, const char* value) {
|
||||
return g_config ? g_config->set(key, value) : -1;
|
||||
}
|
||||
|
||||
// 主机端日志函数(host_log_impl 的 varargs 包装)。
|
||||
// Host-facing log function (varargs wrapper around host_log_impl).
|
||||
void api_log(int level, const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
@@ -108,11 +137,16 @@ namespace {
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
// 内存分配包装 / Memory allocation wrapper (malloc).
|
||||
void* api_alloc(size_t size) { return malloc(size); }
|
||||
// 内存释放包装 / Memory free wrapper (free).
|
||||
void api_free(void* ptr) { free(ptr); }
|
||||
|
||||
// 字符串复制包装 / String duplication wrapper (host_strdup).
|
||||
char* api_strdup(const char* s) { return host_strdup(s); }
|
||||
|
||||
// 传递给每个插件 on_init 的完整主机 API vtable。
|
||||
// The complete host API vtable passed to every plugin's on_init.
|
||||
dstalk_host_api_t g_host_api = {
|
||||
api_register_service,
|
||||
api_query_service,
|
||||
@@ -127,8 +161,12 @@ namespace {
|
||||
api_strdup
|
||||
};
|
||||
|
||||
// ---- 插件目录扫描 ----
|
||||
// ---- 插件目录扫描 / Plugin directory scanning ----
|
||||
|
||||
// 扫描目录中的插件 DLL 并通过 PluginLoader 加载。
|
||||
// 返回加载的插件数量,出错返回 -1。
|
||||
// Scan a directory for plugin DLLs and load them via PluginLoader.
|
||||
// Returns the number of plugins loaded, or -1 on error.
|
||||
int load_plugins_from_directory(const char* plugin_dir) {
|
||||
if (!plugin_dir) return -1;
|
||||
|
||||
@@ -163,9 +201,11 @@ namespace {
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 公共 API
|
||||
// 公共 API / Public API
|
||||
// ============================================================
|
||||
|
||||
// 初始化 dstalk 主机:创建单例、加载配置、扫描插件、初始化所有插件。
|
||||
// Initialize the dstalk host: create singletons, load config, scan plugins, initialize all plugins.
|
||||
DSTALK_API int dstalk_init(const char* config_path)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_init_mutex);
|
||||
@@ -178,14 +218,14 @@ DSTALK_API int dstalk_init(const char* config_path)
|
||||
g_service_registry = new dstalk::ServiceRegistry();
|
||||
g_plugin_loader = new dstalk::PluginLoader();
|
||||
|
||||
// 加载配置
|
||||
// 加载配置 / Load config
|
||||
if (config_path && config_path[0]) {
|
||||
if (g_config->load_file(config_path) != 0) {
|
||||
host_log(DSTALK_LOG_WARN, "Failed to load config: %s", config_path);
|
||||
}
|
||||
}
|
||||
|
||||
// 扫描插件目录
|
||||
// 扫描插件目录 / Scan plugin directory
|
||||
const char* plugin_dir = g_config->get("plugin_dir");
|
||||
if (!plugin_dir) plugin_dir = "plugins";
|
||||
int loaded = load_plugins_from_directory(plugin_dir);
|
||||
@@ -195,7 +235,7 @@ DSTALK_API int dstalk_init(const char* config_path)
|
||||
loaded = load_plugins_from_directory("../plugins");
|
||||
}
|
||||
|
||||
// 初始化所有插件
|
||||
// 初始化所有插件 / Initialize all plugins
|
||||
if (g_plugin_loader->initialize_all(&g_host_api) != 0) {
|
||||
host_log(DSTALK_LOG_WARN, "Some plugins failed to initialize");
|
||||
}
|
||||
@@ -214,6 +254,8 @@ DSTALK_API int dstalk_init(const char* config_path)
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭 dstalk 主机:关闭插件、销毁单例、释放资源。
|
||||
// Shutdown the dstalk host: shutdown plugins, destroy singletons, release resources.
|
||||
DSTALK_API void dstalk_shutdown(void)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_init_mutex);
|
||||
@@ -234,6 +276,8 @@ DSTALK_API void dstalk_shutdown(void)
|
||||
g_initialized = false;
|
||||
}
|
||||
|
||||
// 从给定路径加载单个插件 DLL 并初始化。返回插件 ID,失败返回 -1。
|
||||
// Load a single plugin DLL from the given path and initialize it. Returns plugin ID or -1.
|
||||
DSTALK_API int dstalk_plugin_load(const char* path)
|
||||
{
|
||||
if (!g_initialized || !g_plugin_loader) return -1;
|
||||
@@ -244,12 +288,16 @@ DSTALK_API int dstalk_plugin_load(const char* path)
|
||||
return id;
|
||||
}
|
||||
|
||||
// 按 ID 卸载插件:调用 on_shutdown,卸载 DLL,从注册表中移除。成功返回 0。
|
||||
// Unload a plugin by ID: call on_shutdown, unload DLL, remove from registry. Returns 0 on success.
|
||||
DSTALK_API int dstalk_plugin_unload(int plugin_id)
|
||||
{
|
||||
if (!g_initialized || !g_plugin_loader) return -1;
|
||||
return g_plugin_loader->unload_plugin(plugin_id);
|
||||
}
|
||||
|
||||
// 以 JSON 字符串列出所有已加载插件。调用者必须用 dstalk_free 释放 *output_json。
|
||||
// List all loaded plugins as a JSON string. Caller must free *output_json with dstalk_free.
|
||||
DSTALK_API int dstalk_plugin_list(char** output_json)
|
||||
{
|
||||
if (!g_initialized || !g_plugin_loader || !output_json) return -1;
|
||||
@@ -257,12 +305,16 @@ DSTALK_API int dstalk_plugin_list(char** output_json)
|
||||
return *output_json ? 0 : -1;
|
||||
}
|
||||
|
||||
// 按名称和最低版本从全局服务注册表查询服务 vtable。
|
||||
// Query a service vtable by name and minimum version from the global service registry.
|
||||
DSTALK_API void* dstalk_service_query(const char* service_name, int min_version)
|
||||
{
|
||||
if (!g_initialized || !g_service_registry) return nullptr;
|
||||
return g_service_registry->query_service(service_name, min_version);
|
||||
}
|
||||
|
||||
// 订阅回调到事件类型。返回订阅 ID,失败返回 -1。
|
||||
// Subscribe a callback to an event type. Returns a subscription ID or -1.
|
||||
DSTALK_API int dstalk_event_subscribe(int event_type, dstalk_event_handler_fn handler, void* userdata)
|
||||
{
|
||||
if (!g_initialized || !g_event_bus || !handler) return -1;
|
||||
@@ -270,30 +322,40 @@ DSTALK_API int dstalk_event_subscribe(int event_type, dstalk_event_handler_fn ha
|
||||
[handler, userdata](int type, const void* data) { handler(type, data, userdata); });
|
||||
}
|
||||
|
||||
// 向订阅了该事件类型的所有处理函数发送事件。
|
||||
// Emit an event to all handlers subscribed to the given event type.
|
||||
DSTALK_API int dstalk_event_emit(int event_type, const void* data)
|
||||
{
|
||||
if (!g_initialized || !g_event_bus) return -1;
|
||||
return g_event_bus->emit(event_type, data);
|
||||
}
|
||||
|
||||
// 按订阅 ID 取消注册之前的事件处理函数。
|
||||
// Unsubscribe a previously registered event handler by subscription ID.
|
||||
DSTALK_API void dstalk_event_unsubscribe(int subscription_id)
|
||||
{
|
||||
if (!g_initialized || !g_event_bus) return;
|
||||
g_event_bus->unsubscribe(subscription_id);
|
||||
}
|
||||
|
||||
// 按键读取配置值。返回指向内部存储的指针(请勿释放)。
|
||||
// Read a configuration value by key. Returns pointer to internal storage (do not free).
|
||||
DSTALK_API const char* dstalk_config_get(const char* key)
|
||||
{
|
||||
if (!g_initialized || !g_config) return nullptr;
|
||||
return g_config->get(key);
|
||||
}
|
||||
|
||||
// 设置配置键值对。成功返回 0。
|
||||
// Set a configuration key/value pair. Returns 0 on success.
|
||||
DSTALK_API int dstalk_config_set(const char* key, const char* value)
|
||||
{
|
||||
if (!g_initialized || !g_config) return -1;
|
||||
return g_config->set(key, value);
|
||||
}
|
||||
|
||||
// 在给定级别记录消息(printf 风格)。写入 stderr 并转发到诊断回调。
|
||||
// Log a message at the given level (printf-style). Writes to stderr and forwards to diag callback.
|
||||
DSTALK_API void dstalk_log(int level, const char* fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
@@ -302,24 +364,33 @@ DSTALK_API void dstalk_log(int level, const char* fmt, ...)
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
// 通过 malloc 分配内存(为插件 ABI 一致性提供) / Allocate memory via malloc (provided for plugin ABI consistency).
|
||||
DSTALK_API void* dstalk_alloc(size_t size) { return malloc(size); }
|
||||
// 释放通过 dstalk_alloc 分配的内存(为插件 ABI 一致性提供) / Free memory allocated via dstalk_alloc (provided for plugin ABI consistency).
|
||||
DSTALK_API void dstalk_free(void* ptr) { free(ptr); }
|
||||
// 使用 dstalk_alloc 复制 C 字符串(调用者必须 dstalk_free) / Duplicate a C string using dstalk_alloc (caller must dstalk_free).
|
||||
DSTALK_API char* dstalk_strdup(const char* s) { return host_strdup(s); }
|
||||
|
||||
// 注册接收所有日志消息的诊断回调(传入 null 可取消设置)。
|
||||
// Register a diagnostic callback that receives all log messages (may be null to unset).
|
||||
DSTALK_API void dstalk_set_diag_callback(dstalk_diag_cb cb) {
|
||||
g_diag_callback.store(cb, std::memory_order_release);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// LSP 便捷函数 (委托给 "lsp" 服务插件)
|
||||
// LSP 便捷函数 (委托给 "lsp" 服务插件) / LSP convenience functions (delegated to "lsp" service plugin)
|
||||
// ============================================================
|
||||
|
||||
// 从全局服务注册表获取 "lsp" 服务 vtable,不可用则返回 null。
|
||||
// Retrieve the "lsp" service vtable from the global service registry, or null if unavailable.
|
||||
static const dstalk_lsp_service_t* get_lsp_service() {
|
||||
if (!g_initialized || !g_service_registry) return nullptr;
|
||||
return static_cast<const dstalk_lsp_service_t*>(
|
||||
g_service_registry->query_service("lsp", 1));
|
||||
}
|
||||
|
||||
// 为给定的命令和语言启动语言服务器进程。
|
||||
// Start a language server process for the given command and language.
|
||||
DSTALK_API int dstalk_lsp_start(const char* server_cmd, const char* language)
|
||||
{
|
||||
auto* svc = get_lsp_service();
|
||||
@@ -327,12 +398,16 @@ DSTALK_API int dstalk_lsp_start(const char* server_cmd, const char* language)
|
||||
return svc->start(server_cmd, language);
|
||||
}
|
||||
|
||||
// 停止当前正在运行的语言服务器进程。
|
||||
// Stop the currently running language server process.
|
||||
DSTALK_API void dstalk_lsp_stop(void)
|
||||
{
|
||||
auto* svc = get_lsp_service();
|
||||
if (svc && svc->stop) svc->stop();
|
||||
}
|
||||
|
||||
// 在 LSP 服务器中打开文档以供分析(didOpen 通知)。
|
||||
// Open a document in the LSP server for analysis (didOpen notification).
|
||||
DSTALK_API int dstalk_lsp_open(const char* uri, const char* content, const char* language_id)
|
||||
{
|
||||
auto* svc = get_lsp_service();
|
||||
@@ -340,6 +415,8 @@ DSTALK_API int dstalk_lsp_open(const char* uri, const char* content, const char*
|
||||
return svc->open_document(uri, content, language_id);
|
||||
}
|
||||
|
||||
// 在 LSP 服务器中关闭文档(didClose 通知)。
|
||||
// Close a document in the LSP server (didClose notification).
|
||||
DSTALK_API int dstalk_lsp_close(const char* uri)
|
||||
{
|
||||
auto* svc = get_lsp_service();
|
||||
@@ -347,6 +424,8 @@ DSTALK_API int dstalk_lsp_close(const char* uri)
|
||||
return svc->close_document(uri);
|
||||
}
|
||||
|
||||
// 检索文档的当前诊断信息。调用者必须用 dstalk_free 释放 *output。
|
||||
// Retrieve current diagnostics for a document. Caller must free *output with dstalk_free.
|
||||
DSTALK_API int dstalk_lsp_diagnostics(const char* uri, char** output)
|
||||
{
|
||||
auto* svc = get_lsp_service();
|
||||
@@ -354,6 +433,8 @@ DSTALK_API int dstalk_lsp_diagnostics(const char* uri, char** output)
|
||||
return svc->get_diagnostics(uri, output);
|
||||
}
|
||||
|
||||
// 请求文档位置处的悬停信息。调用者必须用 dstalk_free 释放 *output。
|
||||
// Request hover information at a document position. Caller must free *output with dstalk_free.
|
||||
DSTALK_API int dstalk_lsp_hover(const char* uri, int line, int character, char** output)
|
||||
{
|
||||
auto* svc = get_lsp_service();
|
||||
@@ -361,6 +442,8 @@ DSTALK_API int dstalk_lsp_hover(const char* uri, int line, int character, char**
|
||||
return svc->get_hover(uri, line, character, output);
|
||||
}
|
||||
|
||||
// 请求文档位置处的补全项。调用者必须用 dstalk_free 释放 *output。
|
||||
// Request completion items at a document position. Caller must free *output with dstalk_free.
|
||||
DSTALK_API int dstalk_lsp_completion(const char* uri, int line, int character, char** output)
|
||||
{
|
||||
auto* svc = get_lsp_service();
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* @file plugin_loader.cpp
|
||||
* @brief PluginLoader implementation: DLL load/unload, path validation, Kahn topological sort, lifecycle management.
|
||||
* PluginLoader 实现:DLL 加载/卸载、路径验证、Kahn 拓扑排序、生命周期管理。
|
||||
* Copyright (c) 2026 dstalk contributors. GPLv3.
|
||||
*/
|
||||
|
||||
#include "plugin_loader.hpp"
|
||||
|
||||
#include <boost/json.hpp>
|
||||
@@ -21,20 +28,26 @@ namespace dstalk {
|
||||
namespace json = boost::json;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
// 析构函数:调用 shutdown_all 释放所有插件并释放 DLL 句柄。
|
||||
// Destructor: calls shutdown_all to release all plugins and free DLL handles.
|
||||
PluginLoader::~PluginLoader()
|
||||
{
|
||||
shutdown_all();
|
||||
}
|
||||
|
||||
// 加载插件 DLL:验证路径(扩展名、目录遍历、目录),加载库,
|
||||
// 解析 dstalk_plugin_init,验证 API 版本,解析依赖,分配 ID。
|
||||
// Load a plugin DLL: validate path (extension, traversal, directory), load library,
|
||||
// resolve dstalk_plugin_init, verify API version, parse dependencies, assign ID.
|
||||
int PluginLoader::load_plugin(const char* path)
|
||||
{
|
||||
if (!path) return -1;
|
||||
|
||||
// === Path validation (F-18.3-3) ===
|
||||
// === 路径验证 (F-18.3-3) / Path validation (F-18.3-3) ===
|
||||
{
|
||||
fs::path p = fs::absolute(fs::path(path)).lexically_normal();
|
||||
|
||||
// Extension check (case-insensitive)
|
||||
// 扩展名检查(大小写不敏感) / Extension check (case-insensitive)
|
||||
std::string ext = p.extension().string();
|
||||
std::transform(ext.begin(), ext.end(), ext.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
@@ -57,7 +70,7 @@ int PluginLoader::load_plugin(const char* path)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Directory traversal check
|
||||
// 目录遍历检查 / Directory traversal check
|
||||
bool has_dotdot = false;
|
||||
bool in_plugins_dir = false;
|
||||
for (const auto& comp : p) {
|
||||
@@ -78,6 +91,7 @@ int PluginLoader::load_plugin(const char* path)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 目录约束:必须位于 'plugins' 目录下或为纯文件名
|
||||
// Directory constraint: must be under a 'plugins' directory or be a plain filename
|
||||
if (!in_plugins_dir && p.has_parent_path()) {
|
||||
if (host_api_) {
|
||||
@@ -88,7 +102,7 @@ int PluginLoader::load_plugin(const char* path)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载DLL
|
||||
// 加载DLL / Load DLL
|
||||
#ifdef _WIN32
|
||||
void* handle = LoadLibraryA(path);
|
||||
#else
|
||||
@@ -109,7 +123,7 @@ int PluginLoader::load_plugin(const char* path)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 获取入口函数
|
||||
// 获取入口函数 / Resolve entry function
|
||||
#ifdef _WIN32
|
||||
auto init_fn = (dstalk_plugin_init_fn)GetProcAddress(
|
||||
(HMODULE)handle, "dstalk_plugin_init");
|
||||
@@ -138,7 +152,7 @@ int PluginLoader::load_plugin(const char* path)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 调用入口函数获取插件信息
|
||||
// 调用入口函数获取插件信息 / Call entry function to get plugin info
|
||||
dstalk_plugin_info_t* info = nullptr;
|
||||
try {
|
||||
info = init_fn();
|
||||
@@ -160,7 +174,7 @@ int PluginLoader::load_plugin(const char* path)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 检查API版本兼容性
|
||||
// 检查API版本兼容性 / Check API version compatibility
|
||||
if (info->api_version != DSTALK_API_VERSION) {
|
||||
if (host_api_) {
|
||||
host_api_->log(DSTALK_LOG_ERROR,
|
||||
@@ -175,7 +189,7 @@ int PluginLoader::load_plugin(const char* path)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 创建插件信息
|
||||
// 创建插件信息 / Create plugin info
|
||||
int id = next_id_++;
|
||||
PluginInfo plugin;
|
||||
plugin.id = id;
|
||||
@@ -187,7 +201,7 @@ int PluginLoader::load_plugin(const char* path)
|
||||
plugin.info = info;
|
||||
plugin.initialized = false;
|
||||
|
||||
// 解析依赖
|
||||
// 解析依赖 / Parse dependencies
|
||||
for (int i = 0; i < DSTALK_MAX_DEPS && info->dependencies[i]; i++) {
|
||||
plugin.dependencies.push_back(info->dependencies[i]);
|
||||
}
|
||||
@@ -196,6 +210,8 @@ int PluginLoader::load_plugin(const char* path)
|
||||
return id;
|
||||
}
|
||||
|
||||
// 按 ID 卸载插件:若已初始化则调用 on_shutdown,释放 DLL 句柄,从 map 中移除。
|
||||
// Unload a plugin by ID: call on_shutdown if initialized, free the DLL handle, erase from map.
|
||||
int PluginLoader::unload_plugin(int plugin_id)
|
||||
{
|
||||
auto it = plugins_.find(plugin_id);
|
||||
@@ -203,7 +219,7 @@ int PluginLoader::unload_plugin(int plugin_id)
|
||||
|
||||
PluginInfo& plugin = it->second;
|
||||
|
||||
// 调用关闭回调
|
||||
// 调用关闭回调 / Call shutdown callback
|
||||
if (plugin.initialized && plugin.info->on_shutdown) {
|
||||
try {
|
||||
plugin.info->on_shutdown();
|
||||
@@ -216,7 +232,7 @@ int PluginLoader::unload_plugin(int plugin_id)
|
||||
}
|
||||
}
|
||||
|
||||
// 卸载DLL
|
||||
// 卸载DLL / Unload DLL
|
||||
#ifdef _WIN32
|
||||
FreeLibrary((HMODULE)plugin.handle);
|
||||
#else
|
||||
@@ -227,6 +243,8 @@ int PluginLoader::unload_plugin(int plugin_id)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 将所有已加载插件序列化为 JSON 数组字符串。
|
||||
// Serialize all loaded plugins into a JSON array string.
|
||||
std::string PluginLoader::list_plugins() const
|
||||
{
|
||||
json::array arr;
|
||||
@@ -250,15 +268,19 @@ std::string PluginLoader::list_plugins() const
|
||||
return json::serialize(arr);
|
||||
}
|
||||
|
||||
// 使用 Kahn 算法计算依赖顺序的插件 ID 列表。
|
||||
// 若检测到循环依赖则抛出 std::runtime_error。
|
||||
// Compute dependency-ordered plugin IDs using Kahn's algorithm.
|
||||
// Throws std::runtime_error if a circular dependency is detected.
|
||||
std::vector<int> PluginLoader::topological_sort() const
|
||||
{
|
||||
// 构建名称到ID的映射
|
||||
// 构建名称到ID的映射 / Build name-to-ID map
|
||||
std::unordered_map<std::string, int> name_to_id;
|
||||
for (const auto& [id, plugin] : plugins_) {
|
||||
name_to_id[plugin.name] = id;
|
||||
}
|
||||
|
||||
// 计算入度
|
||||
// 计算入度 / Calculate in-degrees
|
||||
std::unordered_map<int, int> in_degree;
|
||||
std::unordered_map<int, std::vector<int>> dependents;
|
||||
|
||||
@@ -277,7 +299,7 @@ std::vector<int> PluginLoader::topological_sort() const
|
||||
}
|
||||
}
|
||||
|
||||
// 拓扑排序(Kahn算法)
|
||||
// 拓扑排序(Kahn算法) / Topological sort (Kahn's algorithm)
|
||||
std::queue<int> queue;
|
||||
for (const auto& [id, degree] : in_degree) {
|
||||
if (degree == 0) {
|
||||
@@ -298,7 +320,7 @@ std::vector<int> PluginLoader::topological_sort() const
|
||||
}
|
||||
}
|
||||
|
||||
// 检查循环依赖
|
||||
// 检查循环依赖 / Check for circular dependency
|
||||
if (sorted.size() != plugins_.size()) {
|
||||
throw std::runtime_error("Circular dependency detected");
|
||||
}
|
||||
@@ -306,17 +328,21 @@ std::vector<int> PluginLoader::topological_sort() const
|
||||
return sorted;
|
||||
}
|
||||
|
||||
// 验证依赖:检查缺失依赖和循环依赖。
|
||||
// 成功返回 0,发现错误返回 -1(错误通过 host_api_ 记录)。
|
||||
// Validate dependencies: checks for missing dependencies and circular dependencies.
|
||||
// Returns 0 on success, -1 if any errors found (errors are logged via host_api_).
|
||||
int PluginLoader::validate_dependencies() const
|
||||
{
|
||||
int error_count = 0;
|
||||
|
||||
// 构建名称到ID的映射
|
||||
// 构建名称到ID的映射 / Build name-to-ID map
|
||||
std::unordered_map<std::string, int> name_to_id;
|
||||
for (const auto& [id, plugin] : plugins_) {
|
||||
name_to_id[plugin.name] = id;
|
||||
}
|
||||
|
||||
// 检查1:缺失依赖(deps 引用的插件未加载)
|
||||
// 检查1:缺失依赖(deps 引用的插件未加载) / Check 1: missing dependencies (deps reference plugins not loaded)
|
||||
for (const auto& [id, plugin] : plugins_) {
|
||||
for (const auto& dep_name : plugin.dependencies) {
|
||||
if (name_to_id.find(dep_name) == name_to_id.end()) {
|
||||
@@ -330,7 +356,7 @@ int PluginLoader::validate_dependencies() const
|
||||
}
|
||||
}
|
||||
|
||||
// 检查2:循环依赖(拓扑排序失败)
|
||||
// 检查2:循环依赖(拓扑排序失败) / Check 2: circular dependency (topological sort fails)
|
||||
try {
|
||||
topological_sort();
|
||||
} catch (const std::runtime_error&) {
|
||||
@@ -344,12 +370,19 @@ int PluginLoader::validate_dependencies() const
|
||||
return error_count > 0 ? -1 : 0;
|
||||
}
|
||||
|
||||
// 按依赖顺序初始化所有未初始化的插件。
|
||||
// 无效依赖或失败初始化会标记插件名,避免级联失败。
|
||||
// 返回初始化失败的插件数量,严重错误返回 -1。
|
||||
// Initialize all uninitialized plugins in dependency order.
|
||||
// Invalid dependencies or failed inits mark the plugin name, avoiding cascading failures.
|
||||
// Returns the number of plugins that failed to initialize, or -1 on critical error.
|
||||
int PluginLoader::initialize_all(const dstalk_host_api_t* host_api)
|
||||
{
|
||||
if (!host_api) return -1;
|
||||
host_api_ = host_api;
|
||||
|
||||
// 依赖合法性校验(log 错误但不 crash,继续初始化流程)
|
||||
// Validate dependencies (log errors but don't crash, continue initialization)
|
||||
if (validate_dependencies() != 0) {
|
||||
host_api->log(DSTALK_LOG_WARN,
|
||||
"[plugin_loader] Dependency validation failed; initialization may be incomplete");
|
||||
@@ -368,7 +401,7 @@ int PluginLoader::initialize_all(const dstalk_host_api_t* host_api)
|
||||
PluginInfo& plugin = it->second;
|
||||
if (plugin.initialized) continue;
|
||||
|
||||
// 检查依赖是否已失败
|
||||
// 检查依赖是否已失败 / Check if dependency has already failed
|
||||
bool dep_unavailable = false;
|
||||
for (const auto& dep_name : plugin.dependencies) {
|
||||
if (failed_names.count(dep_name)) {
|
||||
@@ -415,13 +448,17 @@ int PluginLoader::initialize_all(const dstalk_host_api_t* host_api)
|
||||
|
||||
return failed_count;
|
||||
} catch (const std::runtime_error&) {
|
||||
// 循环依赖
|
||||
// 循环依赖 / Circular dependency
|
||||
return -1;
|
||||
} catch (const std::exception&) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// 仅初始化尚未初始化的插件(用于增量/按需加载)。
|
||||
// 返回新初始化的插件数量,失败返回 -1。
|
||||
// Initialize only plugins that haven't been initialized yet (used for incremental/on-demand loading).
|
||||
// Returns the number of newly initialized plugins, or -1 on failure.
|
||||
int PluginLoader::initialize_pending(const dstalk_host_api_t* host_api)
|
||||
{
|
||||
host_api_ = host_api;
|
||||
@@ -463,15 +500,17 @@ int PluginLoader::initialize_pending(const dstalk_host_api_t* host_api)
|
||||
}
|
||||
}
|
||||
|
||||
// 按逆依赖顺序关闭所有插件,然后释放所有 DLL 句柄并清空 map。
|
||||
// Shutdown all plugins in reverse dependency order, then free all DLL handles and clear the map.
|
||||
void PluginLoader::shutdown_all()
|
||||
{
|
||||
// 按逆序关闭
|
||||
// 按逆序关闭 / Shutdown in reverse order
|
||||
std::vector<int> order;
|
||||
try {
|
||||
order = topological_sort();
|
||||
std::reverse(order.begin(), order.end());
|
||||
} catch (...) {
|
||||
// 如果排序失败,按任意顺序关闭
|
||||
// 如果排序失败,按任意顺序关闭 / If sorting fails, shutdown in arbitrary order
|
||||
for (const auto& [id, _] : plugins_) {
|
||||
order.push_back(id);
|
||||
}
|
||||
@@ -496,7 +535,7 @@ void PluginLoader::shutdown_all()
|
||||
plugin.initialized = false;
|
||||
}
|
||||
|
||||
// 释放所有 DLL 句柄
|
||||
// 释放所有 DLL 句柄 / Free all DLL handles
|
||||
for (auto& [id, plugin] : plugins_) {
|
||||
if (plugin.handle) {
|
||||
#ifdef _WIN32
|
||||
@@ -510,6 +549,8 @@ void PluginLoader::shutdown_all()
|
||||
plugins_.clear();
|
||||
}
|
||||
|
||||
// 按 ID 查找插件。返回 PluginInfo 指针,未找到则返回 nullptr。
|
||||
// Look up a plugin by ID. Returns pointer to PluginInfo, or nullptr if not found.
|
||||
const PluginInfo* PluginLoader::get_plugin(int plugin_id) const
|
||||
{
|
||||
auto it = plugins_.find(plugin_id);
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* @file plugin_loader.hpp
|
||||
* @brief DLL plugin loader with topological sort for dependency-ordered initialization.
|
||||
* DLL 插件加载器,使用拓扑排序实现按依赖顺序初始化。
|
||||
* Copyright (c) 2026 dstalk contributors. GPLv3.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "dstalk/dstalk_host.h"
|
||||
@@ -8,6 +15,8 @@
|
||||
|
||||
namespace dstalk {
|
||||
|
||||
// 描述单个已加载插件:标识、DLL 句柄、信息 vtable 和初始化状态。
|
||||
// Describes a single loaded plugin: identity, DLL handle, info vtable, and init state.
|
||||
struct PluginInfo {
|
||||
int id;
|
||||
std::string name;
|
||||
@@ -16,42 +25,47 @@ struct PluginInfo {
|
||||
int api_version;
|
||||
std::vector<std::string> dependencies;
|
||||
|
||||
void* handle; // DLL handle
|
||||
void* handle; // DLL 句柄 / DLL handle
|
||||
dstalk_plugin_info_t* info;
|
||||
bool initialized;
|
||||
};
|
||||
|
||||
// 管理基于 DLL 的插件生命周期:加载、卸载、验证依赖、
|
||||
// 拓扑排序初始化、关闭和 JSON 列表。
|
||||
// Manages the lifecycle of DLL-based plugins: load, unload, validate dependencies,
|
||||
// topological-sort initialization, shutdown, and JSON listing.
|
||||
class PluginLoader {
|
||||
public:
|
||||
PluginLoader() = default;
|
||||
~PluginLoader();
|
||||
|
||||
// 加载插件(返回插件ID,失败返回-1)
|
||||
// 加载插件(返回插件ID,失败返回-1) / Load plugin (returns plugin ID, -1 on failure)
|
||||
int load_plugin(const char* path);
|
||||
|
||||
// 卸载插件
|
||||
// 卸载插件 / Unload plugin
|
||||
int unload_plugin(int plugin_id);
|
||||
|
||||
// 获取插件列表(JSON格式)
|
||||
// 获取插件列表(JSON格式) / Get plugin list (JSON format)
|
||||
std::string list_plugins() const;
|
||||
|
||||
// 按依赖顺序初始化所有插件
|
||||
// 按依赖顺序初始化所有插件 / Initialize all plugins in dependency order
|
||||
int initialize_all(const dstalk_host_api_t* host_api);
|
||||
|
||||
// 仅初始化尚未初始化的插件(增量加载场景)
|
||||
// 仅初始化尚未初始化的插件(增量加载场景) / Initialize only uninitialized plugins (incremental loading scenario)
|
||||
int initialize_pending(const dstalk_host_api_t* host_api);
|
||||
|
||||
// 关闭所有插件
|
||||
// 关闭所有插件 / Shutdown all plugins
|
||||
void shutdown_all();
|
||||
|
||||
// 获取插件信息
|
||||
// 获取插件信息 / Get plugin info
|
||||
const PluginInfo* get_plugin(int plugin_id) const;
|
||||
|
||||
private:
|
||||
// 拓扑排序(按依赖顺序)
|
||||
// 拓扑排序(按依赖顺序) / Topological sort (by dependency order)
|
||||
std::vector<int> topological_sort() const;
|
||||
|
||||
// 依赖合法性校验(缺失依赖 + 循环依赖),返回 0 成功 / -1 失败
|
||||
// Validate dependencies (missing + circular), returns 0 success / -1 failure
|
||||
int validate_dependencies() const;
|
||||
|
||||
std::unordered_map<int, PluginInfo> plugins_;
|
||||
|
||||
@@ -1,22 +1,30 @@
|
||||
/* @file service_registry.cpp
|
||||
* @brief ServiceRegistry implementation: register, query, unregister with reader-writer locking.
|
||||
* ServiceRegistry 实现:基于读写锁的 register、query、unregister。
|
||||
* Copyright (c) 2026 dstalk contributors. GPLv3.
|
||||
*/
|
||||
|
||||
#include "service_registry.hpp"
|
||||
|
||||
namespace dstalk {
|
||||
|
||||
// 注册指定版本的命名服务 / Register a named service at a given version. 参数为空返回 -1,已注册返回 -2 / Returns -1 on null args, -2 if already registered.
|
||||
int ServiceRegistry::register_service(const char* name, int version, void* vtable)
|
||||
{
|
||||
if (!name || !vtable) return -1;
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
// 检查是否已注册
|
||||
// 检查是否已注册 / Check if already registered
|
||||
if (services_.find(name) != services_.end()) {
|
||||
return -2; // 已存在
|
||||
return -2; // 已存在 / already registered
|
||||
}
|
||||
|
||||
services_[name] = {name, version, vtable};
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 按名称和最低版本查询服务 / Query a service by name and minimum version. 返回 vtable 指针或 nullptr / Returns vtable pointer or nullptr if not found.
|
||||
void* ServiceRegistry::query_service(const char* name, int min_version) const
|
||||
{
|
||||
if (!name) return nullptr;
|
||||
@@ -31,6 +39,7 @@ void* ServiceRegistry::query_service(const char* name, int min_version) const
|
||||
return it->second.vtable;
|
||||
}
|
||||
|
||||
// 注销指定名称的服务(name 为空或未找到时无操作)/ Unregister a named service (no-op if name is null or not found).
|
||||
void ServiceRegistry::unregister_service(const char* name)
|
||||
{
|
||||
if (!name) return;
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/* @file service_registry.hpp
|
||||
* @brief Name-versioned service registry for decoupled plugin communication.
|
||||
* 基于名称+版本的服务注册表,用于插件间解耦通信。
|
||||
* Copyright (c) 2026 dstalk contributors. GPLv3.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
@@ -7,18 +13,23 @@
|
||||
|
||||
namespace dstalk {
|
||||
|
||||
// 名称 + 最低版本服务目录 / Name + minimum-version service directory.
|
||||
// 插件注册 vtable;消费者按名称和版本约束查询 /
|
||||
// Plugins register vtables; consumers query by name and version constraint.
|
||||
// 读取(query)使用 shared_lock;写入(register/unregister)使用 unique_lock /
|
||||
// Reads (query) use shared_lock; writes (register/unregister) use unique_lock.
|
||||
class ServiceRegistry {
|
||||
public:
|
||||
ServiceRegistry() = default;
|
||||
~ServiceRegistry() = default;
|
||||
|
||||
// 注册服务
|
||||
// 注册服务 / Register a named service at a given version
|
||||
int register_service(const char* name, int version, void* vtable);
|
||||
|
||||
// 查询服务(返回 vtable 指针,或 nullptr)
|
||||
// 查询服务(返回 vtable 指针,或 nullptr)/ Query a service by name and minimum version
|
||||
void* query_service(const char* name, int min_version) const;
|
||||
|
||||
// 注销服务
|
||||
// 注销服务 / Unregister a named service
|
||||
void unregister_service(const char* name);
|
||||
|
||||
private:
|
||||
@@ -28,7 +39,7 @@ private:
|
||||
void* vtable;
|
||||
};
|
||||
|
||||
mutable std::shared_mutex mutex_;
|
||||
mutable std::shared_mutex mutex_; // 读写锁:query 用 shared,register/unregister 用 unique / RW lock: shared for query, unique for register/unregister
|
||||
std::unordered_map<std::string, ServiceEntry> services_;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user