无法在 C++ 项目中使用 CXX 链接 Rust 编写的库

use*_*967 8 c++ binding rust

我正在使用一个非常简单的项目来测试CXX,将 Rust 库链接到 C++ 可执行文件中。

我编写了一个foo() -> ()Rust 函数并尝试从 C++ 访问它,但链接器找不到它。

这是我所拥有的:

// lib.rs

#[cxx::bridge]
mod ffi {
    extern "Rust" {
        pub fn foo() -> ();
    }
}

pub fn foo() -> () {
    println!("foo")
}
Run Code Online (Sandbox Code Playgroud)
# Cargo.toml
[package]
name = "cpprust"
version = "0.1.0"
edition = "2021"

[lib]
name = "cpprust"
path = "src/lib.rs"
crate-type = ["staticlib", "rlib", "dylib"] # EDIT: this is incorrect, see note at the end of question

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
cxx = "1.0"
Run Code Online (Sandbox Code Playgroud)
// main.cpp

void foo(); // I tried including lib.rs.h but it was not generated!

int main() {
    foo();
}
Run Code Online (Sandbox Code Playgroud)

运行cargo build生成target\debug\libcpprust.so. 然后我尝试使用以下命令创建项目(编辑:g++命令不正确,请参阅问题末尾的注释):

g++ -L../target/debug/ -lcpprust -o cpprust main.cpp
/tmp/ccOA8kJy.o: In function `main':
main.cpp:(.text+0x5): undefined reference to `foo()'
collect2: error: ld returned 1 exit status
make: *** [Makefile:2: cpprust] Error 1
Run Code Online (Sandbox Code Playgroud)

这里有什么问题吗?

编辑:prog-fh 的很好的答案正确地指出我需要包含build.rsC++ 编译,即使没有 C++ 在板条箱内编译和访问。然而,即使在实现了他们的答案之后,我仍然收到相同的错误消息。事实证明,我还有另外两个问题:1)我的参数顺序g++不正确,而我pthread -l dl也需要。它应该是: g++ -o cpprust main.cpp -I ../target/cxxbridge -L../target/debug -lcpprust -pthread -l dl 2)我的Cargo.toml文件也生成"rlib", "dylib"库类型,但这不知何故也导致了上面的错误;staticlib仅当生成时才有效。

pro*_*-fh 8

考虑到本文档build.rs脚本应该生成lib.rs.h您尝试中缺少的内容。

\n

请注意,文档中的示例认为主程序来自 Rust,而 C++ 代码是扩展。\n在您的问题中,情况恰恰相反:您的主程序来自 C++,但由一些 Rust 代码扩展。

\n

这个答案由两部分组成:

\n
    \n
  • 一个与你的非常相似的最小示例(没有从 Rust 调用的 C++ 代码),
  • \n
  • 一个更完整的示例,其中 C++ 和 Rust 之间的双向交互(但主程序仍然在 C++ 端)。
  • \n
\n

编辑以回答评论中的后续问题

\n

正如下面第二个注释中所述build.rs,中选择的名称.compile("cpp_from_rust")将用于命名包含已编译的 C++ 代码的库(libcpp_from_rust.a例如)。\n然后 Rust 将使用该库来扩展 Rust 代码:libcpprust.a生成的主要目标由 Rust 包含libcpp_from_rust.a

\n

如果之前未提供 C++ 文件.compile()(如下面第一个最小示例所示),则此 C++ 库仅包含启用extern "Rust"从 C++ 访问的符号。

\n
$ nm ./target/debug/build/cpprust-28371278e6cda5e2/out/libcpp_from_rust.a\n\nlib.rs.o:\n                 U _GLOBAL_OFFSET_TABLE_\n0000000000000000 T _Z13rust_from_cppv\n                 U cxxbridge1$rust_from_cpp\n
Run Code Online (Sandbox Code Playgroud)\n

另一方面,您已经在文档中发现允许多次调用,.file()以便为 C++ 库提供来自各种源文件的更多代码。

\n

另一个问题是关于我们希望 Rust 生成的库类型。\n本文档枚举了 Rust 可以生成的各种二进制目标,特别是各种类型的库。\n因为在您最初的问题中,您希望主要可执行文件位于 C++ 端,这意味着 Rust 应该生成一个可以被视为系统库的库,而不是 Rust 特定的库,因为在生成可执行文件时 Rust 将不再涉及。\n在上述文档中,我们可以看到只有staticlibcdylib适合这种用途。

\n

在我的示例中,我选择staticlib是为了简单起见,但cdylib也可以使用。\n但是,它有点复杂,因为主库 ( libcpprust.so) 是动态生成的,Rust 不会插入 C++ 库 ( libcpp_from_rust.a) 进去; 因此,我们必须链接这个C++库,这不是很方便。

\n
g++ -std=c++17 -o cpp_program src/main.cpp \\\n    -I .. -I target/cxxbridge \\\n    -L target/debug -l cpprust \\\n    -L target/debug/build/cpprust-28371278e6cda5e2/out -l cpp_from_rust \\\n    -pthread -l dl\n
Run Code Online (Sandbox Code Playgroud)\n

当然,因为我们现在处理的是共享库,所以我们必须在运行时找到它。

\n
$ LD_LIBRARY_PATH=target/debug ./cpp_program\n
Run Code Online (Sandbox Code Playgroud)\n

