rust-objc icon indicating copy to clipboard operation
rust-objc copied to clipboard

How to import and use constants

Open betamos opened this issue 2 years ago • 5 comments

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?

betamos avatar Jun 12 '22 18:06 betamos

*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!

madsmtm avatar Jun 12 '22 23:06 madsmtm

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.

betamos avatar Jun 13 '22 01:06 betamos

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).

madsmtm avatar Jun 13 '22 15:06 madsmtm

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...)

madsmtm avatar Jun 13 '22 15:06 madsmtm

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.

betamos avatar Jun 13 '22 19:06 betamos