rust-objc
rust-objc copied to clipboard
How to import and use constants
I'm modifying a library that uses objc. I need to import a constant NSString. I tried const* NSString
and Id<NSString>
but rustc warned me that these are not "FFI safe". This, however, appears to work:
// required to bring NSPasteboard into the path of the class-resolver
#[link(name = "AppKit", kind = "framework")]
extern "C" {
// NSString
static NSPasteboardURLReadingFileURLsOnlyKey: &'static Object;
}
Here is the usage:
let ns_dict = class!(NSDictionary);
let ns_number = class!(NSNumber);
let options: Id<NSDictionary<NSObject, NSObject>> = unsafe {
let obj: Id<NSObject> = msg_send![ns_number, numberWithBool: objc::runtime::YES];
msg_send![ns_dict, dictionaryWithObject:obj forKey: NSPasteboardURLReadingFileURLsOnlyKey]
};
Is this correct, safe and idiomatic?
*const NSString
, or even better, &'static NSString
, should work, but doesn't because NSString
is incorrectly defined. Using &'static Object
instead is perfect fine!
For reference, the implementation should be changed to:
#[repr(C)]
pub struct $name {
_private: [u8; 0],
}
Not really sure about the usage code you provided though, depends on what your tolerance for UB is? Id<T>
is not safe to use directly in arguments / return of msg_send!
, you should use &*obj
in the argument and wrap both msg_send!
with Id::from_retained_ptr
.
(Concretely, it would double-free if there was a panic between the two message sends, and may be UB depending on how Rust structs are laid out).
Happy to answer further questions!
Thanks a ton for the swift and thorough response.
Slightly OT, but are there any resources like guidelines, docs or real-world examples either to help use the objc crate correctly, for people who are also Objective C beginners? It appears most people use this crate for Apple APIs FFI, but everyone seems to use it slightly differently. I can speak for myself and several of the Tauri contributors that we'd need to understand this better.
I've been working on a few crates to improve the soundness situation, see objc2
if you're interested. They're not really ready for general audience, but I'm getting pretty close to integrating it into winit
, and will probably submit a bunch of PRs to other crates as well.
In particular for this case, I've come up with a macro msg_send_id!
to help with following Cocoa's memory management rules, which I seriously always forget! (still need to do a little more work, see https://github.com/madsmtm/objc2/pull/120).
Case-in-point, I was wrong before, you should use Id::from_ptr
, not Id::from_retained_ptr
, because numberWithBool:
and dictionaryWithObject:forKey:
return autoreleased objects (concretely, your code would double-free and maybe use-after-free, both before and after my previous suggestion).
Just to be clear, it should be:
let ns_dict = class!(NSDictionary);
let ns_number = class!(NSNumber);
let options: Id<NSDictionary<NSObject, NSObject>> = unsafe {
let obj: Id<NSObject> = Id::from_ptr(msg_send![ns_number, numberWithBool: objc::runtime::YES]);
Id::from_ptr(msg_send![ns_dict, dictionaryWithObject: &*obj forKey: NSPasteboardURLReadingFileURLsOnlyKey])
};
(At least I'm fairly certain...)
I've been working on a few crates to improve the soundness situation, see objc2 if you're interested.
I'm interested if it helps with soundness & readability. And I know more people who are (in Tauri). I think holding off until it's stable to avoid API churn is a good idea though, but at that point feel free to PR the repo linked above if you need testing grounds.
concretely, your code would double-free and maybe use-after-free
Yes, I was able to repro a segfault if I wrapped my FFI calls in an autoreleasepool. I think it was double-free in my case.
Just to be clear, it should be[...]
Thank you. It fixes the spurious segfault. Just goes to show how important it is to have a sound API, or if not possible, good docs - especially around memory management.