-
Notifications
You must be signed in to change notification settings - Fork 4
Description
Rust 有极强的跨平台性,对比 c/c++,又能解决野指针的问题,还能解决类型安全的问题。
所以,使用 Rust 编译出来的程序,可以以库的形式和其它程序进行连接,是不错的未来。
如果是编写 Nodejs 扩展,https://github.com/napi-rs/node-rs 是一个不错的开始。
如果是编写 wasm 项目,https://github.com/rustwasm/wasm-pack 是一个不错的开始。
本文仅仅从较低层面,讲解如何编译动态库,并在 c 和 rust 之间调用。
C 语言使用 Rust 函数
示例 c-app
第一步,配置 rust Cargo.tomal,使其输出 cdylib
[lib]
name = "hello"
path = "src/lib.rs"
crate-type = ["cdylib"]第二步,编写 rust 代码,使用 extern 声明导出的函数。使用 cargo build 编译产出库文件 libhello.so,位置在 target/debug 文件夹下。(仅用于测试,如果是用于生产,请带上 --release)
#[no_mangle]
pub extern "C" fn sum(a: i32, b: i32) -> i32 {
a + b
}第三步,编写 c 代码,引用 rust 写的功能函数
// hello.h
#ifndef HELLO_H_
#define HELLO_H_
// 声明 sum 来自外部库
extern int sum(int, int);
#endif
// hello.c
#include <stdio.h>
#include "hello.h"
int main(int argc, char const *argv[])
{
int result = sum(1, 2);
printf("result: %d\n", result);
return 0;
}第四步,编译 c 代码,链接 Rust 动态链接库
-l指定链接时需要的动态库,编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.a或.so来确定库-L依赖库搜索路径
gcc hello.c -o hello -lhello -L../target/debugRust 使用 C 函数
示例 c-lib
第一步,编写 C 代码
#include <stdio.h>
typedef void (*rust_callback)(int);
rust_callback cb;
int register_callback(rust_callback callback) {
printf("register_callback...\n");
cb = callback;
return 3;
}
void trigger_callback() {
cb(7); // Will call callback(7) in Rust.
}第二步,编译
# 动态链接
# -fPIC 和 -shared 可以编译出动态链接库
gcc -fPIC -shared -o libext.so ext.c
# 静态链接
gcc -o ext.o -c ext.c
# -c 不要编译为可执行文件,只编译为目标文件
ar -cvr libext.a ext.o第三步,编写 Rust 代码,引用 C 函数
extern fn callback(a: i32) {
println!("I'm called from C with value {0}", a);
}
// 使用 link 宏,引用 C 函数。ext 为编译出来的库名称(一般不写 lib 前缀)
// 可以通过指定 kind,告诉 rustc 是使用 动态链接库 还是 静态链接库
// 动态链接库 #[link(name = "ext")]
// 静态链接库 #[link(name = "ext", kind = "static")]
// https://doc.rust-lang.org/nomicon/ffi.html#linking
// 这里的 extern 类似于接口,定义内部函数的接口,Rust 代码调用时,需要提供接口的实现。
#[link(name = "ext")]
extern {
fn register_callback(cb: extern fn(i32)) -> i32;
fn trigger_callback();
}
fn main() {
unsafe {
let result = register_callback(callback);
println!("result from c: {}", result);
trigger_callback(); // Triggers the callback.
}
}第四步,编写 build.rs。需要注意的是:
对于链接动态链接库,只需要配置
println!(r"cargo:rustc-link-search=./c-lib");
对于静态链接库,需要再增加配置(或者在 rust 代码里面 link 指定 kind 为 static 即可)
https://doc.rust-lang.org/rustc/command-line-arguments.html#-l-link-the-generated-crate-to-a-native-library
println!(r"cargo:rustc-link-lib=static=ext");
fn main() {
// 制定搜索路径
println!(r"cargo:rustc-link-search=./c-lib");
// 如果是动态链接库,在 macOS 上,编译成功之后,需要配置 DYLD_LIBRARY_PATH
// export DYLD_LIBRARY_PATH=.:$DYLD_LIBRARY_PATH
// 此时执行可执行文件才能成功。否则会提示 dyld: Library not loaded
// 待确定:如果是静态链接库,可以配置如下参数。编译成功之后,可以直接执行
// println!(r"cargo:rustc-link-lib=static=ext");
// 有兴趣可以试试 crate cc https://crates.io/crates/cc
// --crate-type=cdylib --crate-name=ext ext.c
// Tell Cargo that if the given file changes, to rerun this build script.
// println!("cargo:rerun-if-changed=c-lib/ext.c");
// Use the `cc` crate to build a C file and statically link it.
// cc::Build::new()
// .file("src/hello.c")
// .compile("hello");
}其它文章参考
- How do I specify the linker path in Rust?
- C++静态库与动态库
- linux中把.c的文件编译成.so文件
- Static and dynamic C runtimes
- Linkage
- Build Scripts
- C side
第五步,执行。对于动态链接库,需要配置 DYLD_LIBRARY_PATH(macOs) / LD_LIBRARY_PATH(Linux) 才能正常执行。否则提示 dyld: Library not loaded。
另外,如果是静态编译,那么需要确保库所在位置只有静态库,不能有同名的动态库
cargo run
# 或者
cargo build
target/debug/xxrlib
如果是 rust 编译出来的库,则可以参考下面的例子。
使用 extern crate 导入。并在编译的时候,通过 --extern 参数指定依赖库的名称和地址
// rustc hello.rs --extern hello=../target/debug/libhello.rlib -o hello
extern crate hello;
fn main() {
let a = 1;
let b = 2;
let c = hello::sum(a, b);
println!("{a} + {b} = {c}");
}