无法在 C++ 项目中使用 CXX link Rust 编写的库
Failing to use CXX to link Rust-written library in C++ project
我正在用一个非常简单的项目 CXX 测试 link 一个 Rust 库到 C++ 可执行文件中。
我写了一个 foo() -> ()
Rust 函数并尝试从 C++ 访问它但是 linker 没有找到它。
这是我的:
// lib.rs
#[cxx::bridge]
mod ffi {
extern "Rust" {
pub fn foo() -> ();
}
}
pub fn foo() -> () {
println!("foo")
}
# 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"
// main.cpp
void foo(); // I tried including lib.rs.h but it was not generated!
int main() {
foo();
}
运行 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
这里有什么问题?
编辑:prog-fh 的出色回答正确地指出我需要将 build.rs
包含在 C++ 编译中,即使没有 C++ 在板条箱中编译和访问.但是,即使在实施了他们的回答之后,我仍然收到相同的错误消息。事实证明我还有另外两个问题:1) 我对 g++
的参数的 order 不正确,我也需要 pthread -l dl
。它应该是:
g++ -o cpprust main.cpp -I ../target/cxxbridge -L../target/debug -lcpprust -pthread -l dl
2) 我的 Cargo.toml
文件也生成了 "rlib", "dylib"
库类型,但不知何故也会导致上述错误;它仅在生成 staticlib
时有效。
考虑到 this documentation,build.rs
脚本应该生成您尝试中丢失的 lib.rs.h
。
注意文档中的示例认为主程序来自Rust,C++代码是一个扩展。
在你的问题中,情况恰恰相反:你的主程序来自 C++,但由一些 Rust 代码扩展。
这个答案由两部分组成:
- 一个与你的非常相似的最小示例(没有要从 Rust 调用的 C++ 代码),
- 一个更完整的C++和Rust双向交互的例子(但是主程序还是在C++端)
编辑以回答评论中的后续问题
如下面第二个 build.rs
的评论所述,.compile("cpp_from_rust")
中选择的名称将用于命名包含已编译 C++ 代码的库(例如 libcpp_from_rust.a
) .
然后 Rust 将使用该库来扩展 Rust 代码:Rust 生成的 libcpprust.a
主要目标包含 libcpp_from_rust.a
.
如果在 .compile()
之前没有提供 C++ 文件(如下面第一个最小示例),此 C++ 库仅包含启用从 C++ 进行 extern "Rust"
访问的符号。
$ nm ./target/debug/build/cpprust-28371278e6cda5e2/out/libcpp_from_rust.a
lib.rs.o:
U _GLOBAL_OFFSET_TABLE_
0000000000000000 T _Z13rust_from_cppv
U cxxbridge1$rust_from_cpp
另一方面,您已经在文档中发现允许多次调用 .file()
以便为 C++ 库提供来自各种源文件的更多代码。
另一个问题是关于我们希望 Rust 生成的库类型。
This documentation 列举了 Rust 可以生成的各种二进制目标,尤其是各种库。
由于在您最初的问题中您希望主要可执行文件位于 C++ 端,这意味着 Rust 应该生成一个可以被视为 system 库的库,而不是特定于 Rust 的库,因为在生成可执行文件时将不再涉及 Rust。
在上述文档中,我们可以看到只有 staticlib
和 cdylib
适合这种用法。
在我的示例中,为了简单起见,我选择了staticlib
,但也可以使用cdylib
。
然而,它有点复杂,因为主库 (libcpprust.so
) 是动态生成的,Rust 不会将 C++ 库 (libcpp_from_rust.a
) 插入其中;因此,我们不得不link反对这个C++库,这不是很方便。
g++ -std=c++17 -o cpp_program src/main.cpp \
-I .. -I target/cxxbridge \
-L target/debug -l cpprust \
-L target/debug/build/cpprust-28371278e6cda5e2/out -l cpp_from_rust \
-pthread -l dl
当然,因为我们现在处理的是共享库,所以我们必须在运行时找到它。
$ LD_LIBRARY_PATH=target/debug ./cpp_program
我不知道某些其他类型的库 (crate-type
) 是否可以(偶然地)与此 C++ 主程序一起工作,但文档指出只有 staticlib
和 cdylib
用于此用途。
最后,请注意,如果您在 Cargo.toml
中使用 crate-type = ["staticlib", "rlib", "dylib"]
(如您评论中所述),您将生成三个库:
target/debug/libcpprust.a
来自 staticlib
,
target/debug/libcpprust.rlib
来自 rlib
,
target/debug/libcpprust.so
来自 dylib
.
不幸的是,当 link 使用命令 g++ ... -l cpprust ...
时,link 用户将 更喜欢 .so
.a
;您将处于与上述 cdylib
相同的情况。
最小示例的目录布局
cpprust
├── Cargo.toml
├── build.rs
└── src
├── lib.rs
└── main.cpp
Cargo.toml
[package]
name = "cpprust"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["staticlib"]
[dependencies]
cxx = "1.0"
[build-dependencies]
cxx-build = "1.0"
build.rs
fn main() {
// This will consider the ffi part in lib.rs in order to
// generate lib.rs.h and lib.rs.cc
// minimal example: no C++ code to be called from Rust
cxx_build::bridge("src/lib.rs")
.compile("cpp_from_rust");
}
src/lib.rs
#[cxx::bridge]
mod ffi {
extern "Rust" {
fn rust_from_cpp() -> ();
}
}
pub fn rust_from_cpp() -> () {
println!("called rust_from_cpp()");
}
src/main.cpp
/*
Building this program happens outside of the cargo process.
We simply need to link against the Rust library and the
system libraries it depends upon
g++ -std=c++17 -o cpp_program src/main.cpp \
-I .. -I target/cxxbridge \
-L target/debug -l cpprust \
-pthread -l dl
*/
// consider the ffi part of Rust code
#include "cpprust/src/lib.rs.h"
#include <iostream>
int
main()
{
std::cout << "starting from C++\n";
rust_from_cpp();
std::cout << "finishing with C++\n";
return 0;
}
cargo build
命令会在target/debug
中生成libcpprust.a
个静态库。
构建主程序只需依赖常用的命令,前提是我们找到相关的头文件和库(见代码中的注释)。
请注意,主程序的 C++ 源代码位于此处的 src
目录中,但它可以放在其他任何地方。
双向示例的目录布局
cpprust
├── Cargo.toml
├── build.rs
└── src
├── cpp_from_rust.cpp
├── cpp_from_rust.hpp
├── lib.rs
└── main.cpp
我们刚刚添加了一对 .hpp
/.cpp
个文件。
build.rs
fn main() {
// This will consider the ffi part in lib.rs in order to
// generate lib.rs.h and lib.rs.cc
// The generated library (libcpp_from_rust.a) contains the code
// from cpp_from_rust.cpp and will be inserted into the generated
// Rust library (libcpprust.a).
cxx_build::bridge("src/lib.rs")
.file("src/cpp_from_rust.cpp")
.flag_if_supported("-std=c++17")
.compile("cpp_from_rust");
}
请注意,这次构建过程实际上处理了一些要从 Rust 调用的 C++ 代码(见下文)。
src/lib.rs
#[cxx::bridge]
mod ffi {
extern "Rust" {
fn rust_from_cpp() -> ();
}
unsafe extern "C++" {
include!("cpprust/src/cpp_from_rust.hpp");
fn cpp_from_rust() -> ();
}
}
pub fn rust_from_cpp() -> () {
println!("entering rust_from_cpp()");
ffi::cpp_from_rust();
println!("leaving rust_from_cpp()");
}
src/cpp_from_rust.hpp
#ifndef CPP_FROM_RUST_HPP
#define CPP_FROM_RUST_HPP
// declare a usual C++ function (no Rust involved here)
void
cpp_from_rust();
#endif // CPP_FROM_RUST_HPP
src/cpp_from_rust.cpp
#include "cpp_from_rust.hpp"
#include <iostream>
// define a usual C++ function (no Rust involved here)
void
cpp_from_rust()
{
std::cout << "called " << __func__ << "()\n";
}
Cargo.toml
、src/main.cpp
和构建过程(cargo build
、g++ ...
)仍然与前面的示例相同。
我正在用一个非常简单的项目 CXX 测试 link 一个 Rust 库到 C++ 可执行文件中。
我写了一个 foo() -> ()
Rust 函数并尝试从 C++ 访问它但是 linker 没有找到它。
这是我的:
// lib.rs
#[cxx::bridge]
mod ffi {
extern "Rust" {
pub fn foo() -> ();
}
}
pub fn foo() -> () {
println!("foo")
}
# 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"
// main.cpp
void foo(); // I tried including lib.rs.h but it was not generated!
int main() {
foo();
}
运行 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
这里有什么问题?
编辑:prog-fh 的出色回答正确地指出我需要将 build.rs
包含在 C++ 编译中,即使没有 C++ 在板条箱中编译和访问.但是,即使在实施了他们的回答之后,我仍然收到相同的错误消息。事实证明我还有另外两个问题:1) 我对 g++
的参数的 order 不正确,我也需要 pthread -l dl
。它应该是:
g++ -o cpprust main.cpp -I ../target/cxxbridge -L../target/debug -lcpprust -pthread -l dl
2) 我的 Cargo.toml
文件也生成了 "rlib", "dylib"
库类型,但不知何故也会导致上述错误;它仅在生成 staticlib
时有效。
考虑到 this documentation,build.rs
脚本应该生成您尝试中丢失的 lib.rs.h
。
注意文档中的示例认为主程序来自Rust,C++代码是一个扩展。 在你的问题中,情况恰恰相反:你的主程序来自 C++,但由一些 Rust 代码扩展。
这个答案由两部分组成:
- 一个与你的非常相似的最小示例(没有要从 Rust 调用的 C++ 代码),
- 一个更完整的C++和Rust双向交互的例子(但是主程序还是在C++端)
编辑以回答评论中的后续问题
如下面第二个 build.rs
的评论所述,.compile("cpp_from_rust")
中选择的名称将用于命名包含已编译 C++ 代码的库(例如 libcpp_from_rust.a
) .
然后 Rust 将使用该库来扩展 Rust 代码:Rust 生成的 libcpprust.a
主要目标包含 libcpp_from_rust.a
.
如果在 .compile()
之前没有提供 C++ 文件(如下面第一个最小示例),此 C++ 库仅包含启用从 C++ 进行 extern "Rust"
访问的符号。
$ nm ./target/debug/build/cpprust-28371278e6cda5e2/out/libcpp_from_rust.a
lib.rs.o:
U _GLOBAL_OFFSET_TABLE_
0000000000000000 T _Z13rust_from_cppv
U cxxbridge1$rust_from_cpp
另一方面,您已经在文档中发现允许多次调用 .file()
以便为 C++ 库提供来自各种源文件的更多代码。
另一个问题是关于我们希望 Rust 生成的库类型。
This documentation 列举了 Rust 可以生成的各种二进制目标,尤其是各种库。
由于在您最初的问题中您希望主要可执行文件位于 C++ 端,这意味着 Rust 应该生成一个可以被视为 system 库的库,而不是特定于 Rust 的库,因为在生成可执行文件时将不再涉及 Rust。
在上述文档中,我们可以看到只有 staticlib
和 cdylib
适合这种用法。
在我的示例中,为了简单起见,我选择了staticlib
,但也可以使用cdylib
。
然而,它有点复杂,因为主库 (libcpprust.so
) 是动态生成的,Rust 不会将 C++ 库 (libcpp_from_rust.a
) 插入其中;因此,我们不得不link反对这个C++库,这不是很方便。
g++ -std=c++17 -o cpp_program src/main.cpp \
-I .. -I target/cxxbridge \
-L target/debug -l cpprust \
-L target/debug/build/cpprust-28371278e6cda5e2/out -l cpp_from_rust \
-pthread -l dl
当然,因为我们现在处理的是共享库,所以我们必须在运行时找到它。
$ LD_LIBRARY_PATH=target/debug ./cpp_program
我不知道某些其他类型的库 (crate-type
) 是否可以(偶然地)与此 C++ 主程序一起工作,但文档指出只有 staticlib
和 cdylib
用于此用途。
最后,请注意,如果您在 Cargo.toml
中使用 crate-type = ["staticlib", "rlib", "dylib"]
(如您评论中所述),您将生成三个库:
target/debug/libcpprust.a
来自staticlib
,target/debug/libcpprust.rlib
来自rlib
,target/debug/libcpprust.so
来自dylib
.
不幸的是,当 link 使用命令 g++ ... -l cpprust ...
时,link 用户将 更喜欢 .so
.a
;您将处于与上述 cdylib
相同的情况。
最小示例的目录布局
cpprust
├── Cargo.toml
├── build.rs
└── src
├── lib.rs
└── main.cpp
Cargo.toml
[package]
name = "cpprust"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["staticlib"]
[dependencies]
cxx = "1.0"
[build-dependencies]
cxx-build = "1.0"
build.rs
fn main() {
// This will consider the ffi part in lib.rs in order to
// generate lib.rs.h and lib.rs.cc
// minimal example: no C++ code to be called from Rust
cxx_build::bridge("src/lib.rs")
.compile("cpp_from_rust");
}
src/lib.rs
#[cxx::bridge]
mod ffi {
extern "Rust" {
fn rust_from_cpp() -> ();
}
}
pub fn rust_from_cpp() -> () {
println!("called rust_from_cpp()");
}
src/main.cpp
/*
Building this program happens outside of the cargo process.
We simply need to link against the Rust library and the
system libraries it depends upon
g++ -std=c++17 -o cpp_program src/main.cpp \
-I .. -I target/cxxbridge \
-L target/debug -l cpprust \
-pthread -l dl
*/
// consider the ffi part of Rust code
#include "cpprust/src/lib.rs.h"
#include <iostream>
int
main()
{
std::cout << "starting from C++\n";
rust_from_cpp();
std::cout << "finishing with C++\n";
return 0;
}
cargo build
命令会在target/debug
中生成libcpprust.a
个静态库。
构建主程序只需依赖常用的命令,前提是我们找到相关的头文件和库(见代码中的注释)。
请注意,主程序的 C++ 源代码位于此处的 src
目录中,但它可以放在其他任何地方。
双向示例的目录布局
cpprust
├── Cargo.toml
├── build.rs
└── src
├── cpp_from_rust.cpp
├── cpp_from_rust.hpp
├── lib.rs
└── main.cpp
我们刚刚添加了一对 .hpp
/.cpp
个文件。
build.rs
fn main() {
// This will consider the ffi part in lib.rs in order to
// generate lib.rs.h and lib.rs.cc
// The generated library (libcpp_from_rust.a) contains the code
// from cpp_from_rust.cpp and will be inserted into the generated
// Rust library (libcpprust.a).
cxx_build::bridge("src/lib.rs")
.file("src/cpp_from_rust.cpp")
.flag_if_supported("-std=c++17")
.compile("cpp_from_rust");
}
请注意,这次构建过程实际上处理了一些要从 Rust 调用的 C++ 代码(见下文)。
src/lib.rs
#[cxx::bridge]
mod ffi {
extern "Rust" {
fn rust_from_cpp() -> ();
}
unsafe extern "C++" {
include!("cpprust/src/cpp_from_rust.hpp");
fn cpp_from_rust() -> ();
}
}
pub fn rust_from_cpp() -> () {
println!("entering rust_from_cpp()");
ffi::cpp_from_rust();
println!("leaving rust_from_cpp()");
}
src/cpp_from_rust.hpp
#ifndef CPP_FROM_RUST_HPP
#define CPP_FROM_RUST_HPP
// declare a usual C++ function (no Rust involved here)
void
cpp_from_rust();
#endif // CPP_FROM_RUST_HPP
src/cpp_from_rust.cpp
#include "cpp_from_rust.hpp"
#include <iostream>
// define a usual C++ function (no Rust involved here)
void
cpp_from_rust()
{
std::cout << "called " << __func__ << "()\n";
}
Cargo.toml
、src/main.cpp
和构建过程(cargo build
、g++ ...
)仍然与前面的示例相同。