Skip to content

Commit

Permalink
Add documentation on core-foundation interop
Browse files Browse the repository at this point in the history
  • Loading branch information
madsmtm committed May 17, 2024
1 parent 06d3b2a commit eb39164
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 9 deletions.
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 12 additions & 9 deletions crates/objc2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ workspace = true
# NOTE: 'unstable' features are _not_ considered part of the SemVer contract,
# and may be removed in a minor release.
[features]
default = ["std", "apple"]
default = ["std", "apple", "core-foundation/default", "block2/default", "objc2-foundation/default"]

# Currently not possible to turn off, put here for forwards compatibility.
std = ["alloc", "objc2-encode/std", "objc-sys/std"]
alloc = ["objc2-encode/alloc", "objc-sys/alloc"]
std = ["alloc", "objc2-encode/std", "objc-sys/std", "block2/std", "objc2-foundation/std"]
alloc = ["objc2-encode/alloc", "objc-sys/alloc", "block2/alloc", "objc2-foundation/alloc"]

# Enables `objc2::exception::throw` and `objc2::exception::catch`
exception = ["objc-sys/unstable-exception"]
Expand Down Expand Up @@ -92,12 +92,12 @@ unstable-docsrs = []
unstable-apple-new = ["apple"]

# Runtime selection. See `objc-sys` for details.
apple = ["objc-sys/apple"]
gnustep-1-7 = ["objc-sys/gnustep-1-7"]
gnustep-1-8 = ["gnustep-1-7", "objc-sys/gnustep-1-8"]
gnustep-1-9 = ["gnustep-1-8", "objc-sys/gnustep-1-9"]
gnustep-2-0 = ["gnustep-1-9", "objc-sys/gnustep-2-0"]
gnustep-2-1 = ["gnustep-2-0", "objc-sys/gnustep-2-1"]
apple = ["objc-sys/apple", "block2/apple", "objc2-foundation/apple"]
gnustep-1-7 = ["objc-sys/gnustep-1-7", "block2/gnustep-1-7", "objc2-foundation/gnustep-1-7"]
gnustep-1-8 = ["gnustep-1-7", "objc-sys/gnustep-1-8", "block2/gnustep-1-8", "objc2-foundation/gnustep-1-8"]
gnustep-1-9 = ["gnustep-1-8", "objc-sys/gnustep-1-9", "block2/gnustep-1-9", "objc2-foundation/gnustep-1-9"]
gnustep-2-0 = ["gnustep-1-9", "objc-sys/gnustep-2-0", "block2/gnustep-2-0", "objc2-foundation/gnustep-2-0"]
gnustep-2-1 = ["gnustep-2-0", "objc-sys/gnustep-2-1", "block2/gnustep-2-1", "objc2-foundation/gnustep-2-1"]
# Used by `block2`
unstable-compiler-rt = ["apple"]

