C ++
shared_ptr<Foo> create_foo();
铁锈
extern "C" {
pub fn create_foo() -> ???;
}
Bindgen将shared_ptr
变成不透明的斑点。
我不能只使用原始指针,因为C ++代码并不知道我有对Foo
的引用,并且可能会调用其解构函数。
答案 0 :(得分:4)
std::shared_ptr
是C ++类,并且是无法从库中直接导出的非平凡类型-您需要使用目标语言对其进行定义,以使其与C ++中的语言一致。要使用FFI,您需要为您的库函数提供一个简单的C ABI(C ++ ABI不稳定,并且可能在编译器版本之间进行更改(Rust的ABI可能会有所不同)),而且我怀疑与std::shared_ptr
相关的所有函数是否都这样,因此还有一个障碍。
我建议改为从您的库中返回原始C指针,并在Rust中拥有它。
即使在C ++中,要加载C ++库,您也要提供C-ABI函数(通过extern C
)来访问您类型的指针,然后根据需要在C ++中使用它。
所以,几点:
从函数返回原始C指针,而无需进行名称修饰,以便我们知道其名称并可以链接到它:
extern "C" Foo* create_foo();
添加一个知道如何正确释放对象的删除器:
extern "C" void delete_foo(Foo *);
让库用户(Rust)决定如何拥有它,例如,通过boxing
的值并通过std::sync::Arc
(如std::shared_ptr
将其与原子引用计数器一起使用确实):
extern "C" {
fn create_foo() -> *mut Foo;
fn delete_foo(p: *mut Foo);
}
struct MyFoo {
raw: *mut Foo,
}
impl MyFoo {
fn new() -> MyFoo {
unsafe { MyFoo { raw: create_foo() } }
}
}
impl Drop for MyFoo {
fn drop(&mut self) {
unsafe {
delete_foo(self.raw);
}
}
}
fn main() {
use std::sync::Arc;
let value = Arc::new(MyFoo::new());
let another_value = value.clone();
println!("Shared counter: {}", Arc::strong_count(&value));
}
让C ++一方忘记拥有此指针-如果在库外部使用它,并且提供了原始指针,则不能依赖它。
如果您无权访问库源代码,则无法执行任何操作:std::shared_ptr
对象将永远不会释放指针,并且我们不能使其不删除指针。
答案 1 :(得分:1)
我不能只使用原始指针,因为C ++代码并不知道我有对
Foo
的引用,并且可能称它为解构函数。
是,不是。用你的实际例子。 C ++将把shared_ptr
的所有权交给一个叫create_foo
的人,因此C ++知道仍然有一些东西拥有指针。
您需要添加一个get
函数,该函数将为您获取值而不丢失指针的所有权,如下所示:
extern "C" {
std::shared_ptr<Foo> create_foo() {
// do the thing
}
/* or maybe this
std::shared_ptr<Foo> &&create_foo() {
// do the thing
}
*/
Foo *get_foo(std::shared_ptr<Foo> &foo) {
foo.get();
}
void destroy_foo(std::shared_ptr<Foo> foo) {
}
/* or maybe this
void destroy_foo(std::shared_ptr<Foo> &&foo) {
}
*/
}
另外shared_ptr<Foo>
也不是有效的C语言,所以我不知道bindgen和C ++是否接受此命令(可能是警告),但是代码中已经存在该消息。
在Rust方面,您可以这样做:
// must be generated by bindgen and this might create a lot of problems
// this need to be the same struct as the shared_ptr on the C++ side.
// if even one octet is not correct you will run into bugs
// BE SURE that bindgen don't implement Copy for this
struct shared_ptr<T>;
struct Foo(i32);
extern "C" {
fn create_foo() -> shared_ptr<Foo>;
fn get_foo(foo: &shared_ptr<Foo>) -> *mut Foo;
fn destroy_foo(foo: shared_ptr<Foo>);
}
fn main() {
unsafe {
let my_shared_foo = create_foo();
let foo = get_foo(&my_shared_foo);
(*foo).0;
destroy_foo(my_shared_foo);
}
}
当然,这只是一个例子,在这方面没有什么是真正安全的。而且由于我无法测试,请告诉我是否写了不起作用的内容。 bindgen
应该做好这项工作。
答案 2 :(得分:-1)
您可以返回指向动态分配的std::shared_ptr
在C ++方面:
shared_ptr<Foo> create_foo();
extern "C" void *rust_create_foo()
{
shared_ptr<Foo> foo = create_foo();
return static_cast<void*>(new shared_ptr<Foo>(foo));
}
extern "C" void rust_delete_foo(void *ptr)
{
shared_ptr<Foo> *foo = static_cast<shared_ptr<Foo>*>(ptr);
delete foo;
}
在Rust方面:
extern "C" {
pub fn rust_create_foo() -> *const c_void;
pub fn rust_delete_foo(foo: *const c_void);
}
如果您无法调用rust_delete_foo
,则动态分配的指针将泄漏,因此该对象将永远不会被释放。
然后,当您要使用该对象时,必须编写使用该void*
的包装函数,进行强制转换并调用相应的函数。