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}