首页 > 解决方案 > 我可以在 Rust 中调用带有任意参数的原始指针吗?

问题描述

我正在尝试在更复杂的场景中将我的 Rust 程序与用 C 编写的库结合起来。

该库提供了这个接口:

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,
    );
}

签名可以是一个字符串,将函数的参数和返回类型描述为 32 或 64 位整数和浮点数(表示形式:b'i'= i32b'I'= i64b'f'= f32b'F'= f64)。u64注册的函数被调用一个( ) 值的数组,uint64_t这些值对应于签名中的参数。

我想把这个注册和回调过程抽象出来,以便将来可以切换到另一个提供类似但不同接口的库。我的想法是创建一个注册的代理函数而不是实际函数。这也将提供一个自定义上下文结构。

我自己的函数可能如下所示:

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应该是上下文并提供将任意数量的参数作为数组获取的代理函数:

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
}

如何用代码填充这些 TODO?

通过使用通用函数指针并定义固定数量的调用,我在某处的 C 代码中看到了这一点:

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

但是在 Rust 中有解决方案吗?(这只需要为 x86_64/amd64 工作)

提前感谢您阅读所有这些并尝试提供帮助。

(我添加了反射标签,因为如果 Rust 有的话,这将通过反射来完成)

====编辑 我已经看到了这些相关的问题,但我认为它们不适用于这里:

标签: creflectionrustx86-64

解决方案


推荐阅读