2 个版本
0.1.1 | 2024 年 6 月 7 日 |
---|---|
0.1.0 | 2024 年 3 月 12 日 |
#36 in macOS 和 iOS API
每月 135 次下载
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-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 使用苹果的 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函数。因此,您可以使用相同的函数加载技术来读取属性。