如何包装对在 Rust 中使用 VarArgs 的 FFI 函数的调用?

How to wrap a call to a FFI function that uses VarArgs in Rust?

mexPrintf,就像 printf 一样,接受参数的可变参数列表,但我不知道用 Rust 包装它的最佳方法是什么。有一个RFC for variadic generics,但是我们今天能做什么呢?

在这个例子中,我想打印输入和输出的数量,但包装函数只是打印垃圾。知道如何解决这个问题吗?

#![allow(non_snake_case)]
#![allow(unused_variables)]

extern crate mex_sys;

use mex_sys::mxArray;
use std::ffi::CString;
use std::os::raw::c_int;
use std::os::raw::c_void;

type VarArgs = *mut c_void;

// attempt to wrap mex_sys::mexPrintf
fn mexPrintf(fmt: &str, args: VarArgs) {
    let cs = CString::new(fmt).unwrap();
    unsafe {
        mex_sys::mexPrintf(cs.as_ptr(), args);
    }
}

#[no_mangle]
pub extern "system" fn mexFunction(
    nlhs: c_int,
    plhs: *mut *mut mxArray,
    nrhs: c_int,
    prhs: *mut *mut mxArray,
) {
    let hw = CString::new("hello world\n").unwrap();
    unsafe {
        mex_sys::mexPrintf(hw.as_ptr());
    }

    let inout = CString::new("%d inputs and %d outputs\n").unwrap();
    unsafe {
        mex_sys::mexPrintf(inout.as_ptr(), nrhs, nlhs);
    }

    mexPrintf("hello world wrapped\n", std::ptr::null_mut());

    let n = Box::new(nrhs);
    let p = Box::into_raw(n);
    mexPrintf("inputs %d\n", p as VarArgs);

    let mut v = vec![3];
    mexPrintf("vec %d\n", v.as_mut_ptr() as VarArgs);
}

与流行的看法相反,可以调用 在 C 中定义的可变参数/可变参数函数。这并不意味着这样做很容易,而且绝对是更容易做坏事,因为可供编译器检查您的工作的类型更少。

这里有一个调用printf的例子。我对几乎所有内容都进行了硬编码:

extern crate libc;

fn my_thing() {
    unsafe {
        libc::printf(b"Hello, %s (%d)[=10=]".as_ptr() as *const i8, b"world[=10=]".as_ptr(), 42i32);
    }
}

fn main() {
    my_thing()
}

请注意,我必须非常明确地确保我的格式字符串和参数都是正确的类型,并且字符串以 NUL 结尾。

通常,您会使用 CString:

这样的工具
extern crate libc;

use std::ffi::CString;

fn my_thing(name: &str, number: i32) {
    let fmt = CString::new("Hello, %s (%d)").expect("Invalid format string");
    let name = CString::new(name).expect("Invalid name");

    unsafe {
        libc::printf(fmt.as_ptr(), name.as_ptr(), number);
    }
}

fn main() {
    my_thing("world", 42)
}

Rust 编译器测试套件还有 an example of calling a variadic function.


专门针对 printf 类函数的警告:C 编译器编写者意识到人们总是搞砸了这种特殊类型的可变参数函数调用。为了帮助解决这个问题,他们编码了特殊的逻辑来解析格式字符串并尝试根据格式字符串期望的类型检查参数类型。 Rust 编译器不会为您检查 C 风格的格式字符串!

我把 "variable list of arguments" 和 va_list 混淆了。如果可以的话,我将避免这两种情况,在这种情况下,我将在 Rust 中进行字符串格式化,然后再将其传递给互操作。在这种情况下,以下是对我有用的方法:

#![allow(non_snake_case)]
#![allow(unused_variables)]

extern crate mex_sys;

use mex_sys::mxArray;
use std::ffi::CString;
use std::os::raw::c_int;

// attempt to wrap mex_sys::mexPrintf
fn mexPrintf(text: &str) {
    let cs = CString::new(text).expect("Invalid text");
    unsafe {
        mex_sys::mexPrintf(cs.as_ptr());
    }
}

#[no_mangle]
pub extern "C" fn mexFunction(
    nlhs: c_int,
    plhs: *mut *mut mxArray,
    nrhs: c_int,
    prhs: *mut *mut mxArray,
) {
    mexPrintf(&format!("{} inputs and {} outputs\n", nrhs, nlhs));
}