1个不稳定版本
0.1.0 | 2024年3月12日 |
---|
#1795 in 过程宏
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的工作原理。
- Loki 的 loki-mac 和 lokinit 库 - 如果你还没有听说过 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属性实际上只是实现为一个获取函数和一个设置函数。因此,您可以使用相同的函数加载技术来读取属性。