当前位置: 首页 > news >正文

【CXX】5.2 extern “C++“

#[cxx::bridge]
mod ffi {
    extern "C++" {
        include!("path/to/header.h");
        include!("path/to/another.h");

        // 这里声明暴露给Rust的C++类型和函数
    }
}

extern "C++"部分是CXX桥接模块中用于声明暴露给Rust的C++类型和函数签名的部分,并指定包含相应C++声明的头文件路径。

一个桥接模块可以包含零个或多个extern "C++"块。

不透明的C++类型

在C++中定义的类型,可以通过间接方式暴露给Rust。

extern "C++" {
    type MyType;
    type MyOtherType;
}

例如,前面案例(BlobstoreClient)作为一个不透明的C++类型实现。BlobstoreClient在C++中创建,并通过UniquePtr返回给Rust。

可变性:与extern "Rust"类型和共享类型不同,extern "C++"类型不允许通过普通的可变引用&mut MyType跨FFI桥接传递。为了支持可变性,桥接必须使用Pin<&mut MyType>。这是为了防止像mem::swap这样的操作交换两个可变引用的内容,因为Rust没有关于底层对象大小的信息,也无法调用适当的C++移动构造函数。

线程安全性:请注意,CXX不会对你的extern "C++"类型的线程安全性做出任何假设。换句话说,CXX为你生成的Rust绑定MyType等不会自动实现Send和Sync。如果你确定你的C++类型满足Send和/或Sync的要求,并需要在Rust中利用这一点,你必须自己提供unsafe的标记trait实现。

/// C++实现的MyType是线程安全的。
unsafe impl Send for ffi::MyType {}
unsafe impl Sync for ffi::MyType {}

在这样做时要小心,因为如果你来自Rust背景,C++中的线程安全性可能非常难以评估。例如,教程中的BlobstoreClient类型并不是线程安全的,尽管它的实现中只做了一些完全无害的事情。并发调用tag成员函数会触发blobs映射上的数据竞争。

函数和成员函数

这部分主要遵循与extern "Rust"函数和方法相同的原则。特别是,任何带有self参数的签名都被解释为C++的非静态成员函数,并作为方法暴露给Rust。

程序员不需要保证他们输入的签名是准确的;这是不合理的。CXX会执行静态断言,确保签名与C++中声明的完全一致。相反,程序员只需要负责那些C++静态信息无法精确捕获的内容,即那些在C++代码中最多只能通过注释表示的内容(静态断言无法理解的内容):即C++函数从Rust调用是否安全。

安全性:extern "C++"块负责决定是否将每个签名暴露为安全调用或不安全调用。如果extern块包含至少一个安全调用的签名,则必须将其写为unsafe extern块,这表示块内容中做出了未经检查的安全性声明。

#[cxx::bridge]
mod ffi {
    unsafe extern "C++" {
        fn f();  // 安全调用
    }

    extern "C++" {
        unsafe fn g();  // 不安全调用
    }
}

生命周期

持有借用数据的C++类型可以通过带有泛型生命周期参数的extern类型在Rust中自然描述。例如,对于以下一对类型:

// header.h

class Resource;

class TypeContainingBorrow {
  TypeContainingBorrow(const Resource &res) : res(res) {}
  const Resource &res;
};

std::shared_ptr<TypeContainingBorrow> create(const Resource &res);

我们希望将其暴露给Rust为:

#[cxx::bridge]
mod ffi {
    unsafe extern "C++" {
        type Resource;
        type TypeContainingBorrow<'a>;

        fn create<'a>(res: &'a Resource) -> SharedPtr<TypeContainingBorrow<'a>>;

        // 或者使用生命周期省略:
        fn create(res: &Resource) -> SharedPtr<TypeContainingBorrow>;
    }
}

重用现有的绑定类型

extern "C++"类型支持一种语法,用于声明当前桥接模块外部已经存在正确的C++类型的Rust绑定。这避免了生成一个新的绑定,Rust的类型系统会认为该绑定与第一个绑定不可互换。

#[cxx::bridge(namespace = "path::to")]
mod ffi {
    extern "C++" {
        type MyType = crate::existing::MyType;
    }

    extern "Rust" {
        fn f(x: &MyType) -> usize;
    }
}

在这种情况下,CXX不会为C++的::path::to::MyType生成一个新的Rust类型ffi::MyType,而是会重用已经存在的crate::existing::MyType绑定来表达f的签名以及桥接模块中MyType的任何其他用途。

CXX通过生成基于crate::existing::MyType的ExternType实现的静态断言,安全地验证crate::existing::MyType实际上是正确的C++类型::path::to::MyType的绑定。ExternType是一个trait,CXX会为它生成的绑定自动实现,但也可以手动实现,如下所述。

ExternType服务于以下两个相关的用例。

安全地在多个桥接中统一外部类型的出现

在以下代码片段中,两个不同文件(可能是不同的crate)中的#[cxx::bridge]调用都包含涉及相同C++类型example::Demo的函数签名。如果两者都只包含type Demo;,那么两个宏扩展都会生成各自独立的Rust类型Demo,因此编译器不允许我们将file1::ffi::create_demo返回的Demo作为file2::ffi::take_ref_demo接受的Demo参数传递。相反,其中一个Demo被定义为另一个的外部类型别名,使它们在Rust中成为相同的类型。

// file1.rs
#[cxx::bridge(namespace = "example")]
pub mod ffi {
    unsafe extern "C++" {
        type Demo;

        fn create_demo() -> UniquePtr<Demo>;
    }
}

