Rust 调用 C/Rust 生成的动态库

在始终是 C/C++ 有着更优越性能的情况下,因而之前介绍过多种 其他不同的语言如何加载使用 C/C++ 写的动态库,有 Go, Python, Java 和 C#。在学习 Rust 之时也有类似的需求。本文的做法是要用到第三方库 libloading,这里将参考官方的例子。

先来创建一个动态库,使用和 Go 调用 C 写的动态库完整例子(Linux版) 一文中相同的例子,add.c 代码内容如下

在 Linux 中使用如下命令编译出 libadd.so 动态库文件

$ gcc -fPIC -shared -o libadd.so add.c

在 Windows 或 Mac OS X 平台可启动一个 Docker 容器,然后在容器中执行上面的命令生成 Linux 下用的 libadd.so 文件。做法是

$ docker run -it -v $(pwd):/work -w /work rust:1.78-buster bash            # 然后在容器中执行
root@a208ba393783:/work# gcc -fPIC -shared -o libadd.so add.c

其实本文完全可以在 Mac OS X 下进行,只要 Mac 下也安装了 gcc,不过在 Mac 下动态库的扩展名是  *.dylib。上面选择 Docker 镜像 rust:1.78 有一举两得之效,它既有 Rust 环境,又有 gcc 8.3.0 编译器。

接下来是用 cargo 创建一个 Rust 项目,命令是

$ cargo new shared-library

然后添加依赖 libloading, 运行命令

$ cd shared-library
$ cargo add libloading

假如希望生成的执行文件更精致,那么要在 Cargo.toml 文件中加上 strip = true, 如此最终的 Cargo.toml 文件内容如下

我们直接编辑 Rust 项目 shared-library 的 main.rs 程序代码,内容如下

留意上面代码中如何加载动态库,映射动态库中的函数,Rust 与 C 之间类型的转换,并且所有与动态库的交互要放到 unsafe 块中

现在再次回到 Docker 容器

$ docker run -it -v $(pwd):/work -w /work rust:1.78-buster bash            # 然后在容器中执行
root@b122a91ea255:/work# cargo build --release                  # 会生成 target/release/shared-library 可执行文件
root@b122a91ea255:/work# target/release/shared-library ./libadd.so  # 假设  libadd.so 在 /work 目录
Result: example string 5

如果执行 target/release/shared-library 时指定的 libadd.so 是错误的,或者是在当前目录中但未写成 ./libadd.so 的格式,会报告找不到 libadd.so 的错误,比如在容器中执行的是

root@b122a91ea255:/work# target/release/shared-library libadd.so   # 下面是报错信息
thread 'main' panicked at src/main.rs:12:41:
called Result::unwrap() on an Err value: DlOpen { desc: "libadd.so: cannot open shared object file: No such file or directory" }
note: run with RUST_BACKTRACE=1 environment variable to display a backtrace

Rust 加载调用 Rust 写的动态库

前面描述的是如何在 Rust 中加载调用 C/C++ 写的动态库,如果是用 Rust 写的动态库,然后用 Rust 加载调用就更简单了。看起来有些多此一举,实际也有可能出现这样的类似于出口转内销的操作。

创建一个动态库项目

$ cargo new add --lib

然后编译加生成的 src/lib.rs 文件,内容如下

在生成的 add/Cargo.toml 文件中加入

指示 cargo build 要生成动态库文件,这里的值永远写成 "dylib",会在不同的平台下生成不同扩展名的动态库文件,如 Windows 的  *.dll, Linux 下是 *.so, Mac OS X 中是 *.dylib。

接着编译

$ cargo build    # 将会生成 target/debug/libadd.so 文件

由于是用 Rust 写成的动态库,所以在 Rust 中加载该动态库调用其中的方法时类型就好处理多了。我们还是重用之前的 shared-library 项目,把 main.rs 的内容修改为

进到 shared-library 项目所在的目录,编译

$ cargo build    # 会生成 target/debug/shared-library

最后执行

$ /work/target/debug/shared-library /work/add/debug/libadd.so
$ Result: example string 5

动态库与调用方有着一致的类型,所以无需进行类型的转换,方便许多。

本文链接 https://yanbin.blog/rust-call-c-rust-shared-library/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments