无法在不导致非法指令的情况下传递新类型包装的不安全 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)
我正在努力包装不安全的 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)