I am trying to create a plugin system, and my project is anized as follows:-
├── Cargo.lock
├── Cargo.toml
├── op_api
│ ├── Cargo.toml
│ └── src
├── op_dispatch
│ ├── Cargo.toml
│ └── src
├── op_user
│ ├── Cargo.toml
│ └── src
└── target
├── CACHEDIR.TAG
└── debug
Here op_api
and op_user
are libraries and op_dispatch
is a binary.
op_api/lib.rs
contains the trait that the plugin needs to implement.
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
defines two Operators Op1
and Op2
. The goal is to be able to load these operators at runtime.
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));
}
Loading and running logic is in 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);
}
}
Since I want the op_user
library to be shared I have added crate-type = ["cdylib"]
in its Cargo.toml.
I have also added op_api
as a dependency in both op_user
and op_dispatch
using op_api = {path = "../op_api"}
.
The issue is that when I print available operators after registering the operators beforehand, I get empty vector. This is caused by the fact that there are two instances of the REGISTRY
hashmap (one in op_user and other in op_dispatch).
How to do this correctly? If you think the approach itself is wrong, kindly suggest some other approach to build such plugin system.