我不知道其他一些类型的库 ( crate-type) 是否可以(偶然)与这个 C++ 主程序一起工作,但文档指出仅staticlibcdylib适合这种用法。

\n

最后,请注意,如果您使用crate-type = ["staticlib", "rlib", "dylib"] in Cargo.toml(如评论中所述),您将生成三个库:

\n
    \n
  • target/debug/libcpprust.astaticlib
  • \n
  • target/debug/libcpprust.rlibrlib
  • \n
  • target/debug/libcpprust.sodylib
  • \n
\n

不幸的是,当使用命令链接时g++ ... -l cpprust ...,链接器会更喜欢; 您将处于与上述相同的情况。.so.acdylib

\n
\n

最小示例的目录布局

\n
cpprust\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 Cargo.toml\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 build.rs\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 src\n    \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 lib.rs\n    \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 main.cpp\n
Run Code Online (Sandbox Code Playgroud)\n

Cargo.toml

\n
[package]\nname = "cpprust"\nversion = "0.1.0"\nedition = "2021"\n\n[lib]\ncrate-type = ["staticlib"]\n\n[dependencies]\ncxx = "1.0"\n\n[build-dependencies]\ncxx-build = "1.0"\n
Run Code Online (Sandbox Code Playgroud)\n

build.rs

\n
fn main() {\n    // This will consider the ffi part in lib.rs in order to\n    // generate lib.rs.h and lib.rs.cc\n    // minimal example: no C++ code to be called from Rust\n    cxx_build::bridge("src/lib.rs")\n        .compile("cpp_from_rust");\n}\n
Run Code Online (Sandbox Code Playgroud)\n

src/lib.rs

\n
#[cxx::bridge]\nmod ffi {\n    extern "Rust" {\n        fn rust_from_cpp() -> ();\n    }\n}\n\npub fn rust_from_cpp() -> () {\n    println!("called rust_from_cpp()");\n}\n
Run Code Online (Sandbox Code Playgroud)\n

src/main.cpp

\n
/*\n  Building this program happens outside of the cargo process.\n  We simply need to link against the Rust library and the\n  system libraries it depends upon\n\n  g++ -std=c++17 -o cpp_program src/main.cpp \\\n      -I .. -I target/cxxbridge \\\n      -L target/debug -l cpprust \\\n      -pthread -l dl\n*/\n\n// consider the ffi part of Rust code\n#include "cpprust/src/lib.rs.h"\n\n#include <iostream>\n\nint\nmain()\n{\n  std::cout << "starting from C++\\n";\n  rust_from_cpp();\n  std::cout << "finishing with C++\\n";\n  return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n

该命令将在 中cargo build生成静态库。\n构建主程序只需依赖常用命令,只要我们找到相关的头文件和库(请参阅代码中的注释)。libcpprust.atarget/debug

\n

请注意,主程序的 C++ 源代码位于src此处的目录中,但它也可以放在其他任何地方。

\n
\n

双向示例的目录布局

\n
cpprust\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 Cargo.toml\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 build.rs\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 src\n    \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 cpp_from_rust.cpp\n    \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 cpp_from_rust.hpp\n    \xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 lib.rs\n    \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 main.cpp\n
Run Code Online (Sandbox Code Playgroud)\n

我们刚刚添加了一对.hpp/.cpp文件。

\n

build.rs

\n
fn main() {\n    // This will consider the ffi part in lib.rs in order to\n    // generate lib.rs.h and lib.rs.cc\n    // The generated library (libcpp_from_rust.a) contains the code\n    // from cpp_from_rust.cpp and will be inserted into the generated\n    // Rust library (libcpprust.a).\n    cxx_build::bridge("src/lib.rs")\n        .file("src/cpp_from_rust.cpp")\n        .flag_if_supported("-std=c++17")\n        .compile("cpp_from_rust");\n}\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,这次构建过程实际上处理了一些从 Rust 调用的 C++ 代码(见下文)。

\n

src/lib.rs

\n
#[cxx::bridge]\nmod ffi {\n    extern "Rust" {\n        fn rust_from_cpp() -> ();\n    }\n    unsafe extern "C++" {\n        include!("cpprust/src/cpp_from_rust.hpp");\n        fn cpp_from_rust() -> ();\n    }\n}\n\npub fn rust_from_cpp() -> () {\n    println!("entering rust_from_cpp()");\n    ffi::cpp_from_rust();\n    println!("leaving rust_from_cpp()");\n}\n
Run Code Online (Sandbox Code Playgroud)\n

src/cpp_from_rust.hpp

\n
#ifndef CPP_FROM_RUST_HPP\n#define CPP_FROM_RUST_HPP\n\n// declare a usual C++ function (no Rust involved here)\nvoid\ncpp_from_rust();\n\n#endif // CPP_FROM_RUST_HPP\n
Run Code Online (Sandbox Code Playgroud)\n

src/cpp_from_rust.cpp

\n
#include "cpp_from_rust.hpp"\n\n#include <iostream>\n\n// define a usual C++ function (no Rust involved here)\nvoid\ncpp_from_rust()\n{\n  std::cout << "called " << __func__ << "()\\n";\n}\n
Run Code Online (Sandbox Code Playgroud)\n

Cargo.tomlsrc/main.cpp并且构建过程(cargo buildg++ ...)仍然与前面的示例相同。

\n