#xpc #apple #wrapper #shared-memory #xpc-dictionary

sys xpc-sys

通过包装 xpc_pipe_routine() 的封装来方便地调用例程,并从 Rust 类型到 XPC 对象及其反向转换!

11 个版本

0.5.1 2024 年 4 月 15 日
0.5.0 2023 年 12 月 21 日
0.4.2 2023 年 11 月 17 日
0.4.1 2023 年 8 月 13 日
0.1.0 2021 年 5 月 29 日

#24 in macOS 和 iOS API

Download history 13/week @ 2024-04-19 88/week @ 2024-04-26 1/week @ 2024-06-28 20/week @ 2024-07-05 82/week @ 2024-07-26 8/week @ 2024-08-02

90 每月下载量

MIT 许可证

61KB
1K SLoC

xpc-sys

Rust crates.io

用于方便地在 Rust 中处理 XPC 的各种实用工具。

入门指南

将 Rust/XPC 对象之间进行转换使用在 Apple 开发者上记录的 xpc.h 函数 通过 From 特性。以下将更详细地描述复杂类型,如数组和共享内存对象。

Rust XPC
i64 _xpc_type_int64
u64 _xpc_type_uint64
f64 _xpc_type_double
bool _xpc_bool_true/false
Into _xpc_type_string
HashMap<Into, Into> _xpc_type_dictionary
Vec<Into> _xpc_type_array
std::os::unix::prelude::RawFd _xpc_type_fd
(MachPortType::Send, mach_port_t) _xpc_type_mach_send
(MachPortType::Recv, mach_port_t) _xpc_type_mach_recv
XPCShmem _xpc_type_shmem

使用 From<T> 为任何东西创建 XPC 对象。请确保为文件描述符和 Mach 端口使用正确的类型

let mut message: HashMap<&str, XPCObject> = HashMap::new();

message.insert(
    "domain-port",
    XPCObject::from((MachPortType::Send, unsafe {
        get_bootstrap_port() as mach_port_t
    })),
);

通过 TryXPCValue 特性将 XPC 对象转换为值。它通过 xpc_get_type() 检查你的对象类型,并在使用错误类型时提供清晰的错误

#[test]
fn deserialize_as_wrong_type() {
    let an_i64: XPCObject = XPCObject::from(42 as i64);
    let as_u64: Result<u64, XPCError> = an_i64.xpc_value();
    assert_eq!(
        as_u64.err().unwrap(),
        XPCValueError("Cannot get int64 as uint64".to_string())
    );
}

顶部

对象生命周期

XPCObject 封装了一个 xpc_object_t

pub struct XPCObject(pub xpc_object_t, pub XPCType);

当它被丢弃时,将调用 xpc_release

顶部

QueryBuilder

虽然我们可以从 HashMap<&str, XPCObject> 转换到 XPCObject,但这可能有点冗长。一个 QueryBuilder 特性公开了一些构建方法,使构建 XPC 字典变得更容易(无需所有 into(),并进行了额外的错误检查)。

编写 launchctl list 的查询

    let LIST_SERVICES: XPCDictionary = XPCDictionary::new()
        // "list com.apple.Spotlight" (if specified)
        // .entry("name", "com.apple.Spotlight");
        .entry("subsystem", 3 as u64)
        .entry("handle", 0 as u64)
        .entry("routine", 815 as u64)
        .entry("legacy", true);

    let reply: Result<XPCDictionary, XPCError> = XPCDictionary::new()
        // LIST_SERVICES is a proto 
        .extend(&LIST_SERVICES)
        // Specify the domain type, or fall back on requester domain
        .with_domain_type_or_default(Some(domain_type))
        .entry_if_present("name", name)
        .pipe_routine_with_error_handling();

除了检查 errno 是否为 0,pipe_routine_with_error_handling 还会在响应字典中查找可能的 errorerrors 键,并提供一个包含 xpc_strerror 内容的 Err()

顶部

XPC 字典

使用 XPCObject 类型将 HashMap 转换为 xpc_object_t

let mut message: HashMap<&str, XPCObject> = HashMap::new();
message.insert("type", XPCObject::from(1 as u64));
message.insert("handle", XPCObject::from(0 as u64));
message.insert("subsystem", XPCObject::from(3 as u64));
message.insert("routine", XPCObject::from(815 as u64));
message.insert("legacy", XPCObject::from(true));

let xpc_object: XPCObject = message.into();

调用 xpc_pipe_routine 并接收 Result<XPCObject, XPCError>

let xpc_object: XPCObject = message.into();

match xpc_object.pipe_routine() {
    Ok(xpc_object) => { /* do stuff and things */ },
    Err(XPCError::PipeError(err)) => { /* err is a string w/strerror(errno) */ }
}

响应可能是一个 XPC 字典 -- 返回到 HashMap

let xpc_object: XPCObject = message.into();
let response: Result<XPCDictionary, XPCError> = xpc_object
    .pipe_routine()
    .and_then(|r| r.try_into());

let XPCDictionary(hm) = response.unwrap();
let whatever = hm.get("...");

响应字典可以嵌套,因此 XPCDictionary 包含一个用于此场景的辅助器

let xpc_object: XPCObject = message.into();

// A string: either "Aqua", "StandardIO", "Background", "LoginWindow", "System"
let response: Result<String, XPCError> = xpc_object
    .pipe_routine()
    .and_then(|r: XPCObject| r.try_into());
    .and_then(|d: XPCDictionary| d.get(&["service", "LimitLoadToSessionType"])
    .and_then(|lltst: XPCObject| lltst.xpc_value());

或者,从响应中检索 service 键(一个子 XPC 字典)

let xpc_object: XPCObject = message.into();

// A string: either "Aqua", "StandardIO", "Background", "LoginWindow", "System"
let response: Result<XPCDictionary, XPCError> = xpc_object
    .pipe_routine()
    .and_then(|r: XPCObject| r.try_into());
    .and_then(|d: XPCDictionary| d.get_as_dictionary(&["service"]);

let XPCDictionary(hm) = response.unwrap();
let whatever = hm.get("...");

顶部

XPC 数组

可以从 Vec<XPCObject>Vec<Into<XPCObject>>

let xpc_array = XPCObject::from(vec![XPCObject::from("eins"), XPCObject::from("zwei"), XPCObject::from("polizei")]);

let xpc_array = XPCObject::from(vec!["eins", "zwei", "polizei"]);

使用 xpc_value 返回到 Vec

let rs_vec: Vec<XPCObject> = xpc_array.xpc_value().unwrap();

顶部

XPC Shmem

通过提供大小和 vm_allocate/mmap 标志来创建 XPC 共享内存对象。使用 vm_allocate 创建内存区域,当 XPCShmem 释放时使用 vm_deallocate

let shmem = XPCShmem::new_task_self(
    0x1400000,
    i32::try_from(MAP_SHARED).expect("Must conv flags"),
)?;

// Use as _xpc_type_shmem argument in XPCDictionary
let response = XPCDictionary::new()
    .extend(&DUMPSTATE)
    .entry("shmem", shmem.xpc_object.clone())
    .pipe_routine_with_error_handling()?;

要使用 shmem 区域,请使用 slice_from_raw_parts

let bytes: &[u8] = unsafe {
    &*slice_from_raw_parts(shmem.region as *mut u8, size)
};

// Make a string from bytes in the shmem
let mut hey_look_a_string = String::new();
bytes.read_to_string(buf);

顶部

致谢

非常感谢这些开源项目和一般资源

其他所有内容(C)David Stancu 及贡献者 2021

依赖关系

~0.2–2MB
~42K SLoC