Skip to content

How to import and use constants #114

Open

Description

@betamos

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?

Activity

  1. madsmtm commented on Jun 12, 2022

    @madsmtm

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

  2. betamos commented on Jun 13, 2022

    @betamos
    Author

    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.

  3. madsmtm commented on Jun 13, 2022

    @madsmtm

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

  4. madsmtm commented on Jun 13, 2022

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

  5. betamos commented on Jun 13, 2022

    @betamos
    Author

    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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions