创作不易,方便的话点点关注,谢谢
文章结尾有最新热度的文章,感兴趣的可以去看看。
本文是经过严格查阅相关权威文献和资料,形成的专业的可靠的内容。全文数据都有据可依,可回溯。特别申明:数据和资料已获得授权。本文内容,不涉及任何偏颇观点,用中立态度客观事实描述事情本身
文章有点长(2400字阅读时长:5分),期望您能坚持看完,并有所收获。
导读
Interop到c++是通过经典的沙漏方法完成的。我们将Rust库连接到C,并创建可以安全地使用C接口的c++类型。这与Python等其他语言的interop工作方式相同。
我们需要一种方法让C++拥有那些Rust对象来调用以Rust对象为参数的Rust函数。我们必须创建Rust对象并将指针泄露给C++代码以实现这一点。我们还在Rust中包含了可以销毁这些对象的函数,给定一个指向它们的指针。我们可以使用一个C++类,其成员是指向Rust对象的不透明指针,使用其析构函数方法来释放它们。这是必要的一个重要原因是分配器和释放器成对出现。使用C++释放器销毁Rust对象或反之是无效的。
在我们必须从Rust库类型创建C++库类型的情况下,例如从nalgebra::geometry::Isometry3创建Eigen::Isometry3d,我们必须复制底层数据而不是共享内存。这是因为,在C++中,我们不能扩展库类型以使用不同的释放器来处理底层内存的销毁。
在Rust同质变换类型nalgebra::geometry::Isometry3的特定情况下,底层数据是由单个16个双精度浮点数数组表示的4x4矩阵。固定大小的数组是我们可以在FFI边界上传递的东西。我们将利用这一点来避免额外的复制或分配。
与C++构建系统集成的担忧
由于我工作中的C++代码使用CMake,我将链接到一个示例,展示如何使这个C++项目可以被其他CMake项目使用。
我将把我的项目分成两个Rust crate(包)以便于代码布局。
-
• robot_joint - 我想从C++使用的Rust库(你所拥有的)
-
• robot_joint-cpp - C++互操作层(你想编写的)
自定义不透明类型
给定这个Rust结构和工厂函数,我们必须创建一个C接口。
pub structJoint{
name:String,
parent_link_to_joint_origin:Isometry3<f64>,
}
implJoint{
pubfnnew()->Self;
}
在robot_joint-cpp中,我创建了一个包含这些细节的lib.rs。
use robot_joint::Joint;
#[no_mangle]
extern"C"fnrobot_joint_new()->*mutJoint{
Box::into_raw(Box::new(Joint::new()))
}
#[no_mangle]
extern"C"fnrobot_joint_free(joint:*mutJoint){
unsafe{
drop(Box::from_raw(joint));
}
}
每个函数都需要#[no_mangle]属性来关闭Rust名称修饰,extern "C"来给函数C调用约定。Box::into_raw(Box::new()是一种在堆上创建Rust对象并泄露指针的技术。最后,drop(Box::from_raw)是一种将指针转换回Box类型并销毁它的方法。
接下来,我们创建一个C++头文件robot_joint.hpp。
#pragma once
#include <memory>
namespace robot_joint::rust {
/// 对Rust对象的指针使用的不透明类型的前置声明。
structJoint;
}// namespace robot_joint::rust
extern"C"{
extern void robot_joint_free(robot_joint::rust::Joint*);
}
/// 从函数模板参数创建自定义删除器。
template<auto fn>
structdeleter_from_fn{
template<typename T>
constexpr void operator()(T* arg) const {
fn(arg);
}
};
namespace robot_joint {
/// 对robot_joint对象的不可移动句柄(生活在Rust一侧)。
classJoint{
public:
Joint()noexcept;
~Joint()noexcept=default;
Joint(Joint&& other)noexcept=default;
Joint&operator=(Joint&& other)noexcept=default;
private:
std::unique_ptr<rust::Joint, deleter_from_fn<robot_joint_free>> robot_joint_;
};
} // namespace robot_joint
C++提供了一种特殊的指针类型,如果我们将删除器指定为模板参数,它将为我们处理Rust对象的清理。为了设置对Rust析构函数的调用,我们必须在头文件中有一个extern定义。由于unique_ptr是不可移动的,复制构造和赋值被禁用。这个C++类现在尽可能安全。
#include "robot_joint.hpp"
extern"C"{
extern robot_joint::rust::Joint* robot_joint_new();
}
namespace robot_joint {
Joint::Joint()noexcept
: joint_{robot_joint_new()}{
}
} // namespace robot_joint
在这里,我们创建了我们的C++接口的源文件。我们再次使用extern "C"来使我们的C++代码能够调用从Rust创建joint对象的C函数。
最后,最具挑战性的部分是使其与CMake项目兼容。我写了一篇关于该主题的后续博客文章。
一等库类型
记住,我说我采取手动方法,因为我想要一个在C++一侧具有Eigen类型的接口。以下是如何实现这一点的简单示例。假设我们有这个Rust函数在我们的Joint类型上。
impl Joint {
pub fn calculate_transform(&self, variables: &[f64]) -> Isometry3<f64>;
}
我们想要创建一个这样的C++接口。
class Joint {
public:
Eigen::Isometry3d calculate_transform(const Eigen::VectorXd& variables);
};
首先,我们必须为这个函数创建Rust FFI接口。
use std::ffi::{c_double, c_uint};
#[repr(C)]
structMat4d{
data:[c_double;16],
}
#[no_mangle]
extern"C"fnrobot_joint_calculate_transform(
joint:*constJoint,
variables:*const c_double,
size: c_uint,
)->Mat4d{
unsafe{
letjoint= joint.as_ref().expect("Invalid pointer to Joint");
letvariables= std::slice::from_raw_parts(variables, size asusize);
lettransform= joint.calculate_transform(variables);
Mat4d{
data: transform.to_matrix().as_slice().try_into().unwrap(),
}
}
}
Rust标准库中的ffi模块提供了我们需要的C类型参数。在调用rust calculate_transform之前,我们必须首先从参数构建Rust类型。
有趣的是,我们利用了一个未记录的事实,即在ffi中可以使用瘦指针。一个大小已知的切片是一个在运行时不存储大小的瘦指针。我们可以通过将其放入一个结构体并设置内存表示为C来按值返回一个大小已知的切片。
然后,我们可以编写一个调用C函数的C++函数。
struct Mat4d{
double data[16];
};
extern"C"{
externstructMat4drobot_joint_calculate_transform(
const robot_joint::rust::Joint*,constdouble*,unsignedint);
}
namespace robot_joint {
Eigen::Isometry3d Joint::calculate_transform(const Eigen::VectorXd& variables)
{
constauto rust_isometry =robot_joint_calculate_transform(
joint_, variables.data(), variables.size());
Eigen::Isometry3d transform;
transform.matrix()=Eigen::Map<Eigen::Matrix4d>(rust_isometry.data);
return transform;
}
} // namespace robot_joint
从robot_joint_calculate_transform返回的Rust Mat4d类型包含一个十六个双精度浮点数的固定大小数组。我们可以使用这个数组类型转换一个4x4的Eigen矩阵,并将其赋值给Isometry3d,然后返回它。
结论
构建一个创造优秀的C++和Rust接口的桥梁比许多人想象的要简单。你可能会在说服你的C++同事让你用Rust编写代码方面遇到更多麻烦,而不是进行互操作。
没有测试的代码应该被认为是破损的。为了信任所有这些不安全的C++和Rust代码,我们应该编写测试来锻炼所有代码路径,并使用sanitizers运行它们。在将来的帖子中,我将向你展示如何使用优秀的C++ Catch2库来测试你的C++绑定,并使用地址和未定义行为sanitizers。我写了一篇关于CMake集成的后续文章来解释如何做到这一点。
最新热门文章推荐:
国外Python程序员分享:如何用Python构建一个多代理AI应用
国外CUDA程序员分享:2024年GPU编程CUDA C++(从环境安装到进阶技巧)
国外Python程序员分享:2024年NumPy高性能计算库(高级技巧)
外国人眼中的卢湖川:从大连理工到全球舞台,他的科研成果震撼世界!
外国人眼中的张祥雨:交大90后男神博士,3年看1800篇论文,还入选福布斯精英榜
参考文献:《图片来源网络》《Rust/C++ Interop Part 1 - Just the Basics》
本文使用 文章同步助手 同步