Skip to main content

objc2/ffi/
mod.rs

1//! # Raw bindings to Objective-C runtimes
2//!
3//! These bindings contain almost no documentation, so it is highly
4//! recommended to read Apple's [documentation about the Objective-C
5//! runtime][runtime-guide], Apple's [runtime reference][apple], or to use
6//! the [`runtime`] module which provides a higher-level API.
7//!
8//! [runtime-guide]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html
9//! [apple]: https://developer.apple.com/documentation/objectivec/objective-c_runtime?language=objc
10//! [`runtime`]: crate::runtime
11//!
12//!
13//! ## Runtime Support
14//!
15//! Objective-C has a runtime, different implementations of said runtime
16//! exist, and they act in slightly different ways. By default, Apple
17//! platforms link to Apple's runtime, but if you're using another runtime you
18//! must tell it to this library using feature flags (you might have to
19//! disable the default `apple` feature first).
20//!
21//! One could ask, why even bother supporting other runtimes? For me, the
22//! primary reasoning iss _robustness_. By testing with these alternative
23//! runtimes in CI, we become by extension much more confident that our
24//! implementation doesn't rely on brittle unspecified behaviour, and works
25//! across different macOS and iOS versions.
26//!
27//!
28//! ### Apple's [`objc4`](https://github.com/apple-oss-distributions/objc4)
29//!
30//! - Feature flag: `apple`.
31//!
32//! This is used by default, and has the highest support priority (all of
33//! `objc2` will work with this runtime).
34//!
35//!
36//! ### GNUStep's [`libobjc2`](https://github.com/gnustep/libobjc2)
37//!
38//! - Feature flag: `gnustep-1-7`, `gnustep-1-8`, `gnustep-1-9`, `gnustep-2-0`
39//!   and `gnustep-2-1` depending on the version you're using.
40//!
41//!
42//! ### Microsoft's [`WinObjC`](https://github.com/microsoft/WinObjC)
43//!
44//! - Feature flag: `unstable-winobjc`.
45//!
46//! **Unstable: Hasn't been tested on Windows yet!**
47//!
48//! [A fork](https://github.com/microsoft/libobjc2) based on GNUStep's
49//! `libobjc2` version 1.8, with very few user-facing changes.
50//!
51//!
52//! ### [`ObjFW`](https://github.com/ObjFW/ObjFW)
53//!
54//! - Feature flag: `unstable-objfw`.
55//!
56//! **Unstable: Doesn't work yet!**
57//!
58//! TODO.
59//!
60//!
61//! ### Other runtimes
62//!
63//! This library will probably only ever support ["Modern"][modern]
64//! Objective-C runtimes, since support for reference-counting primitives like
65//! `objc_retain` and `objc_autoreleasePoolPop` is a vital requirement for
66//! most applications.
67//!
68//! This rules out the GCC [`libobjc`][gcc-libobjc] runtime (see
69//! [this][gcc-objc-support]), the [`mulle-objc`] runtime and [cocotron]. (But
70//! support for [`darling`] may be added). More information on different
71//! runtimes can be found in GNUStep's [Objective-C Compiler and Runtime
72//! FAQ][gnustep-faq].
73//!
74//! [modern]: https://en.wikipedia.org/wiki/Objective-C#Modern_Objective-C
75//! [gcc-libobjc]: https://github.com/gcc-mirror/gcc/tree/master/libobjc
76//! [gcc-objc-support]: https://gcc.gnu.org/onlinedocs/gcc/Standards.html#Objective-C-and-Objective-C_002b_002b-Languages
77//! [`mulle-objc`]: https://github.com/mulle-objc/mulle-objc-runtime
78//! [cocotron]: https://cocotron.org/
79//! [`darling`]: https://github.com/darlinghq/darling-objc4
80//! [gnustep-faq]: http://wiki.gnustep.org/index.php/Objective-C_Compiler_and_Runtime_FAQ
81//!
82//!
83//! ## Objective-C Compiler configuration
84//!
85//! Objective-C compilers like `clang` and `gcc` requires configuring the
86//! calling ABI to the runtime you're using:
87//! - `clang` uses the [`-fobjc-runtime`] flag, of which there are a few
88//!   different [options][clang-objc-kinds].
89//! - `gcc` uses the [`-fgnu-runtime` or `-fnext-runtime`][gcc-flags] options.
90//!   Note that Modern Objective-C features are ill supported.
91//!
92//! Furthermore, there are various flags that are expected in modern
93//! Objective-C, that are off by default. In particular you might want to
94//! enable the `-fobjc-exceptions` and `-fobjc-arc` flags.
95//!
96//! Example usage in your `build.rs` (using the `cc` crate) would be as
97//! follows:
98//!
99//! ```ignore
100//! fn main() {
101//!     let mut builder = cc::Build::new();
102//!     builder.compiler("clang");
103//!     builder.file("my_objective_c_script.m");
104//!
105//!     builder.flag("-fobjc-exceptions");
106//!     builder.flag("-fobjc-arc");
107//!     builder.flag("-fobjc-runtime=..."); // If not compiling for Apple
108//!
109//!     builder.compile("libmy_objective_c_script.a");
110//! }
111//! ```
112//!
113//! [`-fobjc-runtime`]: https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-fobjc-runtime
114//! [clang-objc-kinds]: https://clang.llvm.org/doxygen/classclang_1_1ObjCRuntime.html#af19fe070a7073df4ecc666b44137c4e5
115//! [gcc-flags]: https://gcc.gnu.org/onlinedocs/gcc/Objective-C-and-Objective-C_002b_002b-Dialect-Options.html
116//!
117//!
118//! ## Design choices
119//!
120//! It is recognized that the most primary consumer of this module will be
121//! macOS and secondly iOS applications. Therefore it was chosen not to use
122//! `bindgen`[^1] in our build script to not add compilation cost to those
123//! targets.
124//!
125//! Deprecated functions are also not included for future compatibility, since
126//! they could be removed in any macOS release, and then our code would break.
127//! If you have a need for these, please open an issue and we can discuss it!
128//!
129//! Some items (in particular the `objc_msgSend_X` family) have `cfg`s that
130//! prevent their usage on different platforms; these are **semver-stable** in
131//! the sense that they will only get less restrictive, never more.
132//!
133//! [^1]: That said, most of this is created with the help of `bindgen`'s
134//! commandline interface, so huge thanks to them!
135
136#![allow(clippy::upper_case_acronyms)]
137#![allow(non_camel_case_types)]
138#![allow(non_upper_case_globals)]
139#![allow(non_snake_case)]
140#![allow(missing_debug_implementations)]
141#![allow(missing_docs)]
142
143use core::cell::UnsafeCell;
144use core::marker::{PhantomData, PhantomPinned};
145
146macro_rules! generate_linking_tests {
147    {
148        extern $abi:literal {$(
149            $(#[$m:meta])*
150            $v:vis fn $name:ident(
151                $($(#[$a_m:meta])* $a:ident: $t:ty),* $(,)?
152            ) $(-> $r:ty)?;
153        )+}
154        mod $test_name:ident;
155    } => {
156        extern $abi {$(
157            $(#[$m])*
158            $v fn $name($($(#[$a_m])* $a: $t),*) $(-> $r)?;
159        )+}
160
161        #[allow(deprecated)]
162        #[cfg(test)]
163        mod $test_name {
164            #[allow(unused)]
165            use super::*;
166
167            $(
168                $(#[$m])*
169                #[test]
170                fn $name() {
171                    // Get function pointer to make the linker require the
172                    // symbol to be available.
173                    let f: unsafe extern $abi fn($($(#[$a_m])* $t),*) $(-> $r)? = crate::ffi::$name;
174                    // Execute side-effect to ensure it is not optimized away.
175                    std::println!("{:p}", f);
176                }
177            )+
178        }
179    };
180}
181
182macro_rules! extern_c {
183    {
184        $(
185            $(#[$m:meta])*
186            $v:vis fn $name:ident(
187                $($(#[$a_m:meta])* $a:ident: $t:ty),* $(,)?
188            ) $(-> $r:ty)?;
189        )+
190    } => {
191        generate_linking_tests! {
192            extern "C" {$(
193                $(#[$m])*
194                $v fn $name($($(#[$a_m])* $a: $t),*) $(-> $r)?;
195            )+}
196            mod test_linkable;
197        }
198    };
199}
200
201// A lot of places may call `+initialize`, but the runtime guards those calls
202// with `@try/@catch` blocks already, so we don't need to mark every function
203// "C-unwind", only certain ones!
204macro_rules! extern_c_unwind {
205    {
206        $(
207            $(#[$m:meta])*
208            $v:vis fn $name:ident(
209                $($(#[$a_m:meta])* $a:ident: $t:ty),* $(,)?
210            ) $(-> $r:ty)?;
211        )+
212    } => {
213        generate_linking_tests! {
214            extern "C-unwind" {$(
215                $(#[$m])*
216                $v fn $name($($(#[$a_m])* $a: $t),*) $(-> $r)?;
217            )+}
218            mod test_linkable_unwind;
219        }
220    };
221}
222
223mod class;
224mod constants;
225mod exception;
226mod libc;
227mod message;
228mod method;
229mod object;
230mod property;
231mod protocol;
232mod rc;
233mod selector;
234mod types;
235mod various;
236
237pub use self::class::*;
238pub use self::constants::*;
239pub use self::exception::*;
240pub use self::libc::*;
241pub use self::message::*;
242pub use self::method::*;
243pub use self::object::*;
244pub use self::property::*;
245pub use self::protocol::*;
246pub use self::rc::*;
247pub use self::selector::*;
248pub use self::types::*;
249pub use self::various::*;
250
251#[deprecated = "merged with `runtime::AnyClass`"]
252pub type objc_class = crate::runtime::AnyClass;
253
254#[deprecated = "merged with `runtime::AnyObject`"]
255pub type objc_object = crate::runtime::AnyObject;
256
257#[deprecated = "merged with `runtime::Imp`, and made non-null"]
258pub type IMP = Option<crate::runtime::Imp>;
259
260#[deprecated = "merged with `runtime::Imp`"]
261pub type objc_method = crate::runtime::Method;
262
263#[deprecated = "merged with `runtime::Ivar`"]
264pub type objc_ivar = crate::runtime::Ivar;
265
266/// A mutable pointer to an object / instance.
267#[deprecated = "use `AnyObject` directly"]
268pub type id = *mut crate::runtime::AnyObject;
269
270#[deprecated = "use `runtime::Bool`, or if using `msg_send!`, just bool directly"]
271pub type BOOL = crate::runtime::Bool;
272
273#[deprecated = "use `runtime::Bool::YES`"]
274pub const YES: crate::runtime::Bool = crate::runtime::Bool::YES;
275
276#[deprecated = "use `runtime::Bool::NO`"]
277pub const NO: crate::runtime::Bool = crate::runtime::Bool::NO;
278
279/// We don't know much about the actual structs, so better mark them `!Send`,
280/// `!Sync`, `!UnwindSafe`, `!RefUnwindSafe`, `!Unpin` and as mutable behind
281/// shared references.
282///
283/// Downstream libraries can always manually opt in to these types afterwards.
284/// (It's also less of a breaking change on our part if we re-add these).
285///
286/// TODO: Replace this with `extern type` to also mark it as `!Sized`.
287pub(crate) type OpaqueData = UnsafeCell<PhantomData<(*const UnsafeCell<()>, PhantomPinned)>>;
288
289#[cfg(test)]
290mod tests {
291    use super::*;
292    use core::ffi::CStr;
293
294    #[test]
295    fn smoke() {
296        // Verify that this library links and works fine by itself
297        let name = CStr::from_bytes_with_nul(b"abc:def:\0").unwrap();
298        let sel = unsafe { sel_registerName(name.as_ptr()).unwrap() };
299        let rtn = unsafe { CStr::from_ptr(sel_getName(sel)) };
300        assert_eq!(name, rtn);
301    }
302}