You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust插件系统多Crate共享同一库实例问题及解决咨询

Rust插件系统注册操作符后获取为空的问题

项目结构

├── Cargo.lock
├── Cargo.toml
├── op_api
│   ├── Cargo.toml
│   └── src
├── op_dispatch
│   ├── Cargo.toml
│   └── src
├── op_user
│   ├── Cargo.toml
│   └── src
└── target
    ├── CACHEDIR.TAG
    └── debug

其中op_api和op_user为库,op_dispatch为二进制程序。

核心代码说明

op_api/lib.rs(定义插件Trait与注册表)

use std::collections::HashMap;
use std::sync::Mutex;

pub trait Operator {
    fn run(&self);
}

type OperatorConstructor = fn() -> Box<dyn Operator>;

// Static registry for all available operators in a library
lazy_static::lazy_static! {
    static ref REGISTRY: Mutex<HashMap<&'static str, OperatorConstructor>> = Mutex::new(HashMap::new());
}

// Function to register an operator (used inside the plugin)
pub fn register_operator(name: &'static str, constructor: OperatorConstructor) {
    let mut registry = REGISTRY.lock().unwrap();
    println!("Registry address: {:p}", &*registry);
    registry.insert(name, constructor);
    println!("{:?}", registry.keys());
}

// Function to get the registry (used in main app)
pub fn get_registered_operators() -> Vec<&'static str> {
    let registry = REGISTRY.lock().unwrap();
    println!("Registry address: {:p}", &*registry);
    println!("{:?}", registry.keys());
    registry.keys().cloned().collect()
}

// Function to create an operator instance by name
pub fn create_operator(name: &str) -> Option<Box<dyn Operator>> {
    let registry = REGISTRY.lock().unwrap();
    registry.get(name).map(|constructor| constructor())
}

op_user/lib.rs(定义并注册操作符)

use op_api::{Operator, register_operator};

struct Op1;
impl Operator for Op1 {
    fn run(&self) {
        println!("Running Op1");
    }
}

struct Op2;
impl Operator for Op2 {
    fn run(&self) {
        println!("Running Op2");
    }
}

// Register multiple operators when the library is loaded
#[unsafe(no_mangle)]
pub extern "C" fn register_operators() {
    register_operator("Op1", || Box::new(Op1));
    register_operator("Op2", || Box::new(Op2));
}

op_dispatch/main.rs(加载插件并获取操作符)

fn main() {
    unsafe {
        let lib = Library::new("target/debug/libop_user.so").expect("Failed to load library");

        // Call the register_operators function to populate the registry
        let register: Symbol<fn()> = lib
            .get(b"register_operators")
            .expect("Failed to load register_operators");
        register();

        // Get all registered operators
        let ops = get_registered_operators();
        println!("Available operators: {:?}", ops);
    }
}

当前问题

调用register_operators注册操作符后,get_registered_operators返回空向量。通过打印注册表地址可确认:op_user共享库和op_dispatch二进制程序各自拥有独立的REGISTRY静态实例,插件注册的内容写入了自身实例,主程序读取的是另一个空实例。


解决方案:实现注册表进程内唯一

方法1:将op_api编译为共享库

在op_api/Cargo.toml中添加:

crate-type = ["cdylib", "rlib"]

将op_api编译为共享库后,op_user和op_dispatch会链接同一个动态库实例,静态变量REGISTRY在进程中仅存在一份。

注意:

  • 确保op_user和op_dispatch依赖的op_api均为动态链接版本,避免静态链接导致重复实例。
  • 编译顺序:先构建op_api,再构建op_user和op_dispatch。

方法2:将注册表作为参数传递给插件

彻底移除静态全局变量,让主程序管理注册表并传递给插件:

  1. 修改op_api,将注册表改为可传递的实例:
use std::collections::HashMap;
use std::sync::Mutex;

pub trait Operator {
    fn run(&self);
}

type OperatorConstructor = fn() -> Box<dyn Operator>;
pub type OperatorRegistry = Mutex<HashMap<&'static str, OperatorConstructor>>;

// 注册函数接收外部传入的注册表
pub fn register_operator(registry: &OperatorRegistry, name: &'static str, constructor: OperatorConstructor) {
    let mut registry = registry.lock().unwrap();
    registry.insert(name, constructor);
}
  1. 修改op_user的注册函数,接收注册表参数:
use op_api::{Operator, register_operator, OperatorRegistry};

// Op1和Op2实现不变

#[unsafe(no_mangle)]
pub extern "C" fn register_operators(registry: *mut OperatorRegistry) {
    let registry = unsafe { &*registry };
    register_operator(registry, "Op1", || Box::new(Op1));
    register_operator(registry, "Op2", || Box::new(Op2));
}
  1. 修改op_dispatch主函数,创建并传递注册表:
use op_api::OperatorRegistry;
use std::sync::Mutex;
use std::collections::HashMap;

fn main() {
    let registry = OperatorRegistry::new(HashMap::new());
    
    unsafe {
        let lib = Library::new("target/debug/libop_user.so").expect("Failed to load library");

        let register: Symbol<fn(*mut OperatorRegistry)> = lib
            .get(b"register_operators")
            .expect("Failed to load register_operators");
        register(&registry as *const _ as *mut _);

        // 直接读取主程序管理的注册表
        let ops = registry.lock().unwrap().keys().cloned().collect::<Vec<_>>();
        println!("Available operators: {:?}", ops);
    }
}

这种方式完全规避了静态全局变量的问题,是更灵活的插件设计方案。


替代插件系统方案

  • 基于消息传递的架构:插件与主程序通过IPC(Unix套接字、TCP等)通信,完全隔离进程,避免内存共享问题,但实现复杂度较高。
  • 使用成熟插件库:如libloading配合anyhow简化动态库加载逻辑,这类库已处理好动态链接和类型安全问题。
  • 序列化插件实例:借助dyn-clone和serde将插件实例序列化为字节流,主程序反序列化使用,适合简单场景但性能较低。

内容的提问来源于stack exchange,提问作者Satyam Jay

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 14:25:54