INTERACTIVE LAB

rustVIP4 个可运行示例0 次浏览 更新于 2026-09-28

Rust:动态库、静态库与 FFI

学习 Rust 生成与调用动态库、静态库,以及通过 FFI 调用 C/系统库。

一、前言

rust中多了很多类型的库,比如前面章节中我们提到基本的bin与lib这两种crate类型库。

如果你在命令行执行下列语句:

rustc --help

那么你将能找到这样的内容:

--crate-type [bin|lib|rlib|dylib|cdylib|staticlib|proc-macro]

上面的rustc是rust中用来编译代码的工具,当我们运行cargo build命令的时候,该命令底层会使用rustc去构建源码、编译为可执行文件。

比如你只有一个main.rs文件,那么可以这样执行:rustc main.rs,将该文件编译为可执行文件。

然后我们可以看到,在rust中crate的类型是非常多的:

  • bin:二进制可执行文件,必须要有main函数作为入口
  • lib:它是一种代理名称,指代后续所有类型的库,但其默认指代的是rlib
  • rlib:rust专用的静态中间库格式,其用于纯rust项目库互相调用,比如bin中调用lib的crate内部函数,也是我们用的最多的一个类型。
  • dylib:动态库,会生成相应的特定于系统的动态库文件,比如linux上为.so,MacOs上为.dylib,Windows上为.dll,但这类动态库只能被rust写的程序调用。
  • cdylib:c语言规范的动态库,也会生成.so、.dylib、.dll等特定于系统的动态库文件,但其会符合C语言规范,可以被其它语言调用,比如C/C++。
  • staticlib:将编译为静态库文件,比如linux与mac上的.a文件,又或者windows上的.lib文件。
  • proc-macro:专用于导出过程宏的库

二、库生成

下一步,我们来使用一下上面这些列举出来的类型库。

bin类型就不用多说了,我们直接来到后面的lib类型,首先用下面这条命令生成一个lib:

cargo new testlib --lib

此时该项目将为默认的rlib类型库,比如当我们构建它时:

image.png

就会生成rlib文件,这种文件不需要我们理会,因为这是rust特定的库格式文件,我们一般只需要在其它项目中引入该项目源码进行使用,至于编译链接库的过程则全部交由编译器去处理。

然后下一步我们来看一下其它类型库的编译方式,其实很简单:

image.png 你只需要添加一个配置项即可完成编译。

其它类型也是类似的,将这里的crate-type的值改为对应类型即可,非常简单。

三、库使用

对于rlib与dylib这两种类型的crate,都无需我们操心,因为其使用方法都是基于源码形式使用的。

比如先我有另外一个hello-world的bin类型crate:

image.png

并直接用相对路径将该dylib类型的crate引入,此时该库中默认公开了一个add的函数,那么我们就可以直接使用:

image.png

此时编译下面的bin类型crate,就会发现,其编译后的目录中会多出dll库:

image.png

此后如果你想要运行exe文件,那么就必须将这个dll文件也带着,否则就会运行不起来。

但对于cdylib与staticlib库来说,就不能像上面那样使用了。

首先是cdylib,也就是生成之后得到的dll或者其它系统的动态库文件,也当然也包括一些c/c++语言编写生成的动态库文件。

如果你想要让你的cdylib与staticlib库函数能够正常被其它语言调用,就一定要像下面这样写导出函数:

rust
待运行⌘↵
#[no_mangle]
pub extern "C" fn add(left: u64, right: u64) -> u64 {
    left + right
}
编辑仅保存在本页,刷新后恢复

其中extern "C"代表导出为C格式的函数,no_mangle代表告诉rust编译器,不要去更改、修饰函数名,防止后面找不到这个函数。

此时我们需要添加一个crate来加载使用动态库中的函数:

cargo add libloading

使用方式如下:

image.png 由于从外部加载是一个非常不安全的行为,因此需要用unsafe块将其包裹起来。

注意此时这个testlib.dll库文件,是直接复制过来放在当前项目的根目录下的。

然后调用其上的get方法,传入函数名,就可以去查找函数了。

同时你需要在其返回值上、著名该函数的类型,比如这里的类型写的是:

代码引用ffi-export
第 1–4 行
1#[no_mangle]
2pub extern "C" fn add(left: u64, right: u64) -> u64 {
3    left + right
4}
只读片段,不影响运行示例

意思就是这是一个不安全的、C编译格式的、两个参数与一个返回值均为u64类型的函数。

但实测前面的定语即使不加也不会出错,也就是可以简写为:

代码引用ffi-export
第 1–4 行
1#[no_mangle]
2pub extern "C" fn add(left: u64, right: u64) -> u64 {
3    left + right
4}
只读片段,不影响运行示例

加载完成后,其返回值就是其加载得到的函数,直接调用即可。

除了动态库外,还有静态库,其使用起来相对更简单一些:

image.png

也无需其它库,直接添加一个编译指令即可:

rust
待运行⌘↵
#[cfg(any())]
#[link(name = "testlib", kind = "static")]
unsafe extern "C" {
    fn external_add(left: u64, right: u64) -> u64;
}

extern "C" fn add(left: u64, right: u64) -> u64 {
    left + right
}

fn main() {
    let result = add(2, 3);
    println!("Rust 中定义的 C ABI 函数:{result}");
}
编辑仅保存在本页,刷新后恢复
代码引用ffi-static
第 1–3 行
1#[cfg(any())]
2#[link(name = "testlib", kind = "static")]
3unsafe extern "C" {
只读片段,不影响运行示例

注意这里静态库同样也是直接放在项目根目录下的。

然后声明要导入的函数,也就是函数类型,注意外层同样要用extern "C"进行包裹。

由于外部导入函数是不安全的行为,所以使用的时候必须要添加unsafe块,然后在其内使用即可。

四、总结

虽然rust中crate的类型很多,但实际上总的来看,只有两种类型,一是bin、二是lib。

其中lib为一个统称,其默认行为是rlib,也就是rust专用的库格式,但可以自行修改为其它类型的库格式。

rlib与dylib的使用方法是结合rust源码使用的,并不推荐给其它语言使用,两者之间的唯一区别就是dylib为动态库形式,而rlib为静态库形式。

该实验为 VIP 专享内容,
请开通后继续阅读后续 70% 并运行完整示例~