/* * @file example_plugin.cpp * @brief Example plugin demonstrating the dstalk plugin API contract. * 示例插件:演示 dstalk 插件 API 契约。 * Copyright (c) 2026 dstalk contributors. GPLv3. * * Build instructions (conceptual) / 构建说明(概念性): * * Linux / macOS: * g++ -std=c++20 -shared -fPIC -fvisibility=hidden \ * -I \ * -o example_plugin.so example_plugin.cpp * * Windows (MSVC): * cl /std:c++20 /LD /EHsc \ * /I \ * /Fe:example_plugin.dll example_plugin.cpp * * The resulting `.so` / `.dylib` / `.dll` can be loaded with: * 生成的 .so / .dylib / .dll 可通过以下方式加载: * * int id = dstalk_plugin_load("./example_plugin.so"); */ #include "dstalk/dstalk_host.h" #include /* fprintf */ #include /* malloc, free */ #include /* strlen, strcmp */ /* ------------------------------------------------------------------ * 私有状态(每个插件加载实例一份) / Private state (one instance per plugin load) * ------------------------------------------------------------------ * * In a more complex plugin this struct would hold open database * connections, configuration, etc. * 在更复杂的插件中,此结构体可包含打开的数据库连接、配置等。 */ struct ExampleState { int call_count; }; /* ------------------------------------------------------------------ * 保存主机 API 表,以便回调函数使用主机服务 / Stored host API table so callbacks can use host services. * ------------------------------------------------------------------ */ static const dstalk_host_api_t* g_host = nullptr; static ExampleState g_state; /* 非堆分配:在库映射期间持续有效 / not heap-allocated: stays valid while the library is mapped */ /* ------------------------------------------------------------------ * on_init(原 on_load) / on_init (was on_load) * ------------------------------------------------------------------ */ // 插件初始化:保存主机指针,重置调用计数,记录加载消息 / Plugin init: store host pointer, reset call count, log loaded message. static int my_on_init(const dstalk_host_api_t* host) { g_host = host; g_state.call_count = 0; /* TODO: 真实插件应在此处初始化资源 / real plugins would initialise resources here: * - 通过 host->config_get 解析插件专属配置文件 / parse a plugin-specific config file via host->config_get * - 打开日志文件 / open a log file * - 连接到本地服务 / connect to a local service * - 通过 host->register_service 注册服务 / register services via host->register_service * * Return non-zero to signal a fatal initialisation error to the * host, which will then unload the plugin immediately. * 返回非零值以向主机报告致命初始化错误,主机将立即卸载该插件。 */ if (host) { host->log(DSTALK_LOG_INFO, "[example-plugin] loaded (v1.0.0)"); } else { std::fprintf(stderr, "[example-plugin] loaded (v1.0.0)\n"); } return 0; } /* ------------------------------------------------------------------ * on_shutdown(原 on_unload) / on_shutdown (was on_unload) * ------------------------------------------------------------------ */ // 插件关闭:记录调用次数,释放资源 / Plugin shutdown: log call count, release any resources. static void my_on_shutdown(void) { /* TODO: 释放 on_init 中分配的所有资源。此函数返回后主机将卸载共享库。 / release any resources allocated in on_init. After this * function returns the host will unmap the shared library. */ if (g_host) { g_host->log(DSTALK_LOG_INFO, "[example-plugin] unloaded (%d events processed)", g_state.call_count); } else { std::fprintf(stderr, "[example-plugin] unloaded (%d callbacks processed)\n", g_state.call_count); } } /* ------------------------------------------------------------------ * on_event(原 on_message) / on_event (was on_message) * ------------------------------------------------------------------ */ // 插件事件处理:记录消息事件,忽略其他事件类型 / Plugin event handler: log message events, ignore other event types. static void my_on_event(int event_type, const void* data) { if (event_type == DSTALK_EVENT_MESSAGE && data) { const auto* msg = static_cast(data); g_state.call_count++; /* 真实插件可能: / A real plugin might: * - 将对话记录到文件 / log the conversation to a file * - 实施内容审核 / apply content moderation * - 实时翻译消息 / translate messages on the fly * - 用外部数据丰富消息 / enrich messages with external data */ if (g_host) { g_host->log(DSTALK_LOG_DEBUG, "[example-plugin] message | role=%-9s len=%zu", msg->role, std::strlen(msg->content)); } else { std::fprintf(stderr, "[example-plugin] message | role=%-9s len=%zu\n", msg->role, std::strlen(msg->content)); } } /* 其他事件类型 / Other event types (DSTALK_EVENT_SESSION_CLEAR, DSTALK_EVENT_CONFIG_CHANGED, DSTALK_EVENT_PLUGIN_LOADED, DSTALK_EVENT_PLUGIN_UNLOADED, DSTALK_EVENT_CUSTOM+) 此最小化插件静默忽略 / are silently ignored by this minimal plugin. */ } /* ------------------------------------------------------------------ * 插件描述符(静态 —— 在 .so 的生命周期内有效) / Plugin descriptor (static -- lives for the lifetime of the .so) * ------------------------------------------------------------------ */ static dstalk_plugin_info_t g_info = { /* .name = */ "example-plugin", /* .version = */ "1.0.0", /* .description = */ "An example plugin for dstalk / dstalk 示例插件", /* .api_version = */ DSTALK_API_VERSION, /* .dependencies = */ {nullptr}, /* .on_init = */ my_on_init, /* .on_shutdown = */ my_on_shutdown, /* .on_event = */ my_on_event, }; /* ------------------------------------------------------------------ * 必须入口点 / Mandatory entry point * ------------------------------------------------------------------ * * The host looks for this symbol via dlsym / GetProcAddress. * 主机通过 dlsym / GetProcAddress 查找此符号。 * It MUST be declared extern "C" so the name is not mangled. * 必须声明为 extern "C" 以避免名称修饰。 */ // 返回插件描述符给主机加载器 / Returns the plugin descriptor to the host loader. extern "C" DSTALK_PLUGIN_EXPORT dstalk_plugin_info_t* dstalk_plugin_init(void) { return &g_info; }