// file2.rs
#[cxx::bridge(namespace = "example")]
pub mod ffi {
    unsafe extern "C++" {
        type Demo = crate::file1::ffi::Demo;

        fn take_ref_demo(demo: &Demo);
    }
}

与bindgen生成或手写的不安全绑定集成

手写的ExternType实现使得可以将bindgen生成的数据结构插入为CXX生成的C++类型的定义。

通过编写unsafe ExternType实现,程序员断言type id中给出的C++命名空间和类型名称引用了一个与实现中的Self类型等价的C++类型。

mod folly_sys;  // bindgen生成的绑定

use cxx::{type_id, ExternType};

unsafe impl ExternType for folly_sys::StringPiece {
    type Id = type_id!("folly::StringPiece");
    type Kind = cxx::kind::Opaque;
}

#[cxx::bridge(namespace = "folly")]
pub mod ffi {
    unsafe extern "C++" {
        include!("rust_cxx_bindings.h");

        type StringPiece = crate::folly_sys::StringPiece;

        fn print_string_piece(s: &StringPiece);
    }
}

// 现在如果我们构造一个StringPiece或通过bindgen生成的签名获得一个,
// 我们能够将其传递给ffi::print_string_piece。
ExternType::Id关联类型编码了类型的C++命名空间和类型名称的类型级表示。它将始终使用cxx crate中暴露的type_id!宏定义。

ExternType::Kind关联类型将始终是cxx::kind::Opaque或cxx::kind::Trivial,用于标识C++类型是否可以通过Rust的移动语义安全地重定位。只有在C++类型的移动构造函数是平凡的且没有析构函数时,才能在Rust中按值持有和传递它。在CXX中,这些类型称为Trivial的extern "C++"类型,而具有非平凡移动行为或析构函数的类型必须被视为Opaque,并且只能通过引用或UniquePtr等间接方式在Rust中处理。

如果你认为你的C++类型确实可以在Rust中按值持有和移动,你可以指定:

type Kind = cxx::kind::Trivial;

这将使你能够按值将其传递给C++函数,按值返回它,并将其包含在你声明给cxx::bridge的结构体中。你关于C++类型平凡性的声明将通过生成的C++绑定中的static_assert进行检查。

显式的shim trait实现

这是一个较为小众的功能,但在需要时非常重要。

CXX对C++的std::unique_ptr和std::vector的支持建立在一组内部trait实现上,这些实现将UniquePtr和CxxVector的Rust API连接到C++编译器执行的模板实例化。

当在多个桥接模块中重用绑定类型时(如前一节所述),你可能会发现你的代码需要一些CXX没有决定生成的trait实现。

#[cxx::bridge]
mod ffi1 {
    extern "C++" {
        include!("path/to/header.h");

        type A;
        type B;

        // 正常:CXX看到UniquePtr<B>使用了同一桥接中定义的B类型,
        // 并自动生成与std::unique_ptr<B>对应的模板实例化。
        fn get_b() -> UniquePtr<B>;
    }
}

#[cxx::bridge]
mod ffi2 {
    extern "C++" {
        type A = crate::ffi1::A;

        // Rust trait错误:CXX处理此模块时无法看到上游库是否已经
        // 生成了与std::unique_ptr<A>对应的模板实例化,因此它不会
        // 在此处生成它们。如果上游库没有任何涉及UniquePtr<A>的签名,
        // 则需要在其中一个模块中显式请求模板实例化。
        fn get_a() -> UniquePtr<A>;
    }
}

你可以通过在定义A但不包含任何UniquePtr使用的桥接模块中编写impl UniquePtr {}来请求在Rust crate层次结构中的特定位置进行特定的模板实例化。

#[cxx::bridge]
mod ffi1 {
    extern "C++" {
        include!("path/to/header.h");

        type A;
        type B;

        fn get_b() -> UniquePtr<B>;
    }

    impl UniquePtr<A> {}  // 显式实例化
}

相关文章:

  • 4.2 使用说明:手册写作利器VNote的使用
  • 大白话html第十一章
  • I²C总线应用场景及1.8V与3.3V电压选择
  • Nano-GraphRAG复现——只使用Ollama,无需API Key
  • 质量属性场景描述
  • IO基础练习4
  • CogToolBlock和CogIDTool工具
  • ES时序数据库的性能优化
  • C++ Primer 拷贝、赋值与销毁
  • 如何改变怂怂懦弱的气质(2)
  • 记录一次利用条件索引优化接口性能的实践
  • golang并发编程如何学习
  • unsloth-llama3-8b.py 中文备注版
  • 汽车零部件厂如何选择最适合的安灯系统解决方案
  • ESLint 深度解析:原理、规则与插件开发实践
  • C# Unity 面向对象补全计划 之 索引器与迭代器
  • Spring AI 1.0.0-M6 快速开始(一)
  • MySQL批量生成建表语句
  • 解决CentOS 8.5被恶意扫描的问题
  • 美国国家航空航天局(NASA)的PUNCH任务
  • 第十一届世界雷达展开幕,尖端装备、“大国重器”集中亮相
  • 视频丨歼-10CE首次实战大放异彩
  • 河南一女子被医院强制带走治疗,官方通报:当值医生停职
  • 从近200件文物文献里,回望光华大学建校百年
  • 浙江省委金融办原副主任潘广恩被“双开”
  • 我使馆就中国公民和企业遭不公正待遇向菲方持续提出严正交涉