Rust : 与C多种交互尝试

2023-09-16 11:59:33

rust调用C端的库函数,有很多方法,场景也有所不同。包括windows还是linux,内置库还是自定义库,还是三方库等等。

一、rust调用其内置的C库
这个很简单,直接把extern "C"引入即可:
比如,在rust端main.rs中:

use std::os::raw::c_int;//f32
use std::os::raw:c_double;// f64
extern "C" {
	fn abs(num:c_int) ->c_int;
	fn sqrt(num:c_double) ->c_double;
}
fn main{
	println!("call c->abs :{}",unsafe{abs(-32)});
	println!("call c -> sqrt:{}",unsafe{sqrt(36.0)});
}

不需要做其它的处理,直接cargo run 就可以运行。

二、自定义的C库-以windows平台为例

如果rust要调用自建的C库中的函数,情况会较上面复杂一些。今天介绍通过cc库,通过build生成脚本的方式,实现rust调用c端库函数。

1、相关准备:
在这里插入图片描述
在ffi目录下,创建了c_part和rust_ffi文件夹。 c_part下放了ctools.c文件,里面有一些库函数,需要让rust调用。当然,ctools.c也可以放在其它地方,只需要后面的地址一致即可以。

2、cargo toml部分
在这里插入图片描述这里需要注意:

build="build.rs"
libc ="0.2"
cc ="1.0"

有一些依赖和说明。
3、ctools.c

// ctools.c 代码
int add(int i,int j){
    return i+j;
}
int two_times(int input){
    return input*2;
}
int three_times(int input){
    return input*3;
}

4、build.rs文件

extern crate cc;

fn main(){
    cc::Build::new().file("../c_part/ctools.c").compile("libctools.a");

}

cc::Build::new().file(“…/c_part/ctools.c”).compile(“libctools.a”);的作用是使用 cc crate 编译 …/c_part/ctools.c文件,并将生成的静态库命名为 libctools.a。

(1)需要注意的是,生成的静态库或动态库的命名和文件格式可能会因操作系统和编译器的不同而有所区别。例如,在 Windows 系统上,静态库的命名通常是 libctools.a,而动态库的命名通常是 ctools.dll。生成静态库或动态库后,就可以使用 Rust 的 #[link(name = “ctools”)] 属性来链接库文件并在 Rust 代码中调用 C 函数了。
如果没有在 Rust 代码中使用 #[link(name = “ctools”)] 属性来指定链接的库的名称,Rust 编译器会默认按照一定的规则搜索系统默认的库文件路径来查找库文件。具体来说,Rust 编译器会按照以下顺序搜索库文件:

在系统默认的库搜索路径中查找:Rust 编译器会搜索系统默认的库文件路径,例如 /usr/lib 和 /usr/local/lib 等目录。
在 Rust 代码所在的目录中查找:如果 Rust 代码和库文件在同一个目录中,Rust 编译器会在该目录中查找库文件。
在指定的搜索路径中查找:如果在编译 Rust 代码时使用了 -L 参数指定了库文件搜索路径,Rust 编译器会在这些路径中查找库文件。

注:此部分内容来源以下链接,在此特别说明。

https://vincebye.github.io/posts/rust%E8%B0%83%E7%94%A8c%E4%BB%A3%E7%A0%81/

(2 )需要注意的是,file中ctool.c文件地址一定要准确,否则会有如下报错信息(但没有明示说路径不对,找不到文件之类)。报错可能如下(下面标红处路径是故意写错路径的情况):
在这里插入图片描述5、rust端:main.rs

extern crate libc;
use libc::c_int;
extern "C" {
    fn add(i:c_int,j:c_int)  ->c_int;
    fn two_times(input:c_int) ->c_int;
    fn three_times(input:c_int) ->c_int; 
}

fn main() {
    println!("Hi guys, welcome rust ffi !");
    let twotimes_value:i32 = unsafe{two_times(-8)};
    println!("twotimes_value  : {:?}",twotimes_value);
    let add_value = unsafe{add(2,3)};
    println!("add_value       : {:?}",add_value);
    let threetimes_value = unsafe{three_times(3)};
    println!("threetimes_value: {:?}",threetimes_value);
}

引入libc库,以及c_int类型。

