如何从 Swift FFI 传递参数到函数?

How to pass out parameter to function from Swift FFI?

假设我有一个用 Rust 定义的函数,它看起来像这样:

#[no_mangle]
pub unsafe extern "C" fn do_something(
    my_value: *mut MyStruct,
    some_param: c_uint,
    content: *mut *mut u8,
    length: *mut c_uint,
    capacity: *mut c_uint,
) -> *mut MyStruct {
    // Do something and obtain an `ffi_result`...

    let heap_data = ffi_result.as_mut_ptr();

    // These values are passed as "out" parameters.
    *length = ffi_result.len() as c_uint;
    *capacity = ffi_result.capacity() as c_uint;
    *content = heap_data;

    // We intentionally "leak" this data to the heap.
    // The caller is responsible for cleaning it up by calling another function.
    std::mem::forget(ffi_result);

    std::boxed::Box::into_raw(value_of_type_my_struct)
}

它接受一个指向结构的指针、一个简单的整数参数、几个 out 稍后可用于创建数组的参数,它 returns 一个指向结构的指针。

现在我将 rust 库编译成目标 aarch64-apple-ios 的静态库。我设置了一个 XCode 项目,按照 here 的解释将静态库添加为依赖项,并在其中导入以下头文件

“Objective-C Bridging Header”
#ifndef libmy_project_h
#define libmy_project_h

#include <stdint.h>

struct myStruct;

struct myStruct *do_something(struct myStruct *state, int someParam, char **content, int *length, int *capacity);

#endif

到目前为止,一切似乎都运行良好,我已经成功地将此过程用于一大堆其他功能。但是在这种特殊情况下,我不知道如何从 swift 调用这个函数。我需要从 swift 调用该函数并将 contentlengthcapacity 作为 out 参数 传递,以便我以后可以使用在 Swift 中创建数组的指针,例如 .

这是我到目前为止尝试过的:

var content = UnsafeMutablePointer<UnsafeMutablePointer<CChar>?>(UnsafeMutablePointer(bitPattern: 0))
var length = UnsafeMutablePointer<Int32>(bitPattern: 0)
var capacity = UnsafeMutablePointer<Int32>(bitPattern: 0)
let my_struct = do_something(my_struct, Int32(some_param), content, length, capacity)
        
let buffer = UnsafeRawBufferPointer(start: content?.pointee, count: Int(length!.pointee))
var data = Array(repeating: UInt8(0), count: Int(length!.pointee))
data.withUnsafeMutableBytes { arrayPtr in
    arrayPtr.copyBytes(from: buffer)
}

但是现在当我执行这个 swift 片段时,我得到一个 EXC_BAD_ACCESS 错误,我认为这是因为我手动创建的指针不属于 space 的地址我的应用程序。如何创建可用作 out 参数的指针?

P.S。这里的参考是 C# 中的相同互操作代码:

[DllImport("my_dll")]
        private static extern IntPtr do_something(IntPtr state, uint someParam, out IntPtr content, out uint length, out uint capacity);

可以这样调用:

IntPtr contentPointer;
uint length, capacity;
IntPtr my_struct = do_something(state, myParam, out contentPointer, out length, out capacity);

byte[] rawContent = new byte[length];
Marshal.Copy(contentPointer, rawContent, 0, (int)length);

// Free the data owned by rust with another FFI call:
free_do_something_result(contentPointer, length, capacity);
var length = UnsafeMutablePointer<Int32>(bitPattern: 0)

您需要为您的out-parameters传递存储。这是定义一个空指针。当 Rust 尝试将结果写入地址 0 时,它会崩溃,因为您无权在那里写入。

不是创建两层指针,而是创建一个你想要的类型的值,然后传递那个值的地址(&);这将自动添加额外的指针层。

// Create storage
var content: UnsafeMutablePointer<CChar>? // Could be NULL, so Optional
var length: Int32 = 0
var capacity: Int32 = 0

// Pass as references
do_something(&content, &length, &capacity)

// Copy the data
let data = Array(UnsafeRawBufferPointer(start: content, count: Int(length)))

content这里还是一个指针,因为更新的东西是一个指针。你不是在为内容提供存储,Rust 是。但是你需要为指针提供存储(这就是它所做的)。

我无法编译你的代码,因为它遗漏了很多(MCVE 在这里会好得多),所以我无法测试这是否完全符合你的意思,但它应该是关闭。

在您的示例中,您正在泄漏内存,但由于您的 C# 调用 free_do_something_result(我假设它会清理它),我假设您实际上在 Swift.