Rust 和c/c++互动

先说c++的情况,大的思路是变c
这个方法很多,说我习惯的。
extern c写一个wrapper.cpp把cpp里面c没有的语法给消除了。然后写一个c的wrapper.c。可以走静态和动态编译两条路。

  • 静态
    // g++ -c apple.cpp AppleWrapper.cpp
    // 加-lstdc++表示链接c++库,加-lc表示链接c库,默认情况下就是链接c库,所以如果编译c文件可以不加
    // gcc test.c -o test AppleWrapper.o apple.o -lstdc++
  • 动态
    // g++ -c -fPIC apple.cpp AppleWrapper.cpp
    // gcc test.c -fPIC -shared -o test.so AppleWrapper.o apple.o -lstdc++

在拿到的就是静态库的情况下,可以把静态库链接到动态库,
// g++ -c -shared -fPIC apple.cpp AppleWrapper.cpp
// ar -r libapple.a apple.o AppleWrapper.o
// gcc test.c -fPIC -shared -o test2.so -L. libapple.a -lstdc+

再说rust和c互动,这个太方便,但是有很多细节散乱在各个地方,官方文档并未说清楚(至少对于我这种非熟练的c++选手)。

  1. 用一个工具,bindgen,注意还有一个cbindgen,别搞混了,那个我误用过,没真正用过。创建一个wrapper.h,这个最简单的就是copy。跟着官网文档,会生成bindings.rs这个就是两个语言的接口文件。
  2. 关于类型转化,原生类型没啥说的,接口文件都给你了。字符串需要用到use std::ffi::CString,里面有各种转化方法。
extern "C" {
    pub fn say_str(a: *const ::std::os::raw::c_char) -> *mut ::std::os::raw::c_char;
}
fn use_say_str() {
    unsafe {
        let c_to_print = CString::new("Hello, world!").expect("CString::new failed");
        let arg1 = c_to_print.as_ptr();
        let res1 = b::say_str(arg1);
        // from raw等于释放内存
        let res2 = CString::from_raw(res1);
        dbg!(res2);
    }
}
  1. 下面结构体。大思路,用Box::from_raw()接球,同时也就被rust接管,无需手动释放了内存。
extern "C" {
    pub fn init_man() -> *mut man;
}

fn use_man() {
    unsafe {
        let a = b::init_man();
        // from raw等于释放内存
        let m = Box::from_raw(a);
        let n = m.name;
        let name = ptr_cstring(n, 6);

        let age = m.age;
        dbg!(age,name);
    }
    // 下面这两句多余,因为from raw等于释放内存
    // let p = Box::into_raw(m);
    // unsafe { b::free_man(p) };
}

4 说个坑。如果struct里面含有char*。如果传递struct有string,一定要把str长度传递进来。通过std::slice::from_raw_parts把指针(头地址)以及后面内容拷贝到slice,直接使用CString from_ptr的方法会发生段错误。

fn ptr_cstring(ptr: *mut i8, l: usize) ->String {
    let res=
    unsafe {
        // 把一个字符串指针,copy到一个slice bytes返回地址,数组应该也可以用这种方法
        let arr: &[u8] = std::slice::from_raw_parts(ptr as *const u8, l);
        CString::new(arr).expect("c string error")
    };
    let a=res.into_string().unwrap();
    a
}

5 再说一个查了好久才知道的,build.rs 如何包裹库文件,分为三种情况:
5.1直接使用cc编译c源码是下面的方式build_c,无坑,cc可以根据系统调用合适的编译器。

#[allow(dead_code)]
fn build_c() {
    cc::Build::new()
        .file("src/double.c")
        .compile("libdouble.a");
}

5.2使用bindgen根据wrapp h生成rust接口文件bindings rs,打包静态库。注意这里面传输传递很特殊,通过println cargo::的方式,这个是设置环境变量,不是打印让你看着玩的。

#[allow(dead_code)]
fn set_compile_link_lib() {
    let root = std::env::var("CARGO_MANIFEST_DIR").unwrap();
    let lib = format!("{}/src/lib/", root);
    println!("cargo:warning=MESSAGE");
    println!("cargo:rustc-link-search=all={}", lib);
    println!("cargo:rustc-link-lib=static=wq");
    // println!("cargo:rustc-link-lib=dylib=wq");
}

如果打包的是动态库,需要// export LD_LIBRARY_PATH=你的库的路径:$LD_LIBRARY_PATH
// echo $LD_LIBRARY_PATH

5.3 运行如果是动态链接加载libload so不需要buildrs

extern crate libloading as lib;
pub fn load_dynamic() -> lib::Result {
    let file="/mnt/myrust/rust-ffi-examples-master/rust-call-c/src/lib/libtest.so";

    let lib = lib::Library::new(file)?;
    unsafe {
        let func: lib::Symbol u32> = lib.get(b"say_int")?;
        Ok(func(18))
    }
}
#[test]
pub fn lib_load_so() {
    let a = load_dynamic().unwrap();
    dbg!(a);
}

你可能感兴趣的:(rust,c++)