6、cargo build
如果配置正确,在rust_ffi目录下(build.rs所在目录),运行cargo build:在这里插入图片描述可见build成功。

7、cargo run

在这里插入图片描述相关结果表明,rust端已经正确调用了ctools.c中几个库函数。
注意的是,因为已经是ffi调用,均需要加unsafe。

三、rust调用C封装好的静态库

(一)、linux平台
总体物料安排和上面windows平台自定义库类似,不再详述。
在这里插入图片描述

1、toml文件 , 其实只需要加“build”项,不需要cc库。

[package]
name = "myffi"
version = "0.1.0"
edition = "2021"
build   ="build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

2、准备生成相应的静态库供调用
在cpart目录下,有ctools.c文件
在这里插入图片描述
在cpart目录下,运行以下命令,生成了libcrust.a静态链接库。

gcc -c ctools.c -o crust.o
ar -cr libcrust.a crust.o

如果发现在对应目录下,已经生成libcrust.a文件,证明已经成功。

3、build.rs
build.rs中,主要一个是rustc-link-lib和rustc-link-search(此处我用了绝对地址)两项即可。具体如下:

fn main(){
    println!("cargo:rustc-link-lib=crust");
    println!(r"cargo:rustc-link-search=native=/home/songroom/ffi/cpart");
}

值得说明一下,build.rs中println!中"cargo:"的输出,是一种类“告示”通信方式,并不是一种简单的常规的打印。这个是告诉编译器,你去帮我这么干,没有这些,链接就不会成功,绝对不是或有或无的。

4、main.rs

加注了一个#[link=(name=“crust”,kind=“static”)]。
注意,尽管静态库名是libcurst,但在链接属性这,crust前面不要加lib。

extern crate libc;
use libc::c_int;
#[link=(name="crust",kind="static")]
extern "C" {
    fn add(i:c_int,j:c_int)  ->c_int;
    fn two_times(input:c_int) ->c_int;
    fn three_times(input:c_int) ->c_int; 
}

fn main() {
    println!("Hi guys, welcome rust ffi !");
    let twotimes_value:i32 = unsafe{two_times(-8)};
    println!("twotimes_value  : {:?}",twotimes_value);
    let add_value = unsafe{add(2,3)};
    println!("add_value       : {:?}",add_value);
    let threetimes_value = unsafe{three_times(3)};
    println!("threetimes_value: {:?}",threetimes_value);
}

5、配置路径

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:pwd

6、在build.rs的目录下cargo build

root@DESKTOP-MEDPUTU:/home/songroom/ffi/myffi/src# cargo build
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s

7、cargo run

root@DESKTOP-MEDPUTU:/home/songroom/ffi/myffi/src# cargo run
   Compiling myffi v0.1.0 (/home/songroom/ffi/myffi)
    Finished dev [unoptimized + debuginfo] target(s) in 0.44s
     Running `/home/songroom/ffi/myffi/target/debug/myffi`
Hi guys, welcome rust ffi !
twotimes_value  : -16
add_value       : 5
threetimes_value: 9

(二)、在windows平台
上面的方式,在windows还未成功,待续。

此外,动态库应差不多,有机会待尝试。

四、用cbindgen实现rust与c类型交互类型,并实现互调
此外,还有一些场景,有待深入:
1、上面主要讲的rust调C,并没有涉及C如何调Rust。如何实现C和Rust之间的互调?
2、此外,C类库中头文件中有各种不同的复杂自定义类型,且内容很多,如果没有一个自动化的工具来实现对应转换,两者的交互会很困难。
当然,cbindgen 不但可以生成 C 头文件,也可以针对C头文件生成rust的类型,便于交互。
待续。

五、总结及提示
1、build.rs要放在main.rs同一个目录,否则可以无效。
2、build.rs中的库的路径指定是关键之一。具体见build.rs内容。

更多推荐

Linux进程【1】进程概念(超详解哦)

进程概念引言(操作系统如何管理)基本概念描述与组织进程查看进程进程pid与ppidgetpid与getppid总结引言(操作系统如何管理)在冯诺依曼体系结构中,计算机由输入设备、输出设备、运算器、控制器和存储器组成。我们使用计算机的时候,实际就是数据在这些硬件中传递的过程。硬件的行为由驱动控制,驱动又由更上层的操作系统

