clap_clap/
entry.rs

1/// Export `clap_entry` symbols and build a plugin factory.
2///
3/// Use this macro to build a CLAP plugin bundle from types that implement
4/// the [`Plugin`] trait.
5///
6/// [`Plugin`]: crate::plugin::Plugin
7///
8/// # Example
9///
10/// ```no_compile
11/// #[derive(Default)]
12/// struct MyPlugin;
13///
14/// impl Plugin for MyPlugin {
15///     ...
16/// }
17///
18/// #[derive(Default)]
19/// struct MyPluginToo;
20///
21/// impl Plugin for MyPluginToo {
22///     ...
23/// }
24///
25/// clap_clap::entry!(MyPlugin, MyPluginToo);
26/// ```
27///
28/// The crate that invokes the macro should be a dynamic library with C ABI.
29/// Specify the ABI in your crate's `Cargo.toml`:
30///
31/// ```toml
32/// [lib]
33/// crate-type = ["cdylib"]
34/// ```
35#[macro_export]
36macro_rules! entry {
37    ($($plug:ty),*) => {
38        pub mod _clap_entry {
39            use $crate::ffi::{
40                CLAP_PLUGIN_FACTORY_ID, CLAP_VERSION,
41                clap_plugin, clap_plugin_descriptor, clap_plugin_entry,
42                clap_plugin_factory, clap_host
43            };
44            use $crate::factory::{Factory, FactoryHost, FactoryPluginPrototype};
45            use $crate::plugin::Plugin;
46
47            use super::*; // Access the types supplied as macro arguments.
48
49            fn plugin_prototype<P: Plugin>() -> Box<FactoryPluginPrototype<P>> {
50                Box::new(FactoryPluginPrototype::build()
51                            .expect("cannot build factory plugin descriptor"))
52            }
53
54            static FACTORY: std::sync::LazyLock<Factory> =
55                std::sync::LazyLock::new(||
56                    Factory::new(vec![$(plugin_prototype::<$plug>(),)*])
57                );
58
59            /// SAFETY: CLAP requires this method to be thread-safe.
60            /// The LazyLock guarding FACTORY is thread-safe and
61            /// plugins_count() takes a shared reference to Factory.
62            unsafe extern "C-unwind" fn get_plugin_count(_: *const clap_plugin_factory) -> u32 {
63                FACTORY.plugins_count()
64            }
65
66            /// SAFETY: CLAP requires this method to be thread-safe.
67            /// The LazyLock guarding FACTORY is thread-safe and
68            /// descriptor() takes a shared reference to Factory.
69            unsafe extern "C-unwind" fn get_plugin_descriptor(
70                _: *const clap_plugin_factory,
71                index: u32,
72            ) -> *const clap_plugin_descriptor {
73                FACTORY.descriptor(index).unwrap_or(std::ptr::null())
74            }
75
76            /// SAFETY: CLAP requires this method to be thread-safe.
77            /// The LazyLock guarding FACTORY is thread-safe and
78            /// boxed_clap_plugin() takes a shared reference to Factory.
79            unsafe extern "C-unwind" fn create_plugin(
80                _: *const clap_plugin_factory,
81                host: *const clap_host,
82                plugin_id: *const std::ffi::c_char,
83            ) -> *const clap_plugin {
84                if plugin_id.is_null() {
85                    return std::ptr::null();
86                }
87                // SAFETY: We checked if plug_id is non-null.
88                // The host guarantees that this is a valid C string now.
89                let plugin_id = unsafe { std::ffi::CStr::from_ptr(plugin_id) };
90
91                if host.is_null() {
92                    return std::ptr::null();
93                }
94                let host  = unsafe {&*host};
95                if host.get_extension.is_none()
96                    || host.request_restart.is_none()
97                    || host.request_process.is_none()
98                    || host.request_callback.is_none() {
99                    return std::ptr::null();
100                }
101                // SAFETY: We just checked that host and its methods are non-null.
102                let host = unsafe { FactoryHost::new_unchecked(host) };
103
104                FACTORY
105                    .create_plugin(plugin_id, host)
106                    .unwrap_or(std::ptr::null_mut())
107            }
108
109            static CLAP_PLUGIN_FACTORY: clap_plugin_factory = clap_plugin_factory {
110                get_plugin_count: Some(get_plugin_count),
111                get_plugin_descriptor: Some(get_plugin_descriptor),
112                create_plugin: Some(create_plugin),
113            };
114
115            unsafe extern "C-unwind" fn init(plugin_path: *const std::ffi::c_char) -> bool {
116                !plugin_path.is_null()
117            }
118
119            unsafe extern "C-unwind" fn deinit() {}
120
121            /// SAFETY: CLAP requires this method to be thread-safe.
122            unsafe extern "C-unwind" fn get_factory(factory_id: *const std::ffi::c_char) -> *const std::ffi::c_void {
123                if factory_id.is_null() {
124                    return std::ptr::null();
125                }
126                // SAFETY: we cheched if factory_id is non-null.
127                // The host guarantees that this is a valid C string.
128                let id = unsafe { std::ffi::CStr::from_ptr(factory_id) };
129                if id == CLAP_PLUGIN_FACTORY_ID {
130                    &raw const CLAP_PLUGIN_FACTORY as *const _
131                } else { std::ptr::null() }
132            }
133
134            #[allow(non_upper_case_globals)]
135            #[unsafe(no_mangle)]
136            #[used]
137            // Make this symbor public, so that plugin's own tests can access it.
138            pub static clap_entry: clap_plugin_entry = clap_plugin_entry {
139                clap_version: CLAP_VERSION,
140                init: Some(init),
141                deinit: Some(deinit),
142                get_factory: Some(get_factory),
143            };
144        }
145    };
146}