Rust 调用标准C接口的自定义c/c++库,FFI详解
目录
- 前言
- 关于库
- 创建项目
- 手动绑定
- 自动绑定
- 结构体
- union联合体
- enum枚举
- 回调函数
- 空指针
- 析构
- ownership
- panic
- 参考文章:
前言
没有前言,干就完事了。
关于库
本人环境是win10,vs2013。
不管什么环境,用下面的文件制作出对应的动态库和静态库就可以。
hello.h 文件
#include "stdio.h"
#include <iostream>using namespace std;#define EXTERN_C extern "C"
#define DLLEXPORT __declspec(dllexport)EXTERN_C DLLEXPORT void say_hello();
EXTERN_C DLLEXPORT int num(int a, int b);
EXTERN_C DLLEXPORT int get_strlen(char * s);
hello.c文件
#include "hello.h"DLLEXPORT void say_hello(){cout << "hello" <<endl;
}
DLLEXPORT int num(int a, int b){return a + b;
}
DLLEXPORT int get_strlen(char * s){return (int)strlen(s);
}
注意:rust项目和动态库编译一定要统一平台,都是x86,或者都是x64
我这里都是x64,Unicode编码,如下图
点击生成,制作出动态库和静态库。
创建项目
cargo new testffi
手动绑定
拷贝动态库静态库到项目根目录下,
修改main.rs如下
#[link(name = "hello")]
extern {pub fn say_hello();
}
fn c_say_hello(){unsafe {say_hello();}
}
fn main() {c_say_hello();
}
- link宏说明:
name指的是库名 - kind指定 默认动态库 后缀 so/dll/dylib/a
//标记静态库
#[link(name = "foo", kind = "static")]
//osx的一种特殊库
#[link(name = "CoreFoundation", kind = "framework")]
- extern 标记的快表示是外来的,默认"C" ABI,就是库中来的,完整应该是
extern "C" {
}
运行如下命令编译运行
//编译
cargo build
//运行
cargo run
如下图
编译时需要两个库.dll .lib 都存在(win10,其他环境为测试),运行exe是只需要你指定的库就可以,我们删除掉hello.lib,运行如下命令也是可以的。
自动绑定
这里使用bindgen包做构建
1 前提
1.1 安装vs2015或更高版本
1.2 安装llvm 6以上版本
llvm下载地址:https://releases.llvm.org/download.html#6.0.1
1.3 设置环境变量LIBCLANG_PATH为指向LLVM安装目录的bin目录
查看环境变量是否生效,在powershell中输入命令 查看
Get-ChildItem env:
2 引入包
编辑cargo.toml 文件,加入
[build-dependencies]
bindgen = "0.57"
3 根目录下新建build.rs文件,编辑如下
fn main(){println!("cargo:rustc-link-lib=hello"); //指定库// println!("cargo:rerun-if-changed=lib/hello.h");let bindings = bindgen::Builder::default().header("./lib/hello.h") //指定头文件,可以指定多个.h文件作为输入.parse_callbacks(Box::new(bindgen::CargoCallbacks)).generate().expect("Unable to generate bindings");bindings.write_to_file("./src/output.rs").unwrap(); //输出到那个目录
}
4 将hello.h文件编辑一下,放入项目中。主要是去掉不能识别的部分。
void say_hello();
int num(int a, int b);
int get_strlen(char * s);
最终目录如下
5 修改main.rs为fn main(){}
6 运行编译命令 cargo build生成output.rs
7 修改main.rs 如下
- 这里标注一个fixme,ownership有详细说明
use std::ffi::CStr;
mod output;fn c_say_hello(){unsafe {output::say_hello();}
}
fn c_num(a:i32,b:i32)->i32{unsafe {return output::num(a, b);}
}
//Fixme 对于owned类型此处是一个不那么正确的使用
fn c_get_strlen(s:&str)->i32{unsafe {let s = CStr::from_bytes_with_nul(s.as_bytes()).expect("&str to cstr failed");return output::get_strlen(s.as_ptr() as *mut i8);}
}
fn main() {c_say_hello();println!("2+3={}",c_num(2,3));//c中字符串以\0结println!("\"hello world\" len is {}",c_get_strlen("hello world\0"));
}
cargo run 运行效果如下。
结构体
在.h文件中添加一个结构体如下:
typedef struct struct_hello{int index;
}hello;
重新构建,依次生成如下结构体,测试单元,别名
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct struct_hello {pub index: ::std::os::raw::c_int,
}
#[test]
fn bindgen_test_layout_struct_hello() {...
}
pub type hello = struct_hello;
- #[repr(Rust)],默认布局或不指定repr属性。
- #[repr©],C 布局,这告诉编译器"像C那样对类型布局",可使用在结构体,枚举和联合类型。
union联合体
在.h文件中写入一个union结构
union union_world {unsigned short int index;struct {unsigned int in : 7;//(bit 0-6)unsigned int d : 6;//(bit 7-12)unsigned int ex : 3;//(bit 13-15)};
}world;
构建后得到结果大致如下,标签上和结构体差不多,但多了union_world__bindgen_ty_1 结构和相应的操作方法,方便位的操作。
#[repr(C)]
#[derive(Copy, Clone)]
pub union union_world {pub index: ::std::os::raw::c_ushort,pub __bindgen_anon_1: union_world__bindgen_ty_1,...
}
...
pub struct union_world__bindgen_ty_1 {...
}
impl union_world__bindgen_ty_1 {... //联合体相关操作
}
#[test]
fn bindgen_test_layout_union_world() {...
}
extern "C" {pub static mut world: union_world;
}
enum枚举
于struct差别不大
回调函数
1 编辑hello项目,hello.h中添加函数生命和方法。这里改造一下求和方法。
typedef int(*callback) (int);
EXTERN_C DLLEXPORT int num_callback(int a, callback);
2 编辑hello.c实现num_callback方法
DLLEXPORT int num_callback(int a, callback func){return func(a);
}
3 拷贝hello.lib和hello.dll 到根目录下。编辑testffi项目中的lib/hello.h文件,将新加入的头部信息写入。重新构建output.rs文件。
4 在main.rs中编写回调函数,重写main方法。
- rust回调给c的函数,只需要在rust函数的基础上添加extern "C"就可以了。
use std::os::raw::c_int;
mod output;
pub extern "C" fn square(a:c_int)->c_int{return a * a;
}
fn main() {unsafe {let cb:output::callback = Some(square);println!("3*3={}",output::num_callback(3,cb));}
}
5 cargo run 效果如下:
空指针
如果需要一个空指针。可以用使用 0 as *const _
或者 std::ptr::null()
来生产一个空指针。
析构
在涉及ffi调用时最常见的就是析构问题:这个对象由谁来析构?是否会泄露或use after free? 有些情况下c库会把一类类型malloc了以后传出来,然后不再关系它的析构。因此在做ffi操作时请为这些类型实现析构(Drop Trait)
ownership
由于编译器会自动插入析构代码到块的结束位置,在使用owned类型时要格外的注意。
在上边fixme 注意是标明了一处错误的使用。
rust中CString对应c中的字符串,所以正确的使用应该如下:
fn c_get_strlen(s:&str)->i32{unsafe {let s = CString::new(s).expect("&str to cstring failed");//使用into_raw避免rust在代码块结束时释放cstringreturn output::get_strlen(s.into_raw());}
}
fn main() {//此处不需要加\0,CString和c字符串对应println!("\"hello world\" len is {}",c_get_strlen("hello world"));
}
panic
由于在ffi中panic是未定义行为,切忌在cffi时panic包括直接调用panic!,unimplemented!
,以及强行unwrap
等情况。
引用大佬的一句话
当你写cffi时,记住:你写下的每个单词都可能是发射核弹的密码!
参考文章:
官档doc.rust: https://doc.rust-lang.org/nomicon/ffi.html
极客大佬:https://wiki.jikexueyuan.com/project/rust-primer/ffi/calling-ffi-function.html
bindgen用户指南:https://rust-lang.github.io/rust-bindgen/introduction.html
Rust 调用标准C接口的自定义c/c++库,FFI详解相关推荐
- jmeter 导入java,JMeter导入自定义的Jar包的详解教程
1.简介 原计划这一篇是介绍前置处理器的基础知识的,结果由于许多小伙伴或者童鞋们在微信和博客园的短消息中留言问如何引入自己定义的Jar包呢???我一一回复告诉他们和引入插件的Jar包一样的道理,一通百 ...
- 在python中使用关键字define定义函数_python自定义函数def的应用详解
这里是三岁,来和大家唠唠自定义函数,这一个神奇的东西,带大家白话玩转自定义函数 自定义函数,编程里面的精髓! def 自定义函数的必要函数:def 使用方法:def 函数名(参数1,参数2,参数-): ...
- android-短信验证功能,Android实现获取短信验证码的功能以及自定义GUI短信验证详解...
<Android实现获取短信验证码的功能以及自定义GUI短信验证详解>由会员分享,可在线阅读,更多相关<Android实现获取短信验证码的功能以及自定义GUI短信验证详解(8页珍藏版 ...
- python def函数报错详解_python自定义函数def的应用详解
这篇文章主要介绍了python自定义函数def的应用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 这里是三岁,来和大家唠唠 ...
- 【5年Android从零复盘系列之二十】Android自定义View(15):Matrix详解(图文)【转载】
[转载]本文转载自麻花儿wt 的文章<android matrix 最全方法详解与进阶(完整篇)> [5年Android从零复盘系列之二十]Android自定义View(15):Matri ...
- 微信小程序自定义组件子传父详解(多图)
微信小程序自定义组件子传父详解 前言: 刚开始为了测试父传子,所以把页面的数组放在了父组件中 1. 然而子组件中绑定的自定义点击事件依然放在子组件的js文件中 2. 所以就会出现我们点击页面的文字能改 ...
- Python标准库time详解
Python标准库time详解 1.time库 时间戳(timestamp)的方式:通常来说,时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量 结构化时间(struct_time ...
- html自定义列表图标,自定义列表项符号list-style-image详解
本篇文章给大家带来的内容是关于自定义列表项符号list-style-image详解,有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. 一.list-style-image属性 不管是有序 ...
- mybatis接口中的方法重载_MyBatis的Mapper接口以及Example的实例函数及详解
一.mapper接口中的方法解析 mapper接口中的函数及方法 方法 功能说明 int countByExample(UserExample example) thorws SQLException ...
- 性能测试之JMeter接口关联【JSON提取器】详解
1.JSON提取器介绍 相信做过自动化测试的朋友经常会遇到这样的场景:我想调用系统中的某个业务接口,但是需要先登录系统.也就是现在很多接口的访问,都是需要登录接口的token做为基础. 在JMeter ...
最新文章
- 通过简单的Linux命令,编译一个C语言代码
- sklearn自学指南(part42)--使用手册的目录
- pycharm和python在mac里安装_MAC安装python-opencv及在pycharm下的配置
- laravel 创建自定义的artisan make命令来新建类文件
- Python花式编程:多层嵌套列表扁平化
- 企业如何选择数据分析架构?——谈谈3种架构的利弊
- 允许远程访问MySQL的设置
- Linux下批量修改文件名方法
- vim代码对齐命令_vim自动对齐
- 如何系统整理需求调研报告
- 2020年最新测绘规范目录(可下载在线查看相关规范)
- word打开html是空白,网页复制到Word之后或者出现空白或者格式变乱该怎么办
- 【机器学习】十三、一文看懂Bagging和随机森林算法原理
- 一小时看懂Ruby代码基本逻辑(自定义metasploit模块)
- 小米路由器4C从0到自编译以及刷固件
- 如何通过电影学英语 English through Movies
- Python周刊517期
- Linux 终端文件管理器 —— ranger
- SpiderMonkey相关学习资料
- JavaScript中浏览器打开或下载app