Skip to main content

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}