关于rust:Rust通过FFI调用C
<article class=“article fmt article-content”><h2><code>FFI</code>存在背景</h2><p><code>FFI(Foreign Function Interface)</code>能够用来与其它语言进行交互,然而并不是所有语言都这么称说,例如 <code>Java</code> 称之为 <code>JNI(Java Native Interface)</code></p><p><code>FFI</code> 之所以存在是因为事实中很多代码库都是由不同语言编写的,如果咱们须要应用某个库,然而它是由其它语言编写的,那么往往只有两个抉择:</p><ul><li>对该库进行重写或者移植</li><li>应用 <code>FFI</code></li></ul><h2><code>FFI</code>实现须要面对的挑战</h2><ul><li>调用方语言是否涵盖了被调用语言的数据类型,<code>rust</code>作为调用方语言,涵盖了所有<code>C</code>语言的数据类型,比方<code>C</code>当中的<code>int,double</code>对应了<code>rust</code>中的<code>i32,f64</code>类型</li><li>是否可能解决 <code>C</code>的裸指针,包含指向被看作是字符串的数组指针,包含构造体指针</li></ul><h2>在 <code>Rust</code> 中调用 <code>C</code> 的办法</h2><ol><li>在 <code>Rust</code> 代码中应用 <code>extern</code> 关键字申明要调用的 <code>C</code> 函数</li><li>应用 <code>unsafe</code> 块调用它</li></ol><p>留神点:须要手动解决参数和返回值的转换,可能产生异样报错</p><h2><code>FFI</code>实现调用简略<code>C</code>函数</h2><p>试验平台<code>Ubuntu22.04 amd64 Desktop</code></p><p>调用了<code>C</code>规范库当中的数学库函数,<code>abs</code>求绝对值,<code>pow</code>求幂,<code>sqrt</code>求平方根</p><pre><code class=“rust”>use std::os::raw::{c_double, c_int};// 从规范库 libc 中引入三个函数。// 此处是 Rust 对三个 C 函数的申明:extern “C” { fn abs(num: c_int) -> c_int; fn sqrt(num: c_double) -> c_double; fn pow(num: c_double, power: c_double) -> c_double;}fn main() { let x: i32 = -123; // 每次调用都必须产生在一个unsafe区域内, 表明Rust对外部调用中可能存在的不平安行为不负责 println!("{x}的绝对值是: {}.", unsafe { abs(x) }); let n: f64 = 9.0; let p: f64 = 3.0; println!("{n}的{p}次方是: {}.", unsafe { pow(n, p) }); let mut y: f64 = 64.0; println!("{y}的平方根是: {}.", unsafe { sqrt(y) }); y = -3.14; println!("{y}的平方根是: {}.", unsafe { sqrt(y) }); //** NaN = NotaNumber(不是数字)}</code></pre><p>上述程序的输入是</p><pre><code>-123的绝对值是: 123.9的3次方是: 729.64的平方根是: 8.-3.14的平方根是: NaN.</code></pre><h2><code>FFI</code>调用自定义地位的<code>C</code>库</h2><p>我的项目构造如下</p><pre><code>.├── build.rs├── Cargo.lock├── Cargo.toml├── hello│ └── hello.c└── src └── main.rs</code></pre><p>编辑<code>hello/hello.c</code>文件</p><pre><code class=“c”>#include <stdio.h>void hello() { printf(“Hello, build script!!!!\n”);}</code></pre><p>在<code>Cargo.toml</code>退出构建时依赖</p><pre><code class=“toml”>[build-dependencies]cc = “1.0”</code></pre><p>批改<code>build.rs</code></p><pre><code class=“rust”>fn main() { // 示意在hello/hello.c文件产生批改的时候须要从新运行build脚本 println!(“cargo:rerun-if-changed=hello/hello.c”); let mut builder: cc::Build = cc::Build::new(); builder .file("./hello/hello.c") .compile(“hello”);}</code></pre><p>批改<code>src/main.rs</code></p><pre><code class=“rust”>// 应用extern申明hello是C外面的函数extern “C” { fn hello();}fn main() { // rust调用的时候须要应用unsafe包裹 unsafe { hello(); } println!(“Hello, world!”);}</code></pre><p>运行</p><pre><code class=“shell”>$ cargo runHello, build script!!!!Hello, world!</code></pre><h2><code>FFI</code>调用简单函数</h2><p>本示例蕴含构造体指针的传递</p><p>当初应用<code>C</code>库中的函数<code>asctime</code></p><p><code>header</code>文件地位是<code>/usr/include/time.h</code>,函数定义如下,通过传入一个<code>tm</code>类型的构造体指针,返回一个日期格局为<code>Day Mon dd hh:mm:ss yyyy\n</code>的字符串</p><pre><code class=“c”>/* Return a string of the form “Day Mon dd hh:mm:ss yyyy\n” that is the representation of TP in this format. /extern char asctime (const struct tm __tp) __THROW;</code></pre><p>因为波及到大量的模板代码和类型转换,须要应用<code>bindgen</code>工具从C语言的头文件生成<code>rust</code>代码放慢开发速度,缩小低级谬误,提高效率</p><p>工具由<code>rust</code>语言官网保护,地址</p><pre><code>https://github.com/rust-lang/rust-bindgen</code></pre><p><code>Debian/Ubuntu</code>系列的装置依赖</p><pre><code class=“shell”>$ sudo apt install llvm-dev libclang-dev clang</code></pre><p>其余零碎的装置依赖参考</p><pre><code>https://rust-lang.github.io/rust-bindgen/requirements.html</code></pre><p>装置命令行工具</p><pre><code class=“shell”>$ cargo install bindgen-cli</code></pre><p>比方当初转换<code>/usr/include/time.h</code>文件</p><p>在<code>rust</code>的我的项目根门路下执行命令</p><pre><code class=“shell”>$ bindgen /usr/include/time.h > src/mytime.rs</code></pre><p>之后比照<code>/usr/include/time.h</code>和<code>src/mytime.rs</code></p><p>查看<code>C</code>语言的原始代码,找到函数<code>asctime</code>和构造体<code>tm</code>的定义</p><pre><code class=“c”>/ 源码地位 /usr/include/time.h // Return a string of the form “Day Mon dd hh:mm:ss yyyy\n” that is the representation of TP in this format. /extern char asctime (const struct tm __tp) __THROW;/ 源码地位 /usr/include/x86_64-linux-gnu/bits/types/struct_tm.h尽管执行的命令是bindgen /usr/include/time.h > src/mytime.rs然而工具依然会主动扫描转换相干头文件定义的构造体类型// ISO C `broken-down time’ structure. /struct tm{ int tm_sec; / Seconds. [0-60] (1 leap second) / int tm_min; / Minutes. [0-59] / int tm_hour; / Hours. [0-23] / int tm_mday; / Day. [1-31] / int tm_mon; / Month. [0-11] / int tm_year; / Year - 1900. / int tm_wday; / Day of week. [0-6] / int tm_yday; / Days in year.[0-365] / int tm_isdst; / DST. [-1/0/1]/# ifdef __USE_MISC long int tm_gmtoff; / Seconds east of UTC. */ const char tm_zone; / Timezone abbreviation. /# else long int __tm_gmtoff; / Seconds east of UTC. */ const char __tm_zone; / Timezone abbreviation. */# endif};#endif</code></pre><p>查看<code>src/mytime.rs</code>文件外面的代码,找到<code>asctime</code>函数以及<code>tm</code>构造体的定义</p><pre><code class=“rust”>extern “C” { pub fn asctime(__tp: *const tm) -> *mut ::std::os::raw::c_char;}#[repr(C)]#[derive(Debug, Copy, Clone)]pub struct tm { pub tm_sec: ::std::os::raw::c_int, pub tm_min: ::std::os::raw::c_int, pub tm_hour: ::std::os::raw::c_int, pub tm_mday: ::std::os::raw::c_int, pub tm_mon: ::std::os::raw::c_int, pub tm_year: ::std::os::raw::c_int, pub tm_wday: ::std::os::raw::c_int, pub tm_yday: ::std::os::raw::c_int, pub tm_isdst: ::std::os::raw::c_int, pub tm_gmtoff: ::std::os::raw::c_long, pub tm_zone: *const ::std::os::raw::c_char,}</code></pre><p>批改<code>src/main.rs</code>文件内容</p><pre><code class=“rust”>use std::ffi::{c_char, CStr, CString};// mytime.rs作为main.rs的一个modmod mytime;fn main() { // 因为mytime::mm构造体外面的tm_zone字段是一个字符串指针 // 先创立一个字符串 let timezone = CString::new(“UTC”).unwrap(); // 从mytime中导入一个tm构造体,填写参数如下 let mut time_value = mytime::tm { tm_sec: 1, tm_min: 1, tm_hour: 1, tm_mday: 1, tm_mon: 1, tm_year: 1, tm_wday: 1, tm_yday: 1, tm_isdst: 1, tm_gmtoff: 1, // 此处转换为指针类型 tm_zone: timezone.as_ptr() }; unsafe { // 裸指针 let c_time_value_ptr = &mut time_value; // 获取一个c字符串指针 let asctime_result_ptr = mytime::asctime(c_time_value_ptr); // 从指针再转变为CStr类型 let c_str = CStr::from_ptr(asctime_result_ptr); // 最初再转变为rust的&str类型,返回的是一个Result类型,可能会产生utf-8编码谬误 println!("{:?}", c_str.to_str()); }}</code></pre><p>运行</p><pre><code class=“shell”>$ cargo runOk(“Mon Feb 1 01:01:01 1901\n”)</code></pre><p>因为<code>bindgen</code>是把整个<code>/usr/include/time.h</code>文件外面的所有函数和构造体都转换到<code>rust</code>中,所以编译<code>rust</code>的时候会产生很多的<code>function *** is never used, constant **** should have an upper case name</code>这种告警,这个是失常的</p><h2>参考浏览</h2><p>从 <code>Rust</code> 调用 <code>C</code> 库函数 - <code>linux cn</code></p><p><code>Rust</code>调用<code>C</code>程序的实现步骤</p><p><code>C</code> 规范库</p><p><code>rust</code>语言圣经 - <code>FFI</code></p></article> ...