无法在不导致非法指令的情况下传递新类型包装的不安全 C 结构

can't pass a newtype-wrapped unsafe C struct without causing illegal instruction

我正在努力包装不安全的 FFI 层 here,并且 运行 遇到了一个非常奇怪的问题。 (最近每晚)

extern crate cql_ffi;
use std::ffi::CString;

#[allow(missing_copy_implementations)]
pub struct CassCluster(pub cql_ffi::CassCluster);

fn main() {

    let cluster = &mut CassCluster(unsafe{*cql_ffi::cass_cluster_new()});
    println!("trying method 1");
    let result1 = method1();

    println!("trying method 2");
    let result2 = method2(cluster);
}

pub fn method1() {
    let cluster = &mut CassCluster(unsafe{*cql_ffi::cass_cluster_new()});    
    let result = unsafe{cql_ffi::cass_cluster_set_contact_points(&mut cluster.0,  CString::from_slice("127.0.0.1".as_bytes()).as_ptr() as *const i8)};
}

pub fn method2(cluster: &mut CassCluster) {
    let result = unsafe{cql_ffi::cass_cluster_set_contact_points(&mut cluster.0,  CString::from_slice("127.0.0.1".as_bytes()).as_ptr() as *const i8)};
}

请注意,方法 1 和方法 2 的区别仅在于簇是传入 fn 还是在其中创建。

当运行时:

trying method 1
trying method 2
Illegal instruction

无论是否调用 method1,method2 总是因非法指令而失败。

valgrind 生成的堆栈跟踪可能很有趣:

trying method 2
==19145== Invalid write of size 8
==19145==    at 0x6A10ACF: std::__detail::_List_node_base::_M_hook(std::__detail::_List_node_base*) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.19)
==19145==    by 0x4F78367: std::list<std::string, std::allocator<std::string> >::_M_insert(std::_List_iterator<std::string>, std::string const&) (in /usr/local/lib/libcassandra.so.1.0.0.rc1)
==19145==    by 0x4F77E13: std::list<std::string, std::allocator<std::string> >::push_back(std::string const&) (in /usr/local/lib/libcassandra.so.1.0.0.rc1)
==19145==    by 0x4F8AE69: cass_cluster_set_contact_points (in /usr/local/lib/libcassandra.so.1.0.0.rc1)
==19145==    by 0x10DDC3: method2::h2b76fca37ae2e2878ba (test.rs:25)
==19145==    by 0x10DA1B: main::hc39cc26c65e20849maa (test.rs:13)
==19145==    by 0x11C198: rust_try_inner (in /home/tupshin/workspaces/rust/cql-ffi-safe/target/test)
==19145==    by 0x11C185: rust_try (in /home/tupshin/workspaces/rust/cql-ffi-safe/target/test)
==19145==    by 0x11988C: rt::lang_start::hd3d7c7415c447b9fdBB (in /home/tupshin/workspaces/rust/cql-ffi-safe/target/test)
==19145==    by 0x10DC04: main (in /home/tupshin/workspaces/rust/cql-ffi-safe/target/test)
==19145==  Address 0x0 is not stack'd, malloc'd or (recently) free'd

我认为您从错误的角度来解决这个问题 - 您不应该解除对 C 库 returning 给您的指针的引用。你甚至不知道被指对象的构成是什么!

相反,您应该简单地保留 指针并将其传递回需要它的函数。我已经采用了您的一些代码并对其进行了重组。在这里,我们有一个名为 Cluster 的结构,它将拥有由 cass_cluster_new 编辑的指针 return。我们创建了一个小示例方法,它对集群做了一些有趣的事情,我们还处理了在完成 Cluster.

后释放资源的问题

请注意,此 Cluster 结构实际上占用 space,而不是您当前拥有的空 Cluster 枚举。空枚举占用 zero space,因此它将以有趣的方式进行优化。但是,您实际上需要将指针 保存在某个地方 !

另一件事是我们简单地将库中的 return 指针视为 c_void。这是因为我们永远不会取消引用它,所以我们只是把它当作一个不透明的句柄。

#![feature(libc)]

extern crate libc;

use libc::{c_void,c_int};

extern "C" {
    pub fn cass_cluster_new() -> *mut c_void;
    pub fn cass_cluster_free(cluster: *mut c_void);
    pub fn cass_cluster_set_port(cluster: *mut c_void, port: c_int); // ignoring return code
}

struct Cluster(*mut c_void);

impl Cluster {
    fn new() -> Cluster {
        Cluster(unsafe { cass_cluster_new() })
    }

    // N.B. Ports are better represented as u16! 
    fn set_port(&mut self, port: i32) {
        unsafe { cass_cluster_set_port(self.0, port) }
    }
}

impl Drop for Cluster {
    fn drop(&mut self) {
        unsafe { cass_cluster_free(self.0) }
    }
}

fn main() {
    let mut cluster = Cluster::new();
    cluster.set_port(5432);

    // cluster is automatically dropped when it goes out of scope
}

(Playpen)