Rust调用Object-C的API

使用Tauri开发了一个Mac系统使用的快捷工具,其中有一个功能,需要得到当前处于最前台的软件的名称。

实在没有找到包含此功能的Rust库,自己使用 Applescript 这个苹果系统的脚本实现了这个功能,但执行很慢,需要300毫秒左右,延迟感人。

没办法,必须调用 Mac 系统开发Api了,也就是调用 Mac 原生的 Object-C 的 API。

需要调用的那个Api的文档: developer.apple.com/documentati…

需要下面几步:

  • 先引入 AppKit 这个Mac 的模块
  • 先取 NSWorkspace 这个类
  • 从上面的类中获取 sharedWorkspace 这个对象
  • 调用上面的对象中的 frontmostApplication,得到当前处于前台的那个软件的运行时信息,也就是上面文档中的那个对象。
  • 从软件对象中获取 bundleURL,得到这个软件的安装路径,是一个NSURL对象。
  • 把 NSURL 对象转为 NSString 对象。
  • 把 Objc 的 NSString 返回给 Rust,实际返回的貌似是个「内存地址」,这是我的知识盲区了(这里如果 Objc 返回的是数子、bool值之类的数据类型,貌似Rust可以直接得到值,但复杂的类似 NSString 这种,还需要进一步操作)。
  • 使用 Rust 把读取内存地址,得到 Rust 的 String 数据,到这里算是大功告成了。
  • 执行这段代码,最终只需要话费 4 毫秒左右,我又被深深感动了。

    需要使用cargo安装的依赖:

  • objc:必须
  • percent_encoding(非必须,我用来转码url用的)
  • use std::{os::raw::c_char, ffi::CStr};
    use objc::{class, sel, sel_impl, msg_send, runtime::Object};
    use percent_encoding::percent_decode;
    // 表示引入Mac的AppKit这个模块,因为要使用这个模块下的对象
    #[link(name = "AppKit", kind = "framework")]
    extern "C" {}
    fn main() {
      // unsafe 代码块,表示开发者知道里面的代码不安全
      // 因为要和另一门语言交互,rust必须要确认开发者知道这是不安全的操作
      unsafe {
        // 获取Mac系统的 AppKit 这个模块下的 NSWorkspace 这个对象
        let ns_workspace_class = class!(NSWorkspace);
        // 调用 NSWorkspace 的 sharedWorkspace 方法,拿到 sharedWorkspace
        let shared_workspace: *mut Object = msg_send![ns_workspace_class, sharedWorkspace];
        // 调用 sharedWorkspace 的 frontmostApplication 方法,拿到 frontmostApplication
        // 也就是当前处于前台的 app 对象
        let app: *mut Object = msg_send![shared_workspace, frontmostApplication];
        // 这里可以调用 app 上面的 hide 方法,把这个软件隐藏,相当于系统快捷键 command + h
        // let _result: *mut Object = msg_send![app, hide];
        // 拿到 app 的软件放置的磁盘位置的 NSURL 对象
        let bundle_url: *mut Object = msg_send![app, bundleURL];
        // 把 NSURL 转为 NSString,因为 NSURL 继承于 NSObject 这个几类,所以可以使用 description 这个方法
        let description_string: *mut Object = msg_send![bundle_url, description];
        // 后续操作,是把 object-c 中的 NSString 转为 Rust 的 String 字符串,以便能在 rust 使用
        let description: *mut c_char = msg_send![description_string, UTF8String];
        let c_str: &CStr = CStr::from_ptr(description);
        let str_slice = c_str.to_string_lossy().into_owned();
        // str_slice 就是结果,但值是url的格式,里面的空格、特殊符号等是 url 格式:
        // file:///Applications/Visual%20Studio%20Code.app/
        // 下面把 url 格式的字符串解码
        let iter = percent_decode(str_slice.as_bytes());
        let decoded = iter.decode_utf8().unwrap();
        let decoded = decoded.to_string();
        println!("{}", decoded);
        // 打印结果:
        // file:///Applications/Visual Studio Code.app/
    复制代码
  • 私信