-
Notifications
You must be signed in to change notification settings - Fork 50
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add documentation on core-foundation interop
- Loading branch information
Showing
4 changed files
with
152 additions
and
9 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters