1个不稳定版本

0.1.0 2024年3月12日

#1795 in 过程宏


用于 objective-rust

MIT/Apache

45KB
929

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级别的宏得到稳定,你可以在crate的顶部添加#![objective_rust::objrs],然后在crate中的任何地方使用extern "objc"来生成FFI。

示例

  • AppKit示例 - 这个示例在macOS上使用AppKit打开一个窗口。它不处理事件或渲染任何内容,但展示了objective-rust的工作原理。
  • Lokiloki-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 使用 Apple 的 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属性实际上只是实现为一个获取函数和一个设置函数。因此,您可以使用相同的函数加载技术来读取属性。

无运行时依赖