Skip to main content

sh_layer4/plugin_loader/
dylib.rs

1//! Dynamic Library Plugin Loader
2//!
3//! 使用 libloading 实现动态库 (.so/.dylib/.dll) 插件加载。
4
5use super::PluginMeta;
6use crate::types::Layer4Result;
7use anyhow::{anyhow, Context};
8use libloading::{Library, Symbol};
9use parking_lot::RwLock;
10use std::collections::HashMap;
11use std::path::Path;
12use std::sync::Arc;
13
14/// 插件入口函数签名
15/// 插件必须导出 `plugin_create` 函数返回 Plugin 实例
16pub type PluginCreateFn = extern "C" fn() -> *mut ();
17
18/// 插件名称函数签名
19pub type PluginNameFn = extern "C" fn(*const ()) -> *const std::ffi::c_char;
20
21/// 插件版本函数签名
22pub type PluginVersionFn = extern "C" fn(*const ()) -> *const std::ffi::c_char;
23
24/// 插件元数据函数签名
25/// Note: PluginMeta contains String which is not FFI-safe by default,
26/// but we accept this limitation for the plugin ABI design.
27#[allow(improper_ctypes_definitions)]
28pub type PluginMetaFn = extern "C" fn() -> PluginMeta;
29
30/// 插件初始化函数签名
31/// 接收插件句柄和JSON配置字符串,返回0表示成功,非0表示失败
32pub type PluginInitializeFn = extern "C" fn(*mut (), *const std::ffi::c_char) -> i32;
33
34/// 插件销毁函数签名
35pub type PluginDestroyFn = extern "C" fn(*mut ());
36
37/// 动态库插件加载器
38pub struct DylibLoader {
39    /// 已加载的库
40    libraries: RwLock<HashMap<String, Arc<Library>>>,
41    /// 插件句柄
42    handles: RwLock<HashMap<String, *mut ()>>,
43    /// 插件元数据
44    metas: RwLock<HashMap<String, PluginMeta>>,
45}
46
47// SAFETY: DylibLoader 内部使用 RwLock 保护所有共享状态
48unsafe impl Send for DylibLoader {}
49unsafe impl Sync for DylibLoader {}
50
51impl DylibLoader {
52    /// 创建新的加载器
53    pub fn new() -> Self {
54        Self {
55            libraries: RwLock::new(HashMap::new()),
56            handles: RwLock::new(HashMap::new()),
57            metas: RwLock::new(HashMap::new()),
58        }
59    }
60
61    /// 检查文件是否为有效动态库
62    pub fn is_valid_library(path: &Path) -> bool {
63        if !path.exists() || !path.is_file() {
64            return false;
65        }
66
67        path.extension()
68            .and_then(|ext| ext.to_str())
69            .map(|ext| matches!(ext, "so" | "dylib" | "dll"))
70            .unwrap_or(false)
71    }
72
73    /// 加载动态库插件
74    ///
75    /// # Safety
76    ///
77    /// 加载动态库是不安全的,因为:
78    /// 1. 库代码可能在加载时执行任意代码
79    /// 2. 符号可能不匹配预期签名
80    /// 3. 库可能有数据竞争或内存安全问题
81    pub unsafe fn load(&self, path: &Path) -> Layer4Result<(String, PluginMeta)> {
82        let path_str = path.to_string_lossy().to_string();
83
84        // 加载库
85        let library =
86            Library::new(path).with_context(|| format!("Failed to load library: {}", path_str))?;
87
88        // 获取元数据
89        let meta: Symbol<PluginMetaFn> = library
90            .get(b"plugin_meta")
91            .with_context(|| "Symbol 'plugin_meta' not found")?;
92        let meta = meta();
93
94        // 获取创建函数
95        let create: Symbol<PluginCreateFn> = library
96            .get(b"plugin_create")
97            .with_context(|| "Symbol 'plugin_create' not found")?;
98
99        // 创建插件实例
100        let handle = create();
101
102        let name = meta.name.clone();
103
104        // 存储
105        self.libraries
106            .write()
107            .insert(name.clone(), Arc::new(library));
108        self.handles.write().insert(name.clone(), handle);
109        self.metas.write().insert(name.clone(), meta.clone());
110
111        tracing::info!(
112            "Loaded dylib plugin: {} v{} from {}",
113            meta.name,
114            meta.version,
115            path_str
116        );
117
118        Ok((name, meta))
119    }
120
121    /// 安全加载(带验证)
122    pub fn load_safe(&self, path: &Path) -> Layer4Result<(String, PluginMeta)> {
123        if !Self::is_valid_library(path) {
124            return Err(anyhow!("Invalid library file: {:?}", path));
125        }
126
127        // SAFETY: 我们已验证文件扩展名,但仍需信任库代码
128        unsafe { self.load(path) }
129    }
130
131    /// 获取插件名称
132    pub fn get_name(&self, name: &str) -> Option<String> {
133        let meta = self.metas.read().get(name).cloned()?;
134        Some(meta.name)
135    }
136
137    /// 获取插件版本
138    pub fn get_version(&self, name: &str) -> Option<String> {
139        let meta = self.metas.read().get(name).cloned()?;
140        Some(meta.version)
141    }
142
143    /// 获取插件元数据
144    pub fn get_meta(&self, name: &str) -> Option<PluginMeta> {
145        self.metas.read().get(name).cloned()
146    }
147
148    /// 调用插件的初始化函数
149    ///
150    /// 如果插件导出了 `plugin_initialize` 函数,则调用它。
151    /// 返回 Some(true) 表示成功,Some(false) 表示失败,None 表示函数不存在。
152    pub fn call_initialize(&self, name: &str, config_json: &str) -> Option<bool> {
153        let library = self.libraries.read().get(name).cloned()?;
154        let handle = *self.handles.read().get(name)?;
155
156        // SAFETY: 我们持有有效的库和句柄
157        unsafe {
158            let init_fn: Symbol<PluginInitializeFn> = library.get(b"plugin_initialize").ok()?;
159            let config_cstr = std::ffi::CString::new(config_json).ok()?;
160            let result = init_fn(handle, config_cstr.as_ptr());
161            Some(result == 0)
162        }
163    }
164
165    /// 卸载插件
166    pub fn unload(&self, name: &str) -> Layer4Result<()> {
167        // 获取库和句柄
168        let library = self.libraries.write().remove(name);
169        let handle = self.handles.write().remove(name);
170        self.metas.write().remove(name);
171
172        if let (Some(library), Some(handle)) = (library, handle) {
173            // 尝试调用销毁函数
174            if let Ok(lib) = Arc::try_unwrap(library) {
175                // SAFETY: 我们正在销毁插件
176                unsafe {
177                    if let Ok(destroy) = lib.get::<Symbol<PluginDestroyFn>>(b"plugin_destroy") {
178                        destroy(handle);
179                    }
180                }
181            }
182        }
183
184        tracing::info!("Unloaded dylib plugin: {}", name);
185        Ok(())
186    }
187
188    /// 列出已加载的插件
189    pub fn list(&self) -> Vec<String> {
190        self.metas.read().keys().cloned().collect()
191    }
192
193    /// 检查插件是否已加载
194    pub fn is_loaded(&self, name: &str) -> bool {
195        self.metas.read().contains_key(name)
196    }
197
198    /// 获取插件数量
199    pub fn count(&self) -> usize {
200        self.metas.read().len()
201    }
202}
203
204impl Default for DylibLoader {
205    fn default() -> Self {
206        Self::new()
207    }
208}
209
210/// 插件创建宏(供插件开发者使用)
211///
212/// # 示例
213///
214/// ```rust,ignore
215/// use sh_layer4::plugin_loader::{Plugin, PluginContext};
216/// use sh_layer4::declare_plugin;
217///
218/// struct MyPlugin;
219///
220/// #[async_trait::async_trait]
221/// impl Plugin for MyPlugin {
222///     fn name(&self) -> &str { "my_plugin" }
223///     fn version(&self) -> &str { "0.1.0" }
224///     async fn initialize(&self, _ctx: &PluginContext) -> anyhow::Result<()> { Ok(()) }
225///     async fn execute(&self, input: &serde_json::Value) -> anyhow::Result<serde_json::Value> {
226///         Ok(input.clone())
227///     }
228/// }
229///
230/// declare_plugin!(MyPlugin, "my_plugin", "0.1.0");
231/// ```
232#[macro_export]
233macro_rules! declare_plugin {
234    ($plugin_type:ty, $name:expr, $version:expr) => {
235        static mut PLUGIN_INSTANCE: Option<Box<$plugin_type>> = None;
236
237        /// 插件创建
238        #[no_mangle]
239        pub extern "C" fn plugin_create() -> *mut () {
240            unsafe {
241                PLUGIN_INSTANCE = Some(Box::new(<$plugin_type>::default()));
242                PLUGIN_INSTANCE.as_mut().unwrap().as_mut() as *mut () as *mut ()
243            }
244        }
245
246        /// 插件销毁
247        #[no_mangle]
248        pub extern "C" fn plugin_destroy(_ptr: *mut ()) {
249            unsafe {
250                PLUGIN_INSTANCE = None;
251            }
252        }
253
254        /// 插件元数据
255        #[no_mangle]
256        pub extern "C" fn plugin_meta() -> $crate::plugin_loader::PluginMeta {
257            $crate::plugin_loader::PluginMeta {
258                name: $name.to_string(),
259                version: $version.to_string(),
260                ..Default::default()
261            }
262        }
263
264        /// 插件初始化
265        /// 接收JSON配置字符串,返回0表示成功,非0表示失败
266        #[no_mangle]
267        pub extern "C" fn plugin_initialize(
268            _ptr: *mut (),
269            config_json: *const std::ffi::c_char,
270        ) -> i32 {
271            // SAFETY: config_json should be a valid C string from the caller
272            unsafe {
273                if let Some(instance) = PLUGIN_INSTANCE.as_ref() {
274                    // 尝试解析配置
275                    let config_str = if config_json.is_null() {
276                        "{}"
277                    } else {
278                        std::ffi::CStr::from_ptr(config_json)
279                            .to_str()
280                            .unwrap_or("{}")
281                    };
282
283                    // 插件可以在 Default 实现中处理初始化逻辑
284                    // 对于简单的同步初始化,我们在这里执行
285                    tracing::info!("Plugin {} initialized with config: {}", $name, config_str);
286                    0 // 成功
287                } else {
288                    1 // 实例不存在
289                }
290            }
291        }
292    };
293}
294
295#[cfg(test)]
296mod tests {
297    use super::*;
298
299    #[test]
300    fn test_dylib_loader_creation() {
301        let loader = DylibLoader::new();
302        assert!(loader.list().is_empty());
303        assert_eq!(loader.count(), 0);
304    }
305
306    #[test]
307    fn test_is_valid_library() {
308        // 创建临时文件测试扩展名检查
309        let tmp_so = tempfile::NamedTempFile::with_suffix(".so").unwrap();
310        assert!(DylibLoader::is_valid_library(tmp_so.path()));
311
312        let tmp_dll = tempfile::NamedTempFile::with_suffix(".dll").unwrap();
313        assert!(DylibLoader::is_valid_library(tmp_dll.path()));
314
315        let tmp_dylib = tempfile::NamedTempFile::with_suffix(".dylib").unwrap();
316        assert!(DylibLoader::is_valid_library(tmp_dylib.path()));
317
318        let tmp_txt = tempfile::NamedTempFile::with_suffix(".txt").unwrap();
319        assert!(!DylibLoader::is_valid_library(tmp_txt.path()));
320
321        // 不存在的文件
322        assert!(!DylibLoader::is_valid_library(Path::new("/nonexistent.so")));
323    }
324
325    #[test]
326    fn test_plugin_meta_default() {
327        let meta = PluginMeta::default();
328        assert_eq!(meta.name, "unknown");
329        assert_eq!(meta.version, "0.1.0");
330    }
331
332    #[test]
333    fn test_loader_default() {
334        let loader = DylibLoader::default();
335        assert!(loader.list().is_empty());
336    }
337}