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

C library cannot be linked when main.rs and lib.rs coexist

基本设置

通过 FFI 从 Rust 调用的简单 C 函数,以及 link 在 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 除了 crate 名称、版本、作者和 Rust 版本之外什么都没有。

到目前为止,这一切正常,并且按预期打印了“增加的数字为 5”。


问题

现在我添加一个 lib.rs 文件,这样我就可以将我的箱子用作库和二进制文件:

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

我不改Cargo.toml.
仅此一项就使 link 步骤失败 (MSVC linker):

error LNK2019: Unresolved external symbol "increment" referenced in function "_ZN16my_crate4main17h887bd80180495b7eE"

即使我使用 cargo run --bin my_main:

在 Cargo.toml 和 运行 中明确指定存在库和二进制文件,错误仍然存​​在
[lib]
name = "my_lib"
path = "src/lib.rs"

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

我还确保 build.rs 仍然在二进制情况下执行,让它恐慌以中止构建。

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


为什么空的 lib.rs 会导致 linker 失败?
有没有办法让它在一个箱子里成功?

在一个箱子中同时存在二进制文件和 Rust 库的情况下,Rust 将二进制文件静态链接到 Rust 库本身,就好像该库是任何其他外部箱子一样(例如来自 crates.io) .我假设是为了防止由于重复符号而导致的错误,或者可能只是为了避免代码膨胀或额外的工作,它似乎避免将任何外部工件直接链接到二进制文件,并且不会努力确定相关代码是否可用在 lib.rs 做出这个判断之前。通常这会在你不注意的情况下发生在后台(即它正在对 Rust 标准库、你的系统标准库和外部 crate 以及你编译的所有内容执行此操作),但是当你引入自定义 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 或其他方面)坚持 Rust 静态链接一个库两次针对两个不同的目标,或者仅针对二进制文件本身,但如果存在,则没有很好的记录或明显的。

就是说,在大多数情况下这不太可能成为问题,因为如果您要使您的功能可用作库,那么您很可能会在其中包含通用代码,包括 FFI。如果您有仅在二进制文件中有意义的 C 依赖项(例如,用于设备访问),这可能是一个问题,但我认为这种情况很少见,如果这是您的具体情况,强制您使用工作区拆分板条箱是合理的.