2 个版本

0.1.1 2024 年 6 月 7 日
0.1.0 2024 年 3 月 12 日

#36 in macOS 和 iOS API

Download history 2/week @ 2024-05-19 147/week @ 2024-06-02 11/week @ 2024-06-09 2/week @ 2024-06-16 13/week @ 2024-07-07 1/week @ 2024-07-14

每月 135 次下载

MIT/Apache

17KB
165 行代码(不含注释)

objective-rust

objective-rust 是一个无依赖的、快速高效的 Objective-Cringe FFI 库,用于 Rust。与其它 Objective-C 库不同,objective-rust 允许您将 Objective-C 类作为常规 Rust 类型使用;它不会试图将奇怪的 Objective-C 语法引入 Rust。

以下是一个简单的示例程序,它导入并使用 AppKit 中的 NSApplication

// The `objrs` attribute macro, which generates FFI for you
use objective_rust::objrs;
use std::ptr::NonNull;

// Declare Objective-C types with the `#[objrs]` macro and an
// `extern "objc"` block
#[objrs]
extern "objc" {
    // The class to import
    type NSApplication;

    // Methods for instances of the class (takes &self or &mut self)
    fn run(&self);
    // Static methods for the class itself (doesn't take self)
    fn sharedApplication() -> *mut Self;

    // You can also change which method objective-rust will
    // call internally, with the selector attribute
    #[selector = "sharedApplication"]
    fn shared() -> *mut Self;
}

fn main() {
    // Call class methods just like associated functions in Rust
    let shared = NSApplication::shared();
    // `from_raw` is added by objective-rust, and just converts a pointer
    // to an instance into a useable Rust type
    // It requires a non-null pointer
    let shared = NonNull::new(shared).unwrap();
    let shared = unsafe { NSApplication::from_raw(shared) };
    // Call instance methods just like methods in Rust
    shared.run();
}

// Without this, Rust won't link to AppKit and AppKit classes won't get loaded.
// This doesn't import anything from AppKit. It just links to it so we can use
// its classes.
#[link(name = "AppKit", kind = "framework")]
extern "C" {}

从类型和方法声明的方式(在 extern 块中)到使用方式(关联函数和方法),再到行为(当实例被丢弃时自动调用 release),一切都是为了使它们感觉像是原生 Rust。唯一的真正区别是需要从原始指针构造一个实例。

顺便说一下,objrs 宏也可以用于整个模块

#[objrs]
mod ffi {
    // All `extern "objc"` blocks in the module will get parsed.
    extern "objc" {
        type NSApplication;

        #[selector = "sharedApplication"]
        fn shared() -> *mut Self;
        fn run(&self);
    }

    // This isn't in an `extern "objc"` block, so it is ignored/not processed
    pub struct SomeType {}
}

use ffi::{NSApplication, SomeType};

将来,如果 crate 级别的宏得到稳定,您可以将 #![objective_rust::objrs] 添加到 crate 的顶部,然后在 crate 中任何地方使用 extern "objc" 来生成 FFI。

示例

  • AppKit 示例 - 这个示例在 macOS 上使用 AppKit 打开一个窗口。它不处理事件或渲染任何内容,但展示了 objective-rust 的工作原理。
  • Loki 的 loki-maclokinit 库 - 如果您还没有听说过 Lokinit,它是一个正在开发中的、几乎没有依赖的窗口库。与其它窗口库不同,Lokinit 将您的应用程序置于事件循环的控制之下,而不是让操作系统控制线程。objective-rust 是为 Lokinit 的 macOS 后端而设计的。

限制

  • objective-rust 不支持借用;应使用指针。我还没有确定跨 FFI 的借用如何影响安全性保证,因此只支持指针,并且不提供安全性保证。
  • objective-rust 目前只能导入现有的 Objective-C 类。未来,我希望支持将 Rust 结构体导出为 Objective-C 类,但这尚未添加。
  • 协议目前还不能导入,但未来我希望支持将它们作为特性行为导入。

内部细节 / 工作原理

注意:如果您想看到这个功能在实际中的应用,请运行 cargo install cargo-expand,然后在任何 objective-rust 项目上运行 cargo expand。它将显示宏的输出。

概览

objective-rust 使用苹果的 Objective-C 运行时 API 与 Objective-C 类进行交互。与其他 Objective-C crate 或甚至 Objective-C 本身不同,它不依赖于消息传递;相反,objective-rust 使用 API 获取 Objective-C 方法的底层 C 函数并直接调用该函数。

objective-rust 将使用线程局部存储来存储通过 objrs 宏导入的任何 Objective-C 方法的指针。当您调用一个方法时,它会从线程局部存储中加载该函数指针,并用适当的参数调用该函数。

细节

当您在一个 extern "objc" 块中声明一个类型时,objective-rust 将为它生成这三个结构体(其中代表类名)

  • <class>:与类名相同的结构体。这实现了所有的方法,是您在程序中使用的类型。它是 Objective-C 类的 "Rust 包装类型"。
  • <class>Instance:表示您导入的类的 Objective-C 实例的不可见类型。这仅仅是为了在语义上区分 Objective-C 类型与 Rust 包装类型;它没有任何方法或其他功能。
  • <class>VTable:objective-rust 用来存储类的方法的所有函数指针的结构体。

当您在一个 extern "objc" 块中声明一个函数时,objective-rust 将为该函数向 <class>VTable 结构体添加一个字段。该字段存储该函数的选择器以及该函数本身的指针。objective-rust 然后将 <class>VTable 的实例存储在线程局部存储中。

当您在 <class> 中调用一个方法时,objective-rust 从线程局部存储中的 <class>VTable 实例获取该函数的函数指针和选择器,并使用您提供的所有参数调用该函数。

其他说明

可能对任何使用 Objective-C 运行时的人都有帮助的东西

  • 所有Objective-C方法在底层都是作为C函数实现的。这些函数都具有以下签名:extern "C" fn(instance: *mut Self, selector: Selector, <function arguments>) 简而言之,第一个参数总是运行此方法的实例(即 self 指针),第二个参数是函数的选择器,之后的所有内容都是函数的实际参数(如果有的话)。
  • 您可以使用 class_getMethodImplementation 函数获取Objective-C方法的底层C函数。
  • 上面描述的C函数签名也适用于类/静态方法。对于这些方法,实例是 类本身,而不是类实例。此外,函数是为类的元类实现的,而不是类。因此,要使用 class_getMethodImplementation 加载函数,您需要传递 class 参数的元类。您可以使用 objc_getMetaClass 获取元类。
  • Objective-C属性实际上只是实现了getter函数和setter函数。因此,您可以使用相同的函数加载技术来读取属性。

依赖项