Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

mexPrintf, just like printf, accepts a varargs list of arguments, but I don't know what the best way to wrap this is in Rust. There is a RFC for variadic generics, but what can we do today?

In this example, I want to print of the number of inputs and outputs, but the wrapped function just prints garbage. Any idea how to fix this?

enter image description here

#![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);
}
like image 607
Cameron Taggart Avatar asked Nov 04 '16 22:11

Cameron Taggart


1 Answers

Contrary to popular belief, it is possible to call variadic / vararg functions that were defined in C. That doesn't mean that doing so is very easy, and it's definitely even easier to do something bad because there are even fewer types for the compiler to check your work with.

Here's an example of calling printf. I've hard-coded just about everything:

extern crate libc;

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

fn main() {
    my_thing()
}

Note that I have to very explicitly make sure my format string and arguments are all the right types and the strings are NUL-terminated.

Normally, you'll use tools like 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)
}

The Rust compiler test suite also has an example of calling a variadic function.


A word of warning specifically for printf-like functions: C compiler-writers realized that people screw up this particular type of variadic function call all the time. To help combat that, they've encoded special logic that parses the format string and attempts to check the argument types against the types the format string expect. The Rust compiler will not check your C-style format strings for you!

like image 143
Shepmaster Avatar answered Nov 26 '22 04:11

Shepmaster