本章介绍 VLink 的扩展系统:动态插件接口、schema 反射注册、其他常用扩展组件。
**相关文档**:代理层插件加载参见 16-proxy.md;录制时的 Schema 插件集成参见 12-bag-recording.md。
扩展与插件一览
include/vlink/extension/ 目录下的 header,按用途分组:
**录制 / 回放**:bag_writer.h、bag_reader.h、bag_reader_processor.h、 bag_reader_plugin_interface.h、database_writer.h、database_reader.h、 mcap_writer.h、mcap_reader.h(录制细节见 12-bag-recording.md)
**QoS 与安全**:qos.h、qos_profile.h、security.h (细节见 08-qos.md、09-security.md)
**状态 / 发现**:status.h、status_detail.h、discovery_reporter.h、discovery_viewer.h
**插件接口**:schema_plugin_interface.h、schema_plugin_base.h、schema_plugin_manager.h、 runnable_plugin_interface.h、message_convert_plugin.h、bag_reader_plugin_interface.h
**schema 注册辅助**:flatbuffers_registry.h、protobuf_registry.h
**其他工具**:dynamic_data.h、terminal_stream.h、url_remap.h
说明:ConfPluginInterface(传输 Conf 插件)位于 include/vlink/impl/conf_plugin_interface.h, LoggerPluginInterface 位于 include/vlink/base/logger_plugin_interface.h, Plugin 加载器位于 include/vlink/base/plugin.h。
插件接口全景
核心宏
Plugin — 动态插件加载器
头文件:<vlink/base/plugin.h>
Plugin 是 VLink 插件系统的核心,封装了 dlopen / LoadLibrary,提供类型安全的动态库加载、版本校验和生命周期管理。
主要接口
class Plugin final {
public:
template <class T>
std::shared_ptr<T> load(
const std::string& lib_name,
uint16_t version_major,
uint16_t version_minor,
const std::string& dir_name = "",
const std::deque<std::string>& search_paths = default_search_path(),
const std::string& function_name = "vlink_plugin_create"
);
template <class T>
bool unload(const std::string& lib_name);
template <class T>
bool has_loaded(const std::string& lib_name);
void clear();
static std::deque<std::string> default_search_path();
};
工作原理
- load<T>() 按 search_paths 依次搜索 lib_name(自动添加平台前缀/后缀,如 lib 前缀和 .so 后缀)。
- 打开共享库后调用 vlink_plugin_create 入口点,传入 **插件 ID**(由 T::get_plugin_id() 获取)和版本号。
- 入口点内部调用 Plugin::process_plugin_internal() 进行 ID 和版本校验,失败返回 nullptr。
- 返回的指针被包装为 shared_ptr<T>,其自定义删除器在引用计数归零时调用 vlink_plugin_destroy 并关闭共享库。
使用示例
if (logger) {
logger->init("my_app");
}
Pure-virtual interface for a custom logger backend loaded as a dynamic plugin.
定义 logger_plugin_interface.h:90
Type-safe dynamic plugin loader with version verification and lifecycle management.
定义 plugin.h:122
bool unload(const std::string &lib_name)
Unloads the plugin library for interface T.
定义 plugin.h:300
void set_log_level(Logger::Level level)
Sets the log level used for plugin load/unload diagnostics.
std::shared_ptr< T > load(const std::string &lib_name, uint16_t version_major, uint16_t version_minor, const std::string &dir_name="", const std::deque< std::string > &search_paths=default_search_path(), const std::string &function_name=VLINK_MACRO_STRING_GET(VLINK_PLUGIN_CREATE_FUNC_NAME))
Loads a plugin implementing interface T from a shared library.
定义 plugin.h:281
RunablePluginInterface — 可运行插件
头文件:<vlink/extension/runnable_plugin_interface.h>
RunablePluginInterface(注意:类名中 Runable 为源码中的实际拼写)继承自 MessageLoop,允许插件携带自己的事件循环线程,实现完全自包含的功能组件。
接口定义
class RunablePluginInterface : public MessageLoop {
public:
virtual void on_init() = 0;
virtual void on_deinit() = 0;
};
生命周期
RunablePluginInterface 的公开契约是:宿主先启动插件自己的 MessageLoop,再由宿主线程显式调用 on_init();on_deinit() 同样由宿主在线程外显式触发。它们都不是插件 loop 线程里的隐式回调。
dlopen
-> create (VLINK_PLUGIN_DECLARE)
-> async_run() // 启动插件自己的 MessageLoop 线程
-> on_init() // 宿主线程调用,初始化订阅/定时器等资源
... 运行中 ...
-> on_deinit() // 宿主线程调用,释放资源
-> quit() // 停止循环线程
-> destroy
dlclose
实现示例
#pragma once
public:
sub_ = std::make_unique<vlink::Subscriber<vlink::Bytes>>("dds://sensor/lidar");
VLOG_I(
"Received lidar data, size=", data.
size());
});
}
sub_.reset();
}
private:
std::unique_ptr<vlink::Subscriber<vlink::Bytes>> sub_;
};
#include "my_sensor_plugin.h"
Versatile 128-byte byte buffer with SBO, five ownership modes and compression helpers.
定义 bytes.h:113
size_t size() const noexcept
Returns the number of usable bytes (excluding the prefix offset region).
定义 bytes.h:868
Abstract plugin interface that provides its own MessageLoop event thread.
定义 runnable_plugin_interface.h:76
virtual void on_init()=0
Called by the host after the plugin's event loop has started.
virtual void on_deinit()=0
Called by the host before the plugin is unloaded.
#define VLOG_I(...)
定义 logger.h:850
#define VLINK_PLUGIN_DECLARE(ImplementType, VersionMajor, VersionMinor)
Declares a plugin creation and destruction interface.
定义 plugin.h:380
#define VLINK_PLUGIN_REGISTER(InterfaceType)
Macro to register a plugin, automatically deriving its ID from the interface type name.
定义 plugin.h:343
Plugin interface for self-contained, event-loop-driven plugin components.
if (instance) {
instance->async_run();
instance->on_init();
instance->on_deinit();
instance->quit();
}
SchemaPluginInterface / SchemaPluginBase — Schema 插件
头文件:<vlink/extension/schema_plugin_interface.h>,<vlink/extension/schema_plugin_base.h>
SchemaPluginInterface 为 VLink 的 bag、MCAP、WebViz 和命令行动态解析流水线提供统一 schema 注册能力。接口同时覆盖:
- protobuf 的 FileDescriptorSet / Descriptor / 动态消息原型
- flatbuffers 的 BFBS / reflection::Schema / 运行时 Parser
接口方法
| 方法 | 说明 |
| get_version_info() | 返回插件版本和构建元数据 |
| search_protobuf_descriptor(name) | 按类型名查找 google::protobuf::Descriptor |
| search_schema(name, schema_type) | 按 schema family hint 返回 protobuf FileDescriptorSet 或 flatbuffers BFBS |
| get_all_schemas(schema_type) | 返回当前缓存/导入的全部 schema,可按 family 过滤 |
| create_protobuf_message(name) | 用 DynamicMessageFactory 创建指定类型的消息原型 |
| search_flatbuffers_schema(name) | 返回 BFBS 对应的 reflection::Schema 句柄 |
| create_flatbuffers_parser(name) | 返回已装载 root type 的 FlatBuffers Parser |
所有方法均通过内部 mutex 保证线程安全,查询结果在内部缓存以避免重复查找。
SchemaPluginBase — 内置实现
SchemaPluginBase 是 SchemaPluginInterface 的标准实现:
- protobuf 侧使用 google::protobuf::DescriptorPool::generated_pool() 和 DynamicMessageFactory
- flatbuffers 侧通过 FlatbuffersRegistry 显式导入已编译进当前库的 BFBS,并缓存 reflection::Schema / Parser 运行时句柄
SchemaPluginBase 的设计边界是:
- protobuf 行为保持与之前 protobuf-only 运行时实现一致,默认直接查当前插件/库里已经链接好的 generated descriptors
- flatbuffers 需要你在构建期生成 BFBS 嵌入代码,例如使用 flatc --bfbs-gen-embed
- 如果使用 vlink_generate_cpp(FBS ...),当前会同时生成常规头 xxx.fbs.hpp 和 BFBS 嵌入头 xxx_bfbs.fbs.hpp
- SchemaPluginBase 本身不读取 VLINK_PROTO_DIR、VLINK_FBS_DIR,也不负责从 .proto/.fbs 路径导入 schema
**启用条件**:protobuf 相关代码由 __has_include(<google/protobuf/dynamic_message.h>) 决定 (宏 VLINK_HAS_SCHEMA_PLUGIN_PROTOBUF);flatbuffers 相关代码由 VLINK_HAS_SCHEMA_PLUGIN_FLATBUFFERS 控制。 头文件本身无需额外的 #define 才能包含。
public:
return {
"MySchemaPlugin",
"1.0.0",
__DATE__ " " __TIME__,
"",
""
};
}
};
Default mixed-schema plugin base class for Protobuf and FlatBuffers.
定义 schema_plugin_base.h:85
virtual VersionInfo get_version_info() const =0
Returns version and build metadata for this plugin.
Type-safe dynamic plugin loader with version checking and lifecycle management.
Default schema plugin implementation built around linked protobuf metadata and embedded BFBS blobs.
Plugin version and build metadata.
定义 schema_plugin_interface.h:114
注册已编译 schema
典型做法是:protobuf 直接从当前库里已链接的 generated descriptors 查询;flatbuffers 则在类外静态注册已编译进当前库的 BFBS:
public:
return {"MySchemaPlugin", "1.0.0", __DATE__ " " __TIME__, "", ""};
}
};
VLINK_REGISTER_FLATBUFFERS("my.pkg.MyMessage", MyMessageBinarySchema);
其中:
- protobuf 不需要额外注册,search_protobuf_descriptor() / search_schema() 会直接按之前 protobuf-only 运行时逻辑从 generated_pool() 按需查找
- FlatbuffersRegistry::register_schema() 是静态注册函数,用于把 BFBS 二进制 schema 注册到当前库内的全局静态表
- VLINK_REGISTER_FLATBUFFERS(...) 适合在类外直接调用,支持在同一个 .cc 里调用多次,供 schema plugin 所在库在加载时完成 BFBS 注册
- VLINK_REGISTER_FLATBUFFERS_NOW(...) 会立即返回注册结果,适合你在函数体、初始化代码或自定义注册流程里手动调用
- 如果只需要 BFBS 注册能力,也可以单独包含 <vlink/extension/flatbuffers_registry.h>
search_schema 的 SchemaData 结构
struct SchemaData {
std::string name;
std::string encoding;
SchemaType schema_type;
Bytes data;
};
SchemaPluginManager — 插件管理器
头文件:<vlink/extension/schema_plugin_manager.h>
SchemaPluginManager 是进程级单例,负责加载和持有唯一的 SchemaPluginInterface 实例。
插件路径解析顺序
schema_plugin_path 可以是插件基础名,也可以是共享库路径。Plugin::load() 会在需要时自动补平台前缀/后缀并搜索默认目录。
- SchemaPluginManager::get(schema_plugin_path) 的参数(非空时优先)
- 环境变量 VLINK_SCHEMA_PLUGIN
- 以上均未设置时,is_valid() 返回 false,不加载任何插件
接口
class SchemaPluginManager final {
public:
static SchemaPluginManager& get(const std::string& schema_plugin_path = "");
bool is_valid() const;
std::shared_ptr<SchemaPluginInterface> get_interface() const;
};
使用示例
if (mgr.is_valid()) {
auto iface = mgr.get_interface();
auto schema = iface->search_schema("my_package.MyMessage", SchemaType::kProtobuf);
if (schema.schema_type == SchemaType::kProtobuf) {
}
auto* msg_ptr = static_cast<google::protobuf::Message*>(
iface->create_protobuf_message("my_package.MyMessage")
);
auto* parser = static_cast<flatbuffers::Parser*>(
iface->create_flatbuffers_parser("my_package.MyFlatbuffer")
);
}
static SchemaPluginManager & get(const std::string &schema_plugin_path="")
Returns the process-global SchemaPluginManager singleton.
ConfPluginInterface — 传输配置插件
头文件:<vlink/impl/conf_plugin_interface.h>
ConfPluginInterface 是所有外部传输插件必须实现的接口。每个传输插件导出一个实现该接口的具体类,VLink URL 系统在解析未知 Transport 时动态加载匹配插件。
插件发现机制
- 环境变量 VLINK_URL_PLUGINS 设置插件基础名列表(分号分隔,不含路径、lib 前缀和 .so 后缀;vlink- 前缀可省略)
- 或调用 Url::init_plugins() 显式注册
当 Url 构造时遇到未知 transport 时,Url::load_for_plugin() 会遍历所有已加载插件,调用 get_transport_type() 进行匹配,匹配成功后再调用 create() 获取 Conf 实例。
接口定义
struct ConfPluginInterface {
virtual TransportType get_transport_type() const = 0;
virtual std::unique_ptr<Conf> create() const = 0;
};
实现自定义传输插件
#pragma once
std::string address;
int port{8080};
return !address.empty() && port > 0;
}
return vlink::TransportType::kMyCustom;
}
};
return vlink::TransportType::kMyCustom;
}
std::unique_ptr<vlink::Conf>
create()
const override {
return std::make_unique<MyTransportConf>();
}
};
Abstract transport configuration base class and associated helper macros.
#define VLINK_CONF_IMPL(classname)
Standard boilerplate for concrete Conf subclass declarations.
定义 conf.h:227
#define VLINK_ALLOW_IMPL_TYPE(type)
Declares a static constexpr bitmask of supported ImplType values.
定义 conf.h:268
Plugin ABI for dynamically loaded transport Conf factories.
TransportType
Enumeration of all supported transport backends.
定义 types.h:107
Pure-virtual plugin interface for external transport Conf factories.
定义 conf_plugin_interface.h:80
virtual TransportType get_transport_type() const =0
Returns the transport backend handled by this plugin.
virtual std::unique_ptr< Conf > create() const =0
Creates and returns a new transport Conf instance.
Abstract base class for VLink transport configuration objects.
定义 conf.h:83
virtual bool is_valid() const
Returns true when the configuration holds valid, usable data.
virtual TransportType get_transport_type() const
Returns the transport backend this configuration represents.
LoggerPluginInterface — 日志后端插件
头文件:<vlink/base/logger_plugin_interface.h>
通过 LoggerPluginInterface 可以将 VLink 日志系统对接到任意第三方日志框架(spdlog、log4cxx、自定义文件滚动日志等)。
接口定义
class LoggerPluginInterface {
public:
virtual bool init(std::string_view app_name) = 0;
virtual bool log(int level, std::string_view str) = 0;
};
level 对应关系
| 值 | Logger::Level | 含义 |
| 0 | kTrace | 追踪 |
| 1 | kDebug | 调试 |
| 2 | kInfo | 信息 |
| 3 | kWarn | 警告 |
| 4 | kError | 错误 |
| 5 | kFatal | 致命错误 |
对接 spdlog 示例
#include <spdlog/spdlog.h>
#include <spdlog/sinks/rotating_file_sink.h>
public:
bool init(std::string_view app_name)
override {
logger_ = spdlog::rotating_logger_mt(
std::string(app_name),
"/var/log/" + std::string(app_name) + ".log",
1024 * 1024 * 10, 3
);
return true;
}
bool log(
int level, std::string_view str)
override {
if (!logger_) {
return false;
}
switch (level) {
case 1: logger_->debug(str); break;
case 2: logger_->info(str); break;
case 3: logger_->warn(str); break;
case 4: logger_->error(str); break;
default: logger_->trace(str); break;
}
return true;
}
private:
std::shared_ptr<spdlog::logger> logger_;
};
virtual bool init(std::string_view app_name)=0
Initialises the logger backend for the given application name.
virtual bool log(int level, std::string_view str)=0
Writes a single log entry to the backend.
Abstract interface for pluggable logger backends loaded via the Plugin system.
if (backend) {
backend->init("my_app");
}
MessageConvertPlugin — Foxglove / Rerun 消息转换
头文件:<vlink/extension/message_convert_plugin.h>
MessageConvertPlugin 供 vlink-foxglove 与 vlink-rerun 桥接器使用,把 VLink 的序列化 字节流转换为目标可视化后端能理解的格式。接口不依赖任何第三方库(protobuf、flatbuffers、Rerun SDK、 JSON 库都不需要),便于外部工程独立实现。
目标后端
enum class ConvertTarget : uint8_t {
kFoxglove = 0,
kRerun = 1,
};
| 目标 | payload 格式 | type_name 含义 |
| kFoxglove | FlatBuffer / Protobuf 二进制 | Foxglove schema 名 |
| kRerun | UTF-8 JSON(描述 Rerun archetype 组件) | Rerun archetype 名 |
核心虚函数
| 方法 | 用途 |
| init(config) | 插件加载后调用,返回 false 则卸载插件 |
| can_convert(vlink_ser, target) | 告知是否处理某 VLink 序列化类型 + 目标 |
| get_schema_info(vlink_ser, target, type_name, encoding, schema_encoding, schema_data) | 注册通道时返回 schema 元数据 |
| convert(vlink_ser, raw, target, payload) | 每条消息调用一次,把 raw 转换为目标格式 |
| extract_timestamp(vlink_ser, raw, target)(默认实现返回 0) | 可选:从消息提取时间戳 |
| can_convert_frontend / get_publish_info / convert_frontend | 可选:支持从浏览器 publish 到 VLink 反向通道 |
Rerun JSON payload 约定示例
// Points3D
{ "positions": [[1.0,2.0,3.0]], "colors": [[255,0,0,255]], "radii": [0.1] }
// EncodedImage(二进制数据需 base64 编码)
{ "media_type": "image/jpeg", "data_base64": "<base64>" }
// TextLog
{ "text": "hello", "level": "INFO" }
与 JSON 映射文件的关系
当同时存在 JSON 映射配置与此插件时,先尝试插件;若 can_convert 返回 false,则回退到 JSON 映射管道。因此插件只需覆盖需要自定义逻辑的类型。
线程安全
convert() 可能被 ProxyAPI 的多个数据回调线程并发调用,实现必须自行保证线程安全。
BagReaderPluginInterface — 回放转换插件
头文件:<vlink/extension/bag_reader_plugin_interface.h>
通过 BagReader::bind_plugin_interface(plugin) 注入,用于在回放时做 URL/ser_type 转换 或消息过滤。
核心方法
class BagReaderPluginInterface {
public:
virtual bool convert_url_meta(std::string& url,
std::string& ser_type,
SchemaType& schema_type) = 0;
virtual void push(int64_t timestamp, const std::string& url,
ActionType action_type, const Bytes& data) = 0;
void register_output_callback(OutputCallback&& output_callback);
};
BagReader 在 bind_plugin_interface() 中自动 register_output_callback() 注入 其内部 pipeline;插件的 push() 必须显式调用 output_callback_() 才会把消息发到 最终 OutputCallback。
FlatbuffersRegistry / ProtobufRegistry — schema 注册
protobuf_registry.h
纯包装头,只做两件事:
- #if __has_include(<google/protobuf/dynamic_message.h>) 条件 include protobuf 运行时头
- 条件定义宏 VLINK_HAS_SCHEMA_PLUGIN_PROTOBUF
Protobuf 不需要独立的运行时注册 API:generated messages 通过 google::protobuf::DescriptorPool::generated_pool() 已经可查;SchemaPluginBase 直接从 该 pool 按类型名查找。
flatbuffers_registry.h
FlatBuffers 没有等价的全局 pool,需要显式注册 BFBS 到进程级单例 FlatbuffersRegistry:
class FlatbuffersRegistry final {
public:
template <typename BinarySchema>
static bool register_schema(const std::string& name);
static bool register_schema(const std::string& name,
const uint8_t* bfbs_data, size_t bfbs_size);
SchemaData search_schema(const std::string& name);
std::vector<SchemaData> get_all_schemas();
};
两个宏简化静态注册:
| 宏 | 语义 |
| VLINK_REGISTER_FLATBUFFERS(name, BS) | 展开为翻译单元内的静态对象,加载期调用 register_schema() |
| VLINK_REGISTER_FLATBUFFERS_NOW(name, BS) | 立即求值版,返回 bool,适合手动调用 |
VLINK_REGISTER_FLATBUFFERS_NOW 需要 BFBS 二进制 schema 头已经 include;若只想使用 注册能力,可单独 #include <vlink/extension/flatbuffers_registry.h>。
DiscoveryReporter — 节点发现上报
头文件:<vlink/extension/discovery_reporter.h>
DiscoveryReporter 继承自 MessageLoop,由 NodeImpl 的构造/析构自动调用,维护当前 进程中活跃节点列表并广播上线/下线通知。应用代码一般**不需要**直接使用它; vlink-list 和 DiscoveryViewer 接收其广播。
主要 API:
class DiscoveryReporter : public MessageLoop {
public:
void add(NodeImpl* node_impl);
void remove(NodeImpl* node_impl);
};
相关环境变量:
- VLINK_DISCOVER_DISABLE:=1 完全禁用 Reporter(不再上报)
- VLINK_DISCOVER_NATIVE:=1 将 Reporter 组播接口绑定到 127.0.0.1,仅本机可收到
DynamicData — 动态类型数据
头文件:<vlink/extension/dynamic_data.h>
DynamicData 是类型擦除的数据容器,将任意 VLink 兼容消息类型连同类型名标签一起序列化到内部 Bytes 缓冲区,从而可以在不知道编译时类型的通道中传输,之后再反序列化回具体类型。
内部布局
[ 类型名字段 (20 字节固定) | 序列化载荷 ]
^-- kOffset = 20 ^-- 实际消息数据
类型名字段最大 19 个字符(含 NUL 终止符)。
限制
- 不能序列化/反序列化另一个 DynamicData 对象(编译期 static_assert)
- 不能序列化 CDR 类型(编译期 static_assert)
- 类型名字符串长度必须小于 20 个字符(含 NUL)
主要接口
class DynamicData final {
public:
template <uint8_t SizeT, typename T>
DynamicData& load(const char (&type)[SizeT], const T& t);
template <typename T>
bool convert(T& t) const;
template <typename T>
T as() const;
const std::string_view& get_type() const;
const Bytes& get_data() const;
bool is_empty() const;
bool operator<<(const Bytes& bytes) noexcept;
bool operator>>(Bytes& bytes) const noexcept;
};
使用示例
MyProtoMsg msg;
msg.set_value(42);
auto msg = dd.
as<MyProtoMsg>();
}
});
dd.
load(
"SomeType", some_msg);
dd >> wire;
dd2 << wire;
Runtime-typed container that serializes any VLink-compatible message type.
定义 dynamic_data.h:87
const std::string_view & get_type() const
Returns the type name string embedded in the buffer.
T as() const
Deserializes the internal buffer and returns the result by value.
定义 dynamic_data.h:261
DynamicData & load(const char(&type)[SizeT], const T &t)
Serializes t with a type-name tag into the internal buffer.
定义 dynamic_data.h:227
Type-safe publisher for the VLink event communication model.
定义 publisher.h:102
Type-safe subscriber for the VLink event communication model.
定义 subscriber.h:110
Type-erased data container for runtime serialisation and deserialisation.
TerminalStream — 终端流输出
头文件:<vlink/extension/terminal_stream.h>
TerminalStream 是进程级单例的带缓冲 stdout 输出流,专为 CLI 工具设计,绕过 std::cout 和标准 stdio 的开销。
特性
- **缓冲输出**:默认 1 MiB 内部缓冲区,批量 write() 减少系统调用
- **线程安全**:所有操作受 std::mutex 保护
- **无异常**:所有 public 方法标记为 noexcept
- **TTY 检测**:is_tty() 判断 stdout 是否连接到终端,可按需启用 ANSI 颜色
- **跨平台**:Windows 10+ 上启用虚拟终端处理(ANSI 转义码支持)
便捷宏
#define VLINK_TERM_OUT vlink::TerminalStream::get()
使用示例
}
static TerminalStream & get() noexcept
Returns the process-global TerminalStream singleton.
定义 terminal_stream.h:350
void init() noexcept
Initialises the stream: detects the file descriptor and TTY status.
定义 terminal_stream.h:375
static TerminalStream & endl(TerminalStream &stream) noexcept
Stream manipulator that appends a newline and flushes the buffer.
定义 terminal_stream.h:355
static TerminalStream & flush_manip(TerminalStream &stream) noexcept
Stream manipulator that flushes the buffer without appending a newline.
定义 terminal_stream.h:362
Buffered, thread-safe stdout stream with TTY detection and ANSI support.
#define VLINK_TERM_OUT
定义 terminal_stream.h:626
UrlRemap — URL 重映射
头文件:<vlink/extension/url_remap.h>
UrlRemap 从 JSON 配置文件加载 URL 重映射规则,在运行时将 VLink topic 地址动态替换,无需重新编译即可切换传输后端或修改 topic 名称。
JSON 配置格式
{
"intra://sensor/lidar": "dds://vehicle/lidar",
"shm://camera/front": "dds://camera/front",
"dds://old/topic": "ddsc://new/topic"
}
规则为平坦 JSON 对象,key 是原 URL(或其子串),value 是目标 URL。
匹配算法
convert() 依次检查 remap 列表中每条规则,选取第一条**key 是输入 URL 子串**的规则,返回对应 value。查询结果缓存在内部 unordered_map 中,重复查询为 O(1)。
主要接口
class UrlRemap {
public:
bool load(const std::string& file_path) noexcept;
bool unload() noexcept;
bool reload(const
std::
string& file_path) noexcept;
const
std::
string& convert(const
std::
string& url) noexcept;
bool is_valid() const noexcept;
void set_enable_log(bool enable_log) noexcept;
const
std::
string& get_error_string() const noexcept;
};
使用示例
if (!remap.
load(
"/etc/vlink/remap.json")) {
return;
}
std::string url = remap.
convert(
"intra://sensor/lidar");
remap.
reload(
"/etc/vlink/remap_v2.json");
Loads a JSON remap file and translates VLink URL strings at runtime.
定义 url_remap.h:83
const std::string & convert(const std::string &url) noexcept
Translates url according to the loaded remap rules.
const std::string & get_error_string() const noexcept
Returns the human-readable error description from the last failed operation.
定义 url_remap.h:202
void set_enable_log(bool enable_log) noexcept
Enables or disables logging of each URL conversion.
定义 url_remap.h:196
bool reload(const std::string &file_path) noexcept
Unloads the current configuration and loads a new one atomically.
bool load(const std::string &file_path) noexcept
Loads and parses a JSON remap configuration from file_path.
#define VLOG_E(...)
定义 logger.h:854
JSON-driven URL remapping for VLink topic address translation.
注意事项
- UrlRemap **不是线程安全的**,load/unload/reload/convert 应在同一线程调用,或由调用方加锁。
- convert() 在 is_valid() 为 false 时直接返回原 URL,不会崩溃。
- 匹配为**子串匹配**,规则顺序影响结果,JSON 对象顺序由解析器保证(建议使用有序容器)。
自定义插件实现指南
步骤总览
1. 定义接口类(使用 VLINK_PLUGIN_REGISTER)
2. 实现具体类(继承接口,实现所有纯虚函数)
3. 导出入口函数(VLINK_PLUGIN_DECLARE)
4. 编译为共享库(CMake ADD_LIBRARY(... SHARED ...))
5. 宿主加载(Plugin::load<接口>)
完整示例:自定义监控插件
接口头文件 monitor_plugin.h:
#pragma once
#include <string>
class MonitorPluginInterface {
public:
virtual ~MonitorPluginInterface() = default;
virtual bool init(const std::string& config_path) = 0;
virtual void report(const std::string& metric, double value) = 0;
virtual void shutdown() = 0;
};
实现文件 prometheus_monitor.cpp:
#include "monitor_plugin.h"
#include <prometheus/counter.h>
class PrometheusMonitor : public MonitorPluginInterface {
public:
bool init(const std::string& config_path) override {
return true;
}
void report(const std::string& metric, double value) override {
}
void shutdown() override {
}
};
**CMakeLists.txt**:
add_library(prometheus_monitor SHARED prometheus_monitor.cpp)
target_link_libraries(prometheus_monitor
vlink::vlink
prometheus-cpp::core
)
**宿主加载代码**:
auto monitor = plugin.
load<MonitorPluginInterface>(
"prometheus_monitor", 1, 0);
if (!monitor) {
VLOG_E(
"Failed to load monitor plugin.");
return;
}
monitor->init("/etc/monitor.json");
monitor->report("cpu_usage", 0.35);
monitor->shutdown();
插件加载机制详解
共享库搜索路径(default_search_path())
Plugin::default_search_path() 按以下顺序返回搜索路径队列(源码 src/base/plugin.cc:80):
- VLINK_PLUGIN_DIR 环境变量指定的目录(若非空,插入队首)
- 当前工作目录
- 可执行文件所在目录(Utils::get_app_dir())
- 可执行文件所在目录的 ../lib64、../lib、./lib64、./lib
- 系统目录 /lib64、/lib
可通过 Plugin::load() 的 dir_name 参数或 search_paths 参数覆盖默认值。
平台文件名规则
| 平台 | 前缀 | 后缀 | 示例 |
| Linux | lib | .so | libmy_plugin.so |
| macOS | lib | .dylib | libmy_plugin.dylib |
| Windows | 无 | .dll | my_plugin.dll |
插件 ID 验证
每个接口类型通过 VLINK_PLUGIN_REGISTER(InterfaceType) 注入 get_plugin_id() 静态方法,该方法使用 NameDetector::get<T>() 从编译器的 __PRETTY_FUNCTION__ 提取去修饰的类型名。
加载时,Plugin::process_plugin_internal() 将加载方期望的 ID 与共享库内导出的 ID 进行字符串比对,ID 不匹配或版本不兼容时返回 nullptr,防止 ABI 错配导致的崩溃。
版本兼容性规则
VLINK_PLUGIN_DECLARE(ImplType, Major, Minor) 中的版本号必须与 Plugin::load<T>(lib_name, major, minor) 调用方指定的版本一致,否则加载失败并记录错误日志。版本检验在 process_plugin_internal() 内完成,此函数在共享库内部执行,不跨越库边界传递异常。