Expand All @@ -111,6 +111,9 @@ objc2-proc-macros = { path = "../objc2-proc-macros", version = "0.1.1", optional
iai = { version = "0.1", git = "https://github.com/madsmtm/iai", branch = "callgrind" }
static_assertions = "1.1.0"
memoffset = "0.9.0"
core-foundation = { version = "0.9.3", default-features = false }
block2 = { path = "../block2", default-features = false }
objc2-foundation = { path = "../../framework-crates/objc2-foundation", default-features = false, features = ["NSString", "NSObject"] }

[[bench]]
name = "autorelease"
Expand Down
118 changes: 118 additions & 0 deletions crates/objc2/src/topics/core_foundation_interop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Interop with `core-foundation`-like crates

The `objc2` project does not [yet](https://github.com/madsmtm/objc2/issues/556) provide bindings to CoreFoundation and similar frameworks.

To interact with these, you will have to use existing crates like [`core-foundation`], [`core-graphics`], [`security-framework`], [`system-configuration`] and so on.

This can, however, pose a bit of an issue, since `objc2` and [`block2`] impose certain requirements on the types involved.

[`core-foundation`]: https://crates.io/crates/core-foundation
[`core-graphics`]: https://crates.io/crates/core-graphics
[`security-framework`]: https://crates.io/crates/security-framework
[`system-configuration`]: https://crates.io/crates/system-configuration
[`block2`]: https://docs.rs/block2/latest/block2/


## Implementing `Encode` for a newtype wrapper

Due to Rust's orphan rules, `core-foundation` types do not implement `Encode`, and as such cannot be passed to/from methods and in blocks by default.

We're (slowly) working on fixing this, see [servo/core-foundation-rs#628], but in the meantime, you can use a newtype wrapper around the `CF...Ref`, and implement [`Encode`] for that wrapper.

[servo/core-foundation-rs#628]: https://github.com/servo/core-foundation-rs/pull/628
[`Encode`]: crate::encode::Encode


### Example

Declaring an external function to [`CFRunLoopObserverCreateWithHandler`](https://developer.apple.com/documentation/corefoundation/1542816-cfrunloopobservercreatewithhandl?language=objc), which uses blocks and `block2`, and as such require its parameters to implement `Encode`.

```rust
use std::ptr;

use block2::{RcBlock, Block};
use core_foundation::base::{Boolean, CFAllocatorRef, CFIndex, CFOptionFlags, TCFType};
use core_foundation::runloop::{CFRunLoopActivity, CFRunLoopObserver, CFRunLoopObserverRef, kCFRunLoopAllActivities};
use objc2::encode::{Encode, Encoding};

#[repr(transparent)]
struct CFRunLoopObserverRefWrapper(CFRunLoopObserverRef);

// SAFETY: `CFRunLoopObserverRefWrapper` is `#[repr(transparent)]` over
// `CFRunLoopObserverRef`, which is a typedef to `struct __CFRunLoopObserver *`.
unsafe impl Encode for CFRunLoopObserverRefWrapper {
const ENCODING: Encoding = Encoding::Pointer(&Encoding::Struct("__CFRunLoopObserver", &[]));
}

extern "C" {
fn CFRunLoopObserverCreateWithHandler(
allocator: CFAllocatorRef,
activities: CFOptionFlags,
repeats: Boolean,
order: CFIndex,
block: &Block<dyn Fn(CFRunLoopObserverRefWrapper, CFRunLoopActivity)>
) -> CFRunLoopObserverRef;
}

let block = RcBlock::new(|observer: CFRunLoopObserverRefWrapper, activity| {
// Extract `CFRunLoopObserverRef` from `CFRunLoopObserverRefWrapper`
let observer = observer.0;
});

let observer = unsafe {
CFRunLoopObserver::wrap_under_create_rule(CFRunLoopObserverCreateWithHandler(
ptr::null(),
kCFRunLoopAllActivities,
false as Boolean,
0,
&block,
))
};
#
# assert!(CFRunLoopObserverRefWrapper::ENCODING.equivalent_to_str("^{__CFRunLoopObserver=}"));
```


## Toll-free bridging

Certain CoreFoundation types are documented to be ["toll-free bridged"], which means that they're completely interoperable with the Foundation types. To convert between these in Rust, you'll have to cast the pointers, e.g. between `CFStringRef` and `*const NSString`.

["toll-free bridged"]: https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFDesignConcepts/Articles/tollFreeBridgedTypes.html


### Example

Toll-free bridging between `CFString` and `NSString`.

```rust
use core_foundation::base::TCFType;
use core_foundation::string::{CFString, CFStringRef};
use objc2_foundation::{NSString, ns_string};
use objc2::rc::Id;

fn cf_string_to_ns(s: &CFString) -> &NSString {
let ptr: CFStringRef = s.as_concrete_TypeRef();
let ptr: *const NSString = ptr.cast();
// SAFETY: CFString is toll-free bridged with NSString.
unsafe { ptr.as_ref().unwrap() }
}

// Note: `NSString` is currently a bit special, and requires that we convert
// from `Id<NSString>`, as it could otherwise have come from `&NSMutableString`,
// and then we'd loose lifetime information by converting to `CFString`.
//
// This will be changed in the future, see https://github.com/madsmtm/objc2/issues/563.
fn ns_string_to_cf(s: Id<NSString>) -> CFString {
// Yield ownership over the string
let ptr: *const NSString = Id::into_raw(s);
let ptr: CFStringRef = ptr.cast();
// SAFETY: NSString is toll-free bridged with CFString,
// and ownership was passed above with `Id::into_raw`.
unsafe { CFString::wrap_under_create_rule(ptr) }
}

let cf = CFString::new("foo");
let ns = NSString::from_str("foo");
assert_eq!(cf_string_to_ns(&cf), &*ns);
assert_eq!(cf, ns_string_to_cf(ns));
```
3 changes: 3 additions & 0 deletions crates/objc2/src/topics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@
pub mod about_generated;

#[cfg(not(feature = "gnustep-1-7"))]
#[doc = include_str!("core_foundation_interop.md")]
pub mod core_foundation_interop {}
#[doc = include_str!("layered_safety.md")]
pub mod layered_safety {}

0 comments on commit eb39164

Please sign in to comment.