【问题标题】:Can I use cxx.rs types in cbindgen C callback?我可以在 cbindgen C 回调中使用 cxx.rs 类型吗?
【发布时间】:2022-10-09 11:33:34
【问题描述】:

我正在尝试将一个小的 rust 库嵌入到我的 C++ 代码库中,我使用 cargo-c(我认为它使用 cbindgen ?)来创建一些简单的 rust C api,rust side api,如下所示:

#[no_mangle]
pub extern "C" write_result_to_cpp_callback(
    cpp_callback : Option<extern "C" fn(  i64 )>)

(它基于回调而不是返回值,因为 rust 部分实际上运行一个 tokio 运行时来执行 io 作业,并且对 rust 的任何调用都是非阻塞的)

现在我需要传递一些复杂的结构而不是简单的 i64,似乎 cxx.rs 使这更简单,但是 cxx.rs 函数指针部分文档说

Passing a function pointer from C++ to Rust is not implemented yet, only from Rust to an extern "C++" function is implemented.

我想知道我是否可以在我的 cbindgen C API 中使用 cxx.rs 生成的类型?例如

// for cxx.rs
#[cxx::bridge]
mod ffi{
   struct MyStruct{
      my_vec: Vec<String>
 }
}

// for cbindgen
#[no_mangle]
pub extern "C" write_result_to_cpp_callback(
    cpp_callback : Option<extern "C" fn( *const ffi::MyStruct )>)

如果这样的用法没问题,cbindgen 导出的 api 是否也会对某些容器使用与 cxx.rs 相同的 API,例如下面的代码也可以工作?

// will this Vec have compatible memory layout as I use 
// the cxx.rs generated C++ code ?

#[no_mangle]
pub extern "C" write_result_to_cpp_callback(
    cpp_callback : Option<extern "C" fn( *const Vec<ffi::MyStruct> )>)

// or I need to also wrap the Vec as a cxx.rs struct member 
// just like following ?

mod ffi{
   struct WrapVec{
      my_vec: Vec<MyStruct>
 }
}

#[no_mangle]
pub extern "C" write_result_to_cpp_callback(
    cpp_callback : Option<extern "C" fn( *const  WrapVec)>)


感谢您的建议

【问题讨论】:

  • 感谢您提供的信息,但是示例和我的用法之间仍然存在差距,IIUC,异步显示在 cxx.rs 中声明的 C++ fn 应该识别 cxx.rs 内置类型,包括 Vec<T>,但是如果从 rust 调用它,我是直接通过 ffi 调用(因此内存布局兼容),还是调用 cxx.rs wrap fn? , 并且 vec 显示 rust 函数可以调用 C++ 并传递 rust::Vec<T>,IIRC rust 需要标记 struct repr(C) 以使其与 C 兼容,但我没有在 rust 中找到 Vec 的 wrap 类型,那么这是否意味着 rust std 默认中的 Vec 标记为 repr(C)?
  • 据我了解,rust::Vec&lt;T&gt; 持有一个指向 Rust Vec 的指针,每个函数调用都调用 Rust 以在 Vec 上执行该操作。就像,rust_vec.push_back(val) 将该值传递给 Rust,然后从 Rust 端调用 vec.push(val)

标签: c++ c rust


【解决方案1】:

在@PitaJ 的一些挖掘和帮助下,我找到了我的问题的答案

对于用户声明的类型,它可以在 cbindgen 中使用(甚至作为回调签名),这种类型还可以包含 cxx.rs 支持的成员(例如 Vec)。因为类型本身将被声明为 repr(C) ,因此类型本身在 ffi 边界上是兼容的。如果此类类型包含通用容器成员(例如 Vec),则它将是 rust std vec 类型。您可以通过运行cargo expand 来确认它(如 cxx.rs 建议的那样)

        #[repr(C)]
        pub struct UserDeclaredType{
            pub vec_member: ::std::vec::Vec<String>
        }

由于 rust std Vec 没有标记为 repr(C),它将具有 rust(rustc) 定义的内存布局,这是 C++ 无法直接理解的。 cxx.rs 生成相应的 C++ 模板类作为虚拟包装器

class Vec final {
public:
  ......
private:
  static size_t stride() noexcept;
  void reserve_total(size_t cap) noexcept;
  void set_len(size_t len) noexcept;
  void drop() noexcept;

  std::array<uintptr_t, 3> repr;   
};

如您所见,它不包含任何 C++ 有意义的成员(我将它与 rust vec 的大小/对齐方式相同,以使两个内存块跨 ffi 边界兼容,因为 rust vec 具有 ptr/len/capacity 3 个成员)。对于每个模板实例化,cxx.rs 生成一个模板特化来将每个 C++ 模板函数分派到 cxx.rs 生成的 extern C 帮助函数

template <>
void Vec<::MyType>::drop() noexcept {
  return cxxbridge1$rust_vec$MyType$drop(this);
}

你可以在 rust 中找到这个 cxx.rs 生成的辅助函数

            #[doc(hidden)]
            #[export_name = "cxxbridge1$rust_vec$MyType$drop"]
            unsafe extern "C" fn MyType__vec_drop(
                this: *mut ::cxx::private::RustVec<MyType>,
            ) {
                ::std::ptr::drop_in_place(this);
            }

并且 cxx.rs rust::private::RustVec 是 std Vec 的 repr(C) 包装器

#[repr(C)]
pub struct RustVec<T> {
    pub(crate) repr: Vec<T>,
}

所以即使用户声明的类型被用作cbindgen中回调的参数类型,C++仍然可以理解这种类型(因为它被声明为repr(C)),也是通用容器成员(因为cxx.rs生成了这个包装的Vec类型类型)。因此

#[cxx::bridge]
mod ffi{
   struct MyStruct{
      my_vec: Vec<String>
 }
}

// for cbindgen
#[no_mangle]
pub extern "C" write_result_to_cpp_callback(
    cpp_callback : Option<extern "C" fn( *const ffi::MyStruct )>)

应该可以。但遵循回调类型

#[no_mangle]
pub extern "C" write_result_to_cpp_callback(
    cpp_callback : Option<extern "C" fn( *const Vec<ffi::MyStruct> )>)

将不起作用,因为 cbindgen 在没有任何 C++ 实现的情况下盲目地为参数生成了一个“Vec 模板”,所以 C++ 无法理解它(我认为你仍然可以模仿 cxx.rs 所做的事情)。所以仍然需要手动将 Vec arg 包装为用户声明类型的成员(只需要使其成为 cbindgen 签名的一部分,并且您不想手动模仿 cxx.rs 的内容)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-07-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多