Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to import and use constants #114

Open
betamos opened this issue Jun 12, 2022 · 5 comments
Open

How to import and use constants #114

betamos opened this issue Jun 12, 2022 · 5 comments

Comments

@betamos
Copy link

betamos commented Jun 12, 2022

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?

@madsmtm
Copy link

madsmtm commented Jun 12, 2022

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

@betamos
Copy link
Author

betamos commented Jun 13, 2022

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.

@madsmtm
Copy link

madsmtm commented Jun 13, 2022

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 madsmtm/objc2#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
Copy link

madsmtm commented Jun 13, 2022

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

@betamos
Copy link
Author

betamos commented Jun 13, 2022

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants