pmpx_plugin/export.rs
1//! [`export!`](macro@crate::export) —— 把 [`PackageManager`](crate::PackageManager)
2//! 实现变成一整套 C ABI 外壳。
3
4/// 生成插件的整个 C ABI 外壳。
5///
6/// 用法是**一行**:
7///
8/// ```ignore
9/// impl pmpx_plugin::PackageManager for MyPlugin { /* ... */ }
10///
11/// /// 工厂函数。crate-plugin-kit 生成的 wrapper 工程也会调它。
12/// pub fn create() -> Box<dyn pmpx_plugin::PackageManager> {
13/// Box::new(MyPlugin)
14/// }
15///
16/// pmpx_plugin::export!(create);
17/// ```
18///
19/// # 它生成了什么
20///
21/// | 符号 | 作用 |
22/// | ---- | ---- |
23/// | `pmpx_plugin_entry_v1` | 唯一入口。返回一张 `'static` 的 [`PmpxPluginV1`] 表 |
24/// | `name` / `family` shim | 调你的实现,把 `&str` 泄漏成 [`PmpxStr`] |
25/// | `command` shim | 读输入 → 调 [`crate::PackageManager::command`] → 写输出 |
26/// | `free_str` / `free_command` | 把上面产出的内存还回来 |
27///
28/// # 输入必须是函数的路径
29///
30/// `create` 必须是一个返回 `Box<dyn PackageManager>` 的函数。**不能传类型名**,
31/// 因为你可能想在里面做构造参数注入。
32///
33/// 这个形状不是随意定的:[`crate-plugin-kit`] 生成的 wrapper 工程里,
34/// `src/lib.rs` 的内容正是 `<你的 crate>::create` 的调用 —— 两边必须对得上。
35///
36/// [`crate-plugin-kit`]: https://crates.io/crates/crate-plugin-kit
37/// [`PmpxPluginV1`]: crate::abi::PmpxPluginV1
38/// [`PmpxStr`]: crate::abi::PmpxStr
39///
40/// # 每次调用都新建实例,刻意不缓存
41///
42/// 宿主一个进程只调几次(`name` / `family` 各一次,`command` 一次),
43/// 而缓存需要 `OnceLock` + 静态量 + `Send + Sync` 约束。为几次 `Box::new` 引入这些
44/// 不划算,也让生成的代码更难读。
45#[macro_export]
46macro_rules! export {
47 ($create:path) => {
48 #[doc(hidden)]
49 fn __pmpx_instance() -> ::std::boxed::Box<dyn $crate::PackageManager> {
50 $create()
51 }
52
53 #[doc(hidden)]
54 unsafe extern "C" fn __pmpx_name() -> $crate::abi::PmpxStr {
55 $crate::abi::leak_str(__pmpx_instance().name())
56 }
57
58 #[doc(hidden)]
59 unsafe extern "C" fn __pmpx_family() -> $crate::abi::PmpxStr {
60 $crate::abi::leak_str(__pmpx_instance().family().as_str())
61 }
62
63 #[doc(hidden)]
64 unsafe extern "C" fn __pmpx_command(
65 project_root: $crate::abi::PmpxStr,
66 matched: *const $crate::abi::PmpxStr,
67 matched_len: ::std::primitive::usize,
68 verb: ::std::primitive::u32,
69 args: *const $crate::abi::PmpxStr,
70 args_len: ::std::primitive::usize,
71 out: *mut $crate::abi::PmpxCommand,
72 ) -> ::std::primitive::u32 {
73 // `guard` 在这里不是可选项:从 Rust 1.81 起,让 panic 越过 `extern "C"`
74 // 边界会直接 abort,宿主侧的 catch_unwind 完全救不了。必须由插件自己兜住。
75 $crate::abi::guard(move || {
76 let plugin = __pmpx_instance();
77 // SAFETY: 参数与 out 的有效性由调用方(宿主)按 `PmpxPluginV1::command`
78 // 的约定保证;这里只是把它们转交给同一份约定下的实现。
79 unsafe {
80 $crate::abi::dispatch_command(
81 &*plugin,
82 project_root,
83 matched,
84 matched_len,
85 verb,
86 args,
87 args_len,
88 out,
89 )
90 }
91 })
92 }
93
94 #[doc(hidden)]
95 unsafe extern "C" fn __pmpx_free_str(s: $crate::abi::PmpxStr) {
96 // SAFETY: 这个函数只会被宿主拿 vtable 里的指针调,而 vtable 只由本宏产出,
97 // 所以传回来的必然是本侧 leak_* 的成果。
98 unsafe { $crate::abi::free_str(s) }
99 }
100
101 #[doc(hidden)]
102 unsafe extern "C" fn __pmpx_free_command(c: *mut $crate::abi::PmpxCommand) {
103 // SAFETY: 同上 —— 只有本侧 write_command 填充过的结构体会走到这里。
104 unsafe { $crate::abi::free_command(c) }
105 }
106
107 #[doc(hidden)]
108 static __PMPX_ENTRY: $crate::abi::PmpxPluginV1 = $crate::abi::PmpxPluginV1 {
109 abi_version: $crate::abi::ABI_VERSION,
110 rustc_version: $crate::abi::build_rustc(),
111 target: $crate::abi::build_target(),
112 name: __pmpx_name,
113 family: __pmpx_family,
114 command: __pmpx_command,
115 free_str: __pmpx_free_str,
116 free_command: __pmpx_free_command,
117 };
118
119 /// 插件的唯一入口符号。由 `pmpx_plugin::export!` 生成,**不要手写**。
120 ///
121 /// 用 `#[unsafe(no_mangle)]` 而不是 `#[no_mangle]`:后者在 edition 2024 里是
122 /// 硬错误。`unsafe(...)` 这种写法在 edition 2021 与 2024 下都成立,
123 /// 所以插件用哪个 edition 都能编。
124 #[unsafe(no_mangle)]
125 pub extern "C" fn pmpx_plugin_entry_v1() -> *const $crate::abi::PmpxPluginV1 {
126 &__PMPX_ENTRY
127 }
128 };
129}