jvm深入研究文档--jvm分区以及职责

Java虚拟机(JVM)主要包括以下几个区域:方法区(MethodArea):这个区域存储已被加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。方法区是所有线程共享的。在Java8之前,方法区是永久代(PermGen),从Java8开始,永久代被元空间(Metaspace)替代。堆区(Heap):这是Java

K8S:Pod概念、分类及相关的策略

文章目录一.pod相关概念1.Pod基础概念2.Kubrenetes集群中Pod两种使用方式3.pause容器的Pod中的所有容器共享的资源4.kubernetes中的pause容器主要为每个容器提供功能:5.Kubernetes设计这样的Pod概念和特殊组成结构有什么用意6.Pod分为两类:二.Pod容器的分类1.基

[k8s] 常见yml配置和详细解释

在Kubernetes(K8s)中,常见的YAML文件配置包括:文章目录PodDeploymentServiceClusterIPNodePortLoadBalancerIngressConfigMapSecretVolume访问模式StatefulSetDaemonSetJob容器的重启策略ConJobPodapiV

基于Java的高校科研信息管理系统设计与实现(亮点:完整严谨的科研项目审批流程、多文件上传、多角色)

高校科研信息管理系统一、前言二、我的优势2.1自己的网站2.2自己的小程序(小蔡coding)2.3有保障的售后2.4福利三、开发环境与技术3.1MySQL数据库3.2Vue前端技术3.3SpringBoot框架3.4微信小程序四、功能设计4.1主要功能描述五、系统实现5.1系统主要功能展示5.1.1三个角色展示5.1

详谈操作系统中的内核态和用户态

不知道大家有没有思考过这样一个问题:什么是处理器(CPU)的状态?🤔其实CPU和人一样,没有执行程序的时候,是没有什么状态的,当它执行的程序是用户程序的时候就叫用户态,当执行的程序是操作系统的代码时就叫系统态或者内核态.接下来,我们就来谈谈内核态和用户态.目录1.内核态和用户态的概念2.内核态和用户态的区别3.特权指

《银河麒麟高级服务器操作系统V10》使用

一言而论:讲了麒麟服务器V10的基本使用,包括终端、VNC文章目录前言基本架构环境硬件环境软件环境麒麟安装步骤1.在宿主机上安装好VM,并且激活2.使用VM创建虚拟机3.启动虚拟机终端常用点VNC的使用麒麟上安装VNC服务器Windows上安装VNC客户端VNC服务器补充事项总结一些吐槽(坑)参考资料前言公司接到项目,

【AI】推理系统和推理引擎的整体架构

本文主要是对B站Up主ZOMI酱推理系统系列视频的理解,可以认为是重点笔记。一、深度学习模型的全生命周期相信很多人和我一样,刚看到深度学习模型中的推理系统或推理引擎时是一头雾水,因为学习DL时通常关注于模型的设计和训练。下图是深度学习模型的全生命周期图,主要分为两大类任务,训练任务和推理任务。训练任务:通常需要执行数小

理解 React 服务器组件

自从React被引入开发社区以来的十年里,它经历了几次演变。React团队在发生根本性变革时并不害羞:如果他们发现了一个更好的问题解决方案,他们就会带着它运行。几个月前,React团队推出了ReactServerComponents,这是最新的范式转变。有史以来第一次,React组件可以只在服务器上运行。网上对此有太多

计算物理专题----蒙特卡洛积分实战

Partone蒙特卡洛积分计算案例importnumpyasnpimportmatplotlib.pyplotaspltimportpandasaspdfromscipy.statsimportnorm,kstestnp.random.seed(0)defintegrate(a,b,n=100):x=np.random

面向过程与面向对象、面向对象三大特性的介绍和示例

面向过程:将问题分解成一个个详细的步骤,然后通过函数实现每一个步骤,并依次调用特点:1、适合解决简单的问题,不需要过多的协作和抽象2、关注问题的解决步骤而不是问题的本质3、代码复用性低,扩展性差,不易维护4、只有封装,没有继承和多态面向对象:通过分析问题,分解出一个个对象,然后通过不同对象之间的调用和相互协作来解决问题

热文推荐