1 个不稳定版本
0.1.0 | 2024年5月18日 |
---|
#687 在 开发工具
11KB
Rust 的动态加载插件
目标
在许多软件中,允许其他开发者添加您未考虑到的功能可能是有益的。为此,通常会使用插件式系统。
Rust 是一种强大的语言,它非常注重安全性,然而这可能会使您在处理插件时面临挑战,因为您可能会迅速失去 Rust 提供的许多编译时检查。本库的目标是在运行时加载的插件库(DLLs、DyLibs 或 SOs)编写时重新引入 Rust 建立的基础安全性。
架构
┌───────────┐
Safe │ │ Compile-time
Interface│ Reusable │ Checking
┌──────►│ Plugin │◄─────┬────────┐
│ │ Interface │ │ │
│ │ │ │ │
┌────┴───┐ └───────────┘ ┌────┴─────┐ │
│ │ │ │ │
│ Plugin │◄───────────────►│ Plugin A │ │
│ Host │ Runtime calls │ │ │
│ │◄───────┐ └──────────┘ │
└────────┘ │ │
│ ┌──────────┐ │
│ │ │ │
└───────►│ Plugin B ├──┘
│ │
└──────────┘
插件主机
插件主机是系统的一部分,它将在运行时查找和加载插件,并调用它们的功能。这通常是您的主软件包。
插件客户端
插件客户端(们)是由您或其他开发者编写的插件。这些插件必须与主机提供的插件接口匹配,并允许以安全的方式调用其他代码。
编写插件系统
要编写插件系统,您首先需要决定您的接口。为此示例,我们将使用此接口进行演示
┌───────────────────────────────┐
│ExamplePlugin │
├───────────────────────────────┤
│do_a_thing() │
│say_hello(name: string) -> bool│
└───────────────────────────────┘
这里的 do_a_thing
函数将仅由插件作者决定触发,执行任何操作。 say_hello
函数应向指定的人显示消息,然后返回一个布尔值,表示是否成功。诚然,这不是一个复杂的接口!
编写插件主机
在您的项目中添加 dynamic-plugin
库
cargo add dynamic-plugin --features host
现在在您的 main.rs
文件中,您可以定义您的接口
use dynamic_plugin::{libc::c_char, plugin_interface};
plugin_interface! {
extern struct ExamplePlugin {
/// Ask the plugin to do a thing
fn do_a_thing();
/// Say hello to a person
fn say_hello(to: *const c_char) -> bool;
}
}
请注意,我们不能只是发送字符串!因为这依赖于 FFI,我们需要使用与 C 兼容的数据。如果这样做,Rust 将会警告您!
这就快完成了!我们现在可以编写一些代码来实际使用这些插件
fn main() -> dynamic_plugin::Result<()> {
let plugins = ExamplePlugin::find_plugins("./plugins")?;
for plugin in plugins {
plugin.do_a_thing()?;
let s = std::ffi::CString::new("Jens").unwrap();
plugin.say_hello(s.as_ptr())?;
}
Ok(())
}
编写插件客户端
您现在可以为您的接口编写插件!创建一个新的库项目
cargo new --lib example-plugin
在 Cargo.toml
中,指定这应该构建为与 C 兼容的库
[lib]
crate-type = [ "cdylib" ]
[dependencies]
dynamic-plugin = { version = "x.x.x", features = [ "client" ] }
您现在可以定义您的插件实现
use std::ffi::CStr;
use dynamic_plugin::{libc::c_char, plugin_interface, plugin_impl};
plugin_interface! {
extern struct ExamplePlugin {
/// Ask the plugin to do a thing
fn do_a_thing();
/// Say hello to a person
fn say_hello(to: *const c_char) -> bool;
}
}
plugin_impl! {
ExamplePlugin,
fn do_a_thing() {
println!("A thing has been done!");
}
fn say_hello(name: *const c_char) -> bool {
unsafe {
let name = CStr::from_ptr(name);
println!("Hello, {}!", name.to_string_lossy());
}
true
}
}
插件现在已准备好构建和分发。
进一步探索...
您还可以通过将其放入自己的库中来避免重复使用插件定义。实现此功能的示例可在源存储库的 example-plugin
和 example-plugin-host
文件夹中找到。
尝试计算'0_usize - 1_usize',这将导致溢出
如果您遇到此编译时错误,这表明您正在编写的实现与插件定义的预期实现不匹配。请检查您
- 是否使用了正确的定义。
- 是否拥有满足定义所需的所有函数。
- 所有函数的名称是否正确(与定义相同)。
- 所有函数参数的顺序和类型是否与定义相同。
- 所有函数的返回类型是否与定义相同。
依赖项
~0.4–5.5MB
~19K SLoC