dimanche 8 novembre 2020

Can I call a raw pointer with arbitrary arguments in Rust?

I'm trying to combine my Rust program with a library written in C in a more complex scenario.

The library provides this interface:

use std::os::raw::{c_char, c_void};

extern "C" {
    pub fn register_function(
        name: *const c_char, signature: *const c_char,
        func_ptr: *mut c_void, attachment: *mut c_void,
    );
}

The signature can be a string describing the arguments and return type of the function as 32 or 64 bit ints and floats (representations: b'i' = i32, b'I' = i64, b'f' = f32, b'F' = f64). The registered function gets called with an array of u64 (uint64_t) values which correspond to the arguments from the signature.

I would like to abstract this registration and callback process, so that I can switch to another library in the future which provides a similar but different interface. My idea was to to create a proxy function that is registered instead of the actual function. This would then also provide a custom context struct.

My own functions could look like this:

use std::boxed::Box;
use std::pin::Pin;

fn return_void(context: Pin<Box<MyAttachment>>) {
    // ...
}

fn return_32(context: Pin<Box<MyAttachment>>, a: u32, b: u32) -> u32 {
    context.important_stuff();
    // ...
}

// floating point values would be nice, but are optional
fn return_64(a: i32, b: i64, c: f64) -> f64 {
    // ...
}

MyAttachment is supposed to be the context and provide the proxy function that gets an arbitrary number of arguments as array:

use std::cmp;
use std::ffi::CString;
use std::slice;

#[derive(PartialEq)]
enum ReturnType {
    VOID,
    BITS32,
    BITS64,
}

struct MyAttachment {
    real_func_ptr: *mut c_void,
    signature: String,
    argc: u32,
    pass_attachment: bool,
    return_type: ReturnType,
}

impl MyAttachment {
    pub fn important_stuff(&self) {
        // ...
    }

    unsafe extern "C" fn function_proxy(attachment: *mut c_void, argv: *mut u64) {
        // Given: attachment is the pointer to MyAttachment and argv is the array of arguments.

        let this = attachment.cast::<Self>();
        if this.is_null() || argv.is_null() {
            // error handling
            return;
        }
        let this = Pin::new_unchecked(Box::from_raw(this)); // restore
        let args = slice::from_raw_parts_mut(
            argv,
            // There is at least one element in argv if the function is supposed to return a value,
            // because we need to write our result there.
            cmp::max(
                match this.return_type {
                    ReturnType::VOID => 0,
                    ReturnType::BITS32 | ReturnType::BITS64 => 1,
                },
                this.argc as usize,
            ),
        );

        let func_ptr = this.real_func_ptr;
        // I can get the argument types from the signature.

        // TODO cast to correct pointer type. For example:
        // case return_void: Fn(Pin<Box<MyAttachment>>)
        // case return_32: Fn(Pin<Box<MyAttachment>>, u64, u64) -> u32
        // case return_64: Fn(u64, u64, f64) -> f64

        // TODO call it:
        if this.return_type != ReturnType::VOID {
            //args[0] = func_ptr(...args);
            // or
            //args[0] = func_ptr(this, ...args);
        } else {
            //func_ptr(...args);
        }

        Box::into_raw(Pin::into_inner_unchecked(this)); // delay dropping of this
    }
}

fn main() {
    // defining functions like:
    let func1 = Box::pin(MyAttachment {
        real_func_ptr: return_32 as *mut _,
        signature: String::from("(ii)i"),
        argc: 2, // inferred from signature
        pass_attachment: true,
        return_type: ReturnType::BITS32, // inferred from signature
    });
    let name = CString::new("return_32").unwrap();
    let signature = CString::new(func1.signature.as_str()).unwrap();
    // leak the raw pointer
    let func1_ptr = Box::into_raw(unsafe { Pin::into_inner_unchecked(func1) });
    let _func1 = unsafe { Pin::new_unchecked(Box::from_raw(func1_ptr)) }; // just for housekeeping
    unsafe {
        register_function(
            name.as_ptr(),
            signature.as_ptr(),
            MyAttachment::function_proxy as *mut _,
            func1_ptr as *mut _,
        )
    };

    // ...
    // somewhere here is my proxy called from C
    // ...
    // automatic cleanup of MyAttachment structs, because the Boxes are dropped
}

How do I fill these TODOs with code?

I have seen this in C code somewhere by using a generic function pointer and defining a fixed number of calls:

void (*func_ptr)();
if (argc == 0)
    func_ptr();
else if (argc == 1)
    func_ptr(argv[0]);
else if (argc == 2)
    func_ptr(argv[0], argv[1]);
// ... and so on

But is there a solution to do this in Rust? (This only needs to work for x86_64/amd64)

Thanks in advance for reading all this and trying to help.

(I added the reflection tag, because this would be done via reflection if Rust had any)

==== edit I have seen these related questions, but I don't think they apply here:





Aucun commentaire:

Enregistrer un commentaire