当main.rs和lib.rs共存时,无法链接C库

问题描述

基本设置

Rust通过FFI调用一个简单C函数,以及在build.rs中链接的静态库(Windows)。

// api.c

int increment(int value) {
    return value + 1;
}
// main.rs

extern {
    fn increment(value: i32) -> i32;
}

fn main() {
    let num = unsafe { increment(4) };
    println!("The incremented number is {}",num);
}
// build.rs

fn main() {
    println!("cargo:rustc-link-search=src/ffi");
    println!("cargo:rustc-link-lib=static=api");
}

具有以下目录结构:

Cargo.toml
Cargo.lock
build.rs
src
+-- main.rs
+-- ffi
    +-- api.c
    +-- api.lib

Cargo.toml除了板条箱名称,版本,作者和Rust版本外什么都没有。

到目前为止,这非常正常,并且按预期方式打印了“递增的数字为5”。


问题

现在,我添加一个 lib.rs文件,因此我可以将板条箱用作库和二进制文件

Cargo.toml
Cargo.lock
build.rs
src
+-- lib.rs     <-- NEW
+-- main.rs
+-- ffi
    +-- api.c
    +-- api.lib

我没有更改Cargo.toml。
仅此一项就使链接步骤失败(MSVC链接器):

错误LNK2019:函数“ _ZN16my_crate4main17h887bd80180495b7eE”中引用的未解析的外部符号“增量”

即使我在Cargo.toml中明确指定了库和二进制文件并使用cargo run --bin my_main运行,该错误仍然存​​在:

[lib]
name = "my_lib"
path = "src/lib.rs"

[[bin]]
name = "my_main"
path = "src/main.rs"

我还通过使它因恐慌而中止构建而确保在二进制情况下build.rs仍被执行。

我知道我可以通过拆分板条箱来解决问题,但我想了解发生了什么。所以:


为什么出现空白lib.rs会导致链接器失败?
有没有办法使它成功完成?

解决方法

在单个箱子中同时存在二进制文件和Rust库的情况下,Rust将二进制文件与Rust库本身静态链接起来,就好像该库是任何其他外部箱子(例如来自crates.io)一样。我假设是为了防止由于重复的符号而导致错误,或者只是为了避免代码膨胀或额外的工作,这似乎是在避免将任何外部工件直接链接到二进制文件,并且不会尝试确定所讨论的代码是否可用在进行此判断之前,请在lib.rs中输入。通常,这种情况在后台发生而不会引起您的注意(即,它正在对Rust标准库,系统标准库以及您所编译的所有内容的外部包装箱执行此操作),但是当您引入自定义FFI依赖项时,它就显而易见。

如果您将lib.rs更改为

extern {
    pub fn increment(value: i32) -> i32;
}

还有您的main.rs

fn main() {
    let num = unsafe { libname::increment(4) };
    println!("The incremented number is {}",num);
}

libnameCargo.toml中列出的库名,如果未指定,则为项目名,它将正确编译并运行。的确,无论您变得多么复杂,这都是遵循的。您可以将FFI函数深埋一百万个模块,并从任何地方包含它,但是如果您引用extern "C"函数的定义而不先经过主库的包装,则它将失败,并且会出现相同的错误。

我不知道是否有任何方法(在build.rs中使用 insist 来使Rust静态地针对两个不同的目标两次或者只是针对静态地链接库)。二进制文件本身,但是如果没有二进制文件,则该文件没有明显记载。

也就是说,在大多数情况下,这不太可能成为问题,因为如果您要使功能可用作库,那么无论如何,您很有可能会拥有包括FFI在内的通用代码。如果您具有仅在二进制文件中有意义的C依赖关系(例如,用于设备访问),则可能是一个问题,但是我认为这种情况非常罕见,如果这是您的特定情况,则迫使您使用工作区拆分板条箱是合理的