Extension - 508364/NetLeaf GitHub Wiki
NetLeaf v2.4.0 支持动态扩展库,允许第三方开发者创建遵循与内置模块相同规范的扩展模块。这些扩展库可以在运行时动态加载,并正确注册到主库的模块系统中。
重要变更 (v2.4.0):
⚠️ 静态库已不再支持,扩展库必须动态链接- 扩展库使用新的
NL_EXTENSION_DEFINE宏定义 - 支持自动加载机制(扫描 extensions/ 目录)
- 完整的扩展生命周期管理 API
- 扩展搜索、过滤、元数据管理 API
每个扩展库必须包含以下字段:
| 字段 | 必需 | 说明 | 限制 |
|---|---|---|---|
library_name |
✅ | 库显示名称 | 64字符 |
library_id |
✅ | 唯一标识符 | 32字符 |
version |
✅ | 版本字符串 | 16字符 |
author |
✅ | 作者名称 | 64字符 |
description |
❌ | 简短描述 | 50中文字符(约150字节) |
platforms |
✅ | 支持的平台 | "Windows,Linux,MacOS" 或 "all" |
重要: library_value 是由主库在加载时自动分配的值,用于内部标识。用户不应在定义时设置此字段。
扩展库遵循与内置模块(如 linkagg、vue、ipc 等)相同的接口规范:
- 使用
NL_EXTENSION_DEFINE宏定义扩展信息 - 支持懒加载和模块能力标志
- 通过自动加载机制注册到扩展系统
- 链接主库的共享库(netleaf.dll/netleaf.so)以共享全局状态
重要: 扩展库必须链接主库的共享库(netleaf.dll),而不是静态库。这样扩展库和主程序都使用同一个 netleaf.dll,共享全局变量(如模块列表),确保模块注册能正确工作。
#ifndef NETLEAF_MY_EXTENSION_H
#define NETLEAF_MY_EXTENSION_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stddef.h>
#include <stdint.h>
#include "netleaf_module.h"
#ifdef _WIN32
#ifdef NL_EXTENSION_EXPORTS
#define NL_EXT_API __declspec(dllexport)
#else
#define NL_EXT_API __declspec(dllimport)
#endif
#else
#define NL_EXT_API
#endif
#define NL_MY_EXTENSION_VERSION "1.0.0"
// Extension API - 使用新的扩展库定义
NL_EXT_API nl_extension_info_t* nl_my_extension_get_info(void);
NL_EXT_API int nl_my_extension_is_available(void);
NL_EXT_API const char* nl_my_extension_version(void);
NL_EXT_API int nl_my_extension_init(void);
NL_EXT_API void nl_my_extension_shutdown(void);
// 扩展功能 API
NL_EXT_API int nl_my_extension_process(const char* input, char* output, size_t output_size);
#ifdef __cplusplus
}
#endif
#endif // NETLEAF_MY_EXTENSION_H使用新的 NL_EXTENSION_DEFINE 宏:
#include "netleaf_my_extension.h"
#include "netleaf.h"
#include "netleaf_module.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int g_extension_initialized = 0;
// 使用 NL_EXTENSION_DEFINE 宏定义扩展库
// 必需字段:library_id, library_name, version, author
// 可选字段:description(限制50个中文字符)
// platforms: "Windows,Linux,MacOS" (大小写不限,顺序不限,或使用 "all")
NL_EXTENSION_DEFINE(
my_ext, // library_id(唯一标识符)
"My Extension", // library_name(显示名称)
NL_MY_EXTENSION_VERSION, // version
"Author Name", // author
"这是一个示例扩展库", // description(可选,最多50个中文字符)
"Windows,Linux,MacOS", // platforms(支持大小写不限、顺序不限,或 "all")
NL_CAP_THREAD_SAFE, // capabilities
nl_my_extension_init, // init
nl_my_extension_shutdown, // shutdown
nl_my_extension_is_available, // is_available
nl_my_extension_version // get_version
)
NL_EXT_API nl_extension_info_t* nl_my_extension_get_info(void) {
return NL_EXTENSION_GET_INFO(my_ext);
}
NL_EXT_API int nl_my_extension_is_available(void) {
return 1;
}
NL_EXT_API const char* nl_my_extension_version(void) {
return NL_MY_EXTENSION_VERSION;
}
NL_EXT_API int nl_my_extension_init(void) {
if (g_extension_initialized) return 0;
g_extension_initialized = 1;
printf("[My Extension] Initialized\n");
return 0;
}
NL_EXT_API void nl_my_extension_shutdown(void) {
if (!g_extension_initialized) return;
g_extension_initialized = 0;
printf("[My Extension] Shutdown\n");
}
NL_EXT_API int nl_my_extension_process(const char* input, char* output, size_t output_size) {
if (!input || !output || output_size == 0) return -1;
snprintf(output, output_size, "Processed: %s", input);
return 0;
}cmake_minimum_required(VERSION 3.10)
project(netleaf_my_extension)
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/../../include
)
# 创建扩展库(必须为 SHARED)
add_library(netleaf_my_extension SHARED my_extension.c)
# 关键:链接主库的共享库(netleaf.dll)
# 这确保扩展库和主程序共享同一个全局状态
if(WIN32)
target_compile_definitions(netleaf_my_extension PRIVATE NL_EXTENSION_EXPORTS)
target_link_libraries(netleaf_my_extension netleaf_shared ws2_32)
else()
target_link_libraries(netleaf_my_extension -L${CMAKE_RUNTIME_OUTPUT_DIRECTORY} -lnetleaf -ldl -lpthread)
endif()
set_target_properties(netleaf_my_extension PROPERTIES
OUTPUT_NAME "netleaf_my_extension"
PREFIX ""
)
install(TARGETS netleaf_my_extension DESTINATION extensions)扩展库应该在主库构建完成后编译:
# Windows: 在 Visual Studio Developer Command Prompt 中运行
build_all.bat
# Linux: 直接运行脚本
./build_all.sh将编译好的扩展库放到 extensions/ 目录:
NetLeaf/
├── bin/Release/
│ ├── netleaf.dll # 主库
│ └── extensions/ # 扩展库目录
│ └── netleaf_my_extension.dll # 你的扩展库
v2.4.0 支持扩展库自动加载:
-
目录结构: 将扩展库放入
extensions/子目录(与 netleaf.dll 同级) - 自动识别: 主库初始化时自动扫描并加载所有扩展库
-
标准符号命名: 支持多种符号命名约定(
my_ext_get_info、get_my_ext_info、my_ext_extension_info) -
无需手动调用:
nl_modules_init()会自动触发加载
// 主库初始化会自动加载 extensions/ 目录中的所有扩展库
nl_modules_init();
// 检查已加载的扩展库
int ext_count = nl_extension_get_count();
printf("已加载 %d 个扩展库\n", ext_count);
// 获取特定扩展库信息
nl_extension_info_t* info = nl_extension_get_info("my_ext");
if (info) {
printf("扩展库: %s v%s\n", info->library_name, info->version);
}// 初始化扩展库
int ret = nl_extension_init("my_ext");
if (ret < 0) {
printf("初始化失败\n");
}
// 关闭扩展库
nl_extension_shutdown("my_ext");
// 强制关闭(清理资源)
nl_extension_force_shutdown("my_ext");
// 批量操作
nl_extension_init_all(); // 初始化所有扩展库
nl_extension_shutdown_all(); // 关闭所有扩展库
nl_extension_force_shutdown_all(); // 强制关闭所有扩展库// 检查是否已初始化
int initialized = nl_extension_is_initialized("my_ext");
// 检查是否正在运行
int running = nl_extension_is_running("my_ext");
// 获取状态
nl_module_status_t status = nl_extension_get_state("my_ext");// 获取基本信息(便捷函数)
const char* name = nl_extension_get_name("my_ext");
const char* author = nl_extension_get_author("my_ext");
const char* desc = nl_extension_get_description("my_ext");
uint32_t caps = nl_extension_get_caps("my_ext");
// 检查平台支持
int supports_win = nl_extension_supports_platform("my_ext", "windows");
int supports_linux = nl_extension_supports_platform("my_ext", "linux");
int supports_macos = nl_extension_supports_platform("my_ext", "macos");// 按能力搜索
nl_extension_info_t* results[32];
int count = nl_extension_find_by_capability(NL_CAP_THREAD_SAFE, results, 32);
for (int i = 0; i < count; i++) {
printf("线程安全扩展: %s\n", results[i]->library_name);
}
// 按平台搜索
count = nl_extension_find_by_platform("windows", results, 32);
// 按名称模式搜索
count = nl_extension_find_by_name_pattern("*chat*", results, 32);// 设置元数据
nl_extension_set_metadata("my_ext", "github_url", "https://github.com/user/repo");
nl_extension_set_metadata("my_ext", "license", "MIT");
// 获取元数据
char value[256];
nl_extension_get_metadata("my_ext", "github_url", value, sizeof(value));
printf("GitHub: %s\n", value);// 重载单个扩展库
nl_extension_reload("my_ext");
// 重载所有扩展库
nl_extension_reload_all();| 函数 | 说明 |
|---|---|
nl_extension_get_info(library_id) |
按 ID 获取扩展库信息 |
nl_extension_register(info) |
注册扩展库(自动分配 library_value) |
nl_extension_unregister(library_id) |
注销扩展库 |
nl_extension_get_count() |
获取已注册扩展库数量 |
nl_extension_get_all(count) |
获取所有扩展库数组 |
nl_extension_validate_description(desc) |
验证描述长度 |
| 函数 | 说明 |
|---|---|
nl_extension_init(library_id) |
初始化扩展库 |
nl_extension_shutdown(library_id) |
关闭扩展库 |
nl_extension_force_shutdown(library_id) |
强制关闭扩展库 |
nl_extension_init_all() |
初始化所有扩展库 |
nl_extension_shutdown_all() |
关闭所有扩展库 |
nl_extension_force_shutdown_all() |
强制关闭所有扩展库 |
| 函数 | 说明 |
|---|---|
nl_extension_is_initialized(library_id) |
检查是否已初始化 |
nl_extension_is_running(library_id) |
检查是否正在运行 |
nl_extension_get_state(library_id) |
获取模块状态 |
| 函数 | 说明 |
|---|---|
nl_extension_get_name(library_id) |
获取扩展库名称 |
nl_extension_get_author(library_id) |
获取作者名称 |
nl_extension_get_description(library_id) |
获取描述 |
nl_extension_get_caps(library_id) |
获取能力标志 |
nl_extension_supports_platform(library_id, platform) |
检查平台支持 |
| 函数 | 说明 |
|---|---|
nl_extension_find_by_capability(cap, results, max_count) |
按能力搜索 |
nl_extension_find_by_platform(platform, results, max_count) |
按平台搜索 |
nl_extension_find_by_name_pattern(pattern, results, max_count) |
按名称模式搜索 |
| 函数 | 说明 |
|---|---|
nl_extension_get_metadata(library_id, key, value, val_size) |
获取元数据 |
nl_extension_set_metadata(library_id, key, value) |
设置元数据 |
| 函数 | 说明 |
|---|---|
nl_extension_reload(library_id) |
重载单个扩展库 |
nl_extension_reload_all() |
重载所有扩展库 |
| 函数 | 说明 |
|---|---|
nl_extension_get_value_by_id(library_id) |
通过标识符获取值 |
nl_extension_get_id_by_value(library_value) |
通过值获取标识符 |
示例:
// 注册后获取自动分配的值
nl_extension_register(info);
int32_t value = nl_extension_get_value_by_id("my_ext");
printf("my_ext's value: %d\n", value);
// 通过值反向查询标识符
const char* id = nl_extension_get_id_by_value(1);
printf("value 1's id: %s\n", id);| 函数 | 说明 |
|---|---|
nl_extension_auto_load() |
从 extensions/ 目录自动加载 |
nl_extension_auto_load_from_dir(dir) |
从指定目录自动加载 |
nl_extension_set_auto_load_dir(dir) |
设置自动加载目录 |
nl_extension_get_auto_load_dir() |
获取自动加载目录 |
| 函数 | 说明 |
|---|---|
nl_module_register(info) |
注册模块 |
nl_module_unregister(type) |
注销模块 |
nl_module_get_info(type) |
按类型获取模块 |
nl_module_get_info_by_name(name) |
按名称获取模块 |
nl_module_get_count() |
获取模块数量 |
nl_print_modules() |
打印所有模块 |
| 标志 | 值 | 说明 |
|---|---|---|
NL_CAP_NONE |
0 | 无能力 |
NL_CAP_SERVER |
1 << 0 | 支持创建服务器 |
NL_CAP_CLIENT |
1 << 1 | 支持创建客户端 |
NL_CAP_ASYNC |
1 << 2 | 支持异步操作 |
NL_CAP_THREAD_SAFE |
1 << 3 | 线程安全 |
NL_CAP_PLATFORM_WIN |
1 << 4 | Windows 支持 |
NL_CAP_PLATFORM_LINUX |
1 << 5 | Linux 支持 |
NL_CAP_PLATFORM_MACOS |
1 << 6 | macOS 支持 |
NL_CAP_LAZY_LOAD |
1 << 7 | 支持懒加载 |
NL_CAP_DYNAMIC |
1 << 8 | 可动态加载 |
NL_CAP_PLUGIN |
1 << 9 | 插件模块 |
NL_CAP_EXT_SYSTEM |
1 << 10 | NL 扩展系统组件 |
必须链接 netleaf_shared(动态库),而不是 netleaf_static:
# 正确
target_link_libraries(my_extension netleaf_core)
# 错误 - 会导致全局状态隔离
target_link_libraries(my_extension ${CMAKE_CURRENT_SOURCE_DIR}/path/to/netleaf_static)Windows 上使用 __declspec(dllexport):
#ifdef _WIN32
#ifdef NL_EXTENSION_EXPORTS
#define NL_EXT_API __declspec(dllexport)
#else
#define NL_EXT_API __declspec(dllimport)
#endif
#else
#define NL_EXT_API
#endif始终检查加载结果:
nl_plugin_handle_t ext = nl_plugin_load(path);
if (!ext) {
printf("Error: %s\n", nl_plugin_get_error());
return -1;
}确保扩展库支持所有目标平台:
.platforms = "Windows,Linux,MacOS", // 或使用 "all"完整示例位于 examples/plugin_template/ 目录:
-
netleaf_plugin_template.h- 头文件模板 -
plugin.c- 实现文件 -
CMakeLists.txt- 构建配置 -
README.md- 使用说明
原因: 扩展库链接了静态库而非共享库
解决: 确保 CMakeLists.txt 中链接 netleaf_shared
原因: 导出宏未正确配置
解决: 检查 NL_EXTENSION_EXPORTS 定义是否在编译时设置
原因: 扩展库和主库使用不同的 netleaf.dll 实例
解决: 确保扩展库输出目录与主程序相同,或使用相同的 PATH 环境变量