Rust FFI 传递 trait 对象作为调用回调的上下文
Rust FFI passing trait object as context to call callbacks on
好的,我正在努力实现以下目标:
- C 调用 rust
- rust 回调到 c 并在用户定义的特征对象上注册回调
- c 使用上下文调用 rust
- rust 在上下文(特征对象)上调用回调
我一直在研究它。我已经走得很远了,但还不够。
C位:
#include <dlfcn.h>
#include <stdio.h>
void *global_ctx;
void c_function(void* ctx) {
printf("Called c_function\n");
global_ctx = ctx;
}
int main(void) {
void *thing = dlopen("thing/target/debug/libthing.dylib", RTLD_NOW | RTLD_GLOBAL);
if (!thing) {
printf("error: %s\n", dlerror());
return 1;
}
void (*rust_function)(void) = dlsym(thing, "rust_function");
void (*rust_cb)(void*) = dlsym(thing, "rust_cb");
printf("rust_function = %p\n", rust_function);
rust_function();
rust_cb(global_ctx);
}
锈位:
extern crate libc;
pub trait Foo {
fn callback(&self);
}
extern {
fn c_function(context: *mut libc::c_void);
}
pub struct MyFoo;
impl Foo for MyFoo {
fn callback(&self) {
println!("callback on trait");
}
}
#[no_mangle]
pub extern fn rust_cb(context: *mut Foo) {
unsafe {
let cb:Box<Foo> = Box::from_raw(context);
cb.callback();
}
}
#[no_mangle]
pub extern fn rust_function() {
println!("Called rust_function");
let tmp = Box::new(MyFoo);
unsafe {
c_function(Box::into_raw(tmp) as *const Foo as *mut libc::c_void);
}
}
问题:
- 当我尝试在 "rust_cb"
中的特征对象上调用 "callback" 时,我的程序出现段错误
一个解决方案:
- 将 "rust_cb" 的函数签名更改为
pub extern fn rust_cb(context: *mut MyFoo)
但这不是我想要的,因为我正在尝试创建一个只知道侦听器特征的安全包装器
感谢任何帮助
PS:我的假设是段错误,因为编译器不知道特征 Foo 上回调的偏移量,它需要实际对象来确定它在哪里。但后来我不知道如何解决这个问题
Rust trait 对象如 Box<Foo>
是普通指针大小的两倍,所以你不能用 void *
来表示它们。有关详细信息,请参阅 std::raw::TraitObject
。这是您的代码的工作版本:
program.c:
#include <dlfcn.h>
#include <stdio.h>
struct rs_trait_obj {
void *data;
void *vtable;
};
struct rs_trait_obj global_ctx;
void c_function(struct rs_trait_obj ctx) {
printf("Called c_function\n");
global_ctx = ctx;
}
int main(void) {
void *thing = dlopen("thing/target/debug/libthing.dylib", RTLD_NOW | RTLD_GLOBAL);
if (!thing) {
printf("error: %s\n", dlerror());
return 1;
}
void (*rust_function)(void) = dlsym(thing, "rust_function");
void (*rust_cb)(struct rs_trait_obj) = dlsym(thing, "rust_cb");
printf("rust_function = %p\n", rust_function);
rust_function();
rust_cb(global_ctx);
}
lib.rs:
#![feature(raw)]
extern crate libc;
use std::raw::TraitObject;
use std::mem;
pub trait Foo {
fn callback(&self);
}
extern {
fn c_function(context: TraitObject);
}
pub struct MyFoo;
impl Foo for MyFoo {
fn callback(&self) {
println!("callback on trait");
}
}
#[no_mangle]
pub extern fn rust_cb(context: TraitObject) {
unsafe {
let cb: Box<Foo> = mem::transmute(context);
cb.callback();
}
}
#[no_mangle]
pub extern fn rust_function() {
println!("Called rust_function");
let tmp: Box<Foo> = Box::new(MyFoo);
unsafe {
c_function(mem::transmute(tmp));
}
}
这仅适用于 nightly rustc(因为 #![feature(raw)]
)并且还会发出警告,因为 TraitObject
不是 FFI 安全的。如果你想要稳定运行的东西,你可以像这样定义一些适当大小的结构并使用它而不是 TraitObject
:
#[repr(C)]
struct FFITraitObject {
data: usize,
vtable: usize,
}
当然,另一种选择是使用 Box<Foo>
代替 TraitObject
,但您仍然会收到警告:
extern crate libc;
pub trait Foo {
fn callback(&self);
}
extern {
fn c_function(context: Box<Foo>);
}
pub struct MyFoo;
impl Foo for MyFoo {
fn callback(&self) {
println!("callback on trait");
}
}
#[no_mangle]
pub extern fn rust_cb(context: Box<Foo>) {
context.callback();
}
#[no_mangle]
pub extern fn rust_function() {
println!("Called rust_function");
let tmp: Box<Foo> = Box::new(MyFoo);
unsafe {
c_function(tmp);
}
}
如果你真的想使用 void *
,你可以考虑泄漏 TraitObject
以及 MyFoo
并使用两个间接级别。
因此,如果您需要将 Foo
表示为 void *
,您可以使用:
extern crate libc;
pub trait Foo {
fn callback(&self);
}
extern {
fn c_function(context: *mut libc::c_void);
}
pub struct MyFoo;
impl Foo for MyFoo {
fn callback(&self) {
println!("callback on trait");
}
}
#[no_mangle]
pub extern fn rust_cb(context: *mut Box<Foo>) {
unsafe {
let cb: Box<Box<Foo>> = Box::from_raw(context);
cb.callback();
}
}
#[no_mangle]
pub extern fn rust_function() {
println!("Called rust_function");
let tmp: Box<Box<Foo>> = Box::new(Box::new(MyFoo));
unsafe {
c_function(Box::into_raw(tmp) as *mut Box<Foo> as *mut libc::c_void);
}
}
我认为您可能误解了什么是特征对象。特征对象是一种大小为两个指针的类型(因此,在 64 位系统上为 128 位)。在此示例中,Foo
不是特征对象,它是动态大小的类型(即具有可变大小的类型,例如 str
)。 Box<Foo>
是一个特征对象。 Box<Box<Foo>>
既不是特征对象也不是动态大小的类型,它是一种与指针大小相同的类型,这就是我们需要在这里使用它的原因,因为我们想将它转换为 void *
.
我称它为 "leaking" 因为当你调用 Box::into_raw
时,你正在泄漏盒子里的任何东西的内存,这意味着你有责任确保析构函数(Drop
实现)被调用。
好的,我正在努力实现以下目标:
- C 调用 rust
- rust 回调到 c 并在用户定义的特征对象上注册回调
- c 使用上下文调用 rust
- rust 在上下文(特征对象)上调用回调
我一直在研究它。我已经走得很远了,但还不够。
C位:
#include <dlfcn.h>
#include <stdio.h>
void *global_ctx;
void c_function(void* ctx) {
printf("Called c_function\n");
global_ctx = ctx;
}
int main(void) {
void *thing = dlopen("thing/target/debug/libthing.dylib", RTLD_NOW | RTLD_GLOBAL);
if (!thing) {
printf("error: %s\n", dlerror());
return 1;
}
void (*rust_function)(void) = dlsym(thing, "rust_function");
void (*rust_cb)(void*) = dlsym(thing, "rust_cb");
printf("rust_function = %p\n", rust_function);
rust_function();
rust_cb(global_ctx);
}
锈位:
extern crate libc;
pub trait Foo {
fn callback(&self);
}
extern {
fn c_function(context: *mut libc::c_void);
}
pub struct MyFoo;
impl Foo for MyFoo {
fn callback(&self) {
println!("callback on trait");
}
}
#[no_mangle]
pub extern fn rust_cb(context: *mut Foo) {
unsafe {
let cb:Box<Foo> = Box::from_raw(context);
cb.callback();
}
}
#[no_mangle]
pub extern fn rust_function() {
println!("Called rust_function");
let tmp = Box::new(MyFoo);
unsafe {
c_function(Box::into_raw(tmp) as *const Foo as *mut libc::c_void);
}
}
问题:
- 当我尝试在 "rust_cb" 中的特征对象上调用 "callback" 时,我的程序出现段错误
一个解决方案: - 将 "rust_cb" 的函数签名更改为
pub extern fn rust_cb(context: *mut MyFoo)
但这不是我想要的,因为我正在尝试创建一个只知道侦听器特征的安全包装器
感谢任何帮助
PS:我的假设是段错误,因为编译器不知道特征 Foo 上回调的偏移量,它需要实际对象来确定它在哪里。但后来我不知道如何解决这个问题
Rust trait 对象如 Box<Foo>
是普通指针大小的两倍,所以你不能用 void *
来表示它们。有关详细信息,请参阅 std::raw::TraitObject
。这是您的代码的工作版本:
program.c:
#include <dlfcn.h>
#include <stdio.h>
struct rs_trait_obj {
void *data;
void *vtable;
};
struct rs_trait_obj global_ctx;
void c_function(struct rs_trait_obj ctx) {
printf("Called c_function\n");
global_ctx = ctx;
}
int main(void) {
void *thing = dlopen("thing/target/debug/libthing.dylib", RTLD_NOW | RTLD_GLOBAL);
if (!thing) {
printf("error: %s\n", dlerror());
return 1;
}
void (*rust_function)(void) = dlsym(thing, "rust_function");
void (*rust_cb)(struct rs_trait_obj) = dlsym(thing, "rust_cb");
printf("rust_function = %p\n", rust_function);
rust_function();
rust_cb(global_ctx);
}
lib.rs:
#![feature(raw)]
extern crate libc;
use std::raw::TraitObject;
use std::mem;
pub trait Foo {
fn callback(&self);
}
extern {
fn c_function(context: TraitObject);
}
pub struct MyFoo;
impl Foo for MyFoo {
fn callback(&self) {
println!("callback on trait");
}
}
#[no_mangle]
pub extern fn rust_cb(context: TraitObject) {
unsafe {
let cb: Box<Foo> = mem::transmute(context);
cb.callback();
}
}
#[no_mangle]
pub extern fn rust_function() {
println!("Called rust_function");
let tmp: Box<Foo> = Box::new(MyFoo);
unsafe {
c_function(mem::transmute(tmp));
}
}
这仅适用于 nightly rustc(因为 #![feature(raw)]
)并且还会发出警告,因为 TraitObject
不是 FFI 安全的。如果你想要稳定运行的东西,你可以像这样定义一些适当大小的结构并使用它而不是 TraitObject
:
#[repr(C)]
struct FFITraitObject {
data: usize,
vtable: usize,
}
当然,另一种选择是使用 Box<Foo>
代替 TraitObject
,但您仍然会收到警告:
extern crate libc;
pub trait Foo {
fn callback(&self);
}
extern {
fn c_function(context: Box<Foo>);
}
pub struct MyFoo;
impl Foo for MyFoo {
fn callback(&self) {
println!("callback on trait");
}
}
#[no_mangle]
pub extern fn rust_cb(context: Box<Foo>) {
context.callback();
}
#[no_mangle]
pub extern fn rust_function() {
println!("Called rust_function");
let tmp: Box<Foo> = Box::new(MyFoo);
unsafe {
c_function(tmp);
}
}
如果你真的想使用 void *
,你可以考虑泄漏 TraitObject
以及 MyFoo
并使用两个间接级别。
因此,如果您需要将 Foo
表示为 void *
,您可以使用:
extern crate libc;
pub trait Foo {
fn callback(&self);
}
extern {
fn c_function(context: *mut libc::c_void);
}
pub struct MyFoo;
impl Foo for MyFoo {
fn callback(&self) {
println!("callback on trait");
}
}
#[no_mangle]
pub extern fn rust_cb(context: *mut Box<Foo>) {
unsafe {
let cb: Box<Box<Foo>> = Box::from_raw(context);
cb.callback();
}
}
#[no_mangle]
pub extern fn rust_function() {
println!("Called rust_function");
let tmp: Box<Box<Foo>> = Box::new(Box::new(MyFoo));
unsafe {
c_function(Box::into_raw(tmp) as *mut Box<Foo> as *mut libc::c_void);
}
}
我认为您可能误解了什么是特征对象。特征对象是一种大小为两个指针的类型(因此,在 64 位系统上为 128 位)。在此示例中,Foo
不是特征对象,它是动态大小的类型(即具有可变大小的类型,例如 str
)。 Box<Foo>
是一个特征对象。 Box<Box<Foo>>
既不是特征对象也不是动态大小的类型,它是一种与指针大小相同的类型,这就是我们需要在这里使用它的原因,因为我们想将它转换为 void *
.
我称它为 "leaking" 因为当你调用 Box::into_raw
时,你正在泄漏盒子里的任何东西的内存,这意味着你有责任确保析构函数(Drop
实现)被调用。