3 个版本
0.1.4 | 2023 年 12 月 7 日 |
---|---|
0.1.1 | 2023 年 10 月 22 日 |
0.1.0 | 2023 年 10 月 22 日 |
#850 in Rust 模式
每月 34 次下载
270KB
5.5K SLoC
ferment
FFI 的语法树变形工具(工作进行中)
允许生成符合 FFI 的 rust 类型(结构、枚举、类型、函数)的等效物。
该项目是一个由多个 crate 组成的 rust-workspace
ferment-interfaces
: 提供从/到 FFI 兼容类型转换方法以及一些辅助函数和结构的 traitsferment-macro
: 一个过程宏,它仅仅捕获基于 syn 的目标代码项。ferment-example
: 提供 basic 示例。ferment-example-nested
: 提供依赖于 fermented crate 的示例。ferment
: 使用syn
crate 的强大功能对 FFI 兼容的语法树进行变形的工具。
过程宏由 2 个宏组成
export
- 用于结构/枚举/函数/类型register
- 用于自定义定义的转换
用法
crate 尚未发布,因此可以在本地使用它进行示例
ferment-interfaces = { path = "../../ferment/ferment-interfaces" }
ferment-macro = { path = "../../ferment/ferment-macro" }
ferment = { path = "../../ferment/ferment" }
使用工具意味着使用 cbindgen
并具有以下配置
extern crate cbindgen;
fn main() {
extern crate cbindgen;
extern crate ferment;
use std::process::Command;
fn main() {
match ferment::Builder::new()
.with_mod_name("fermented")
.with_crates(vec![])
.generate() {
Ok(()) => match Command::new("cbindgen")
.args(&["--config", "cbindgen.toml", "-o", "target/example.h"])
.status() {
Ok(status) => println!("Bindings generated into target/example.h with status: {}", status),
Err(err) => panic!("Can't generate bindings: {}", err)
}
Err(err) => panic!("Can't create FFI expansion: {}", err)
}
}
}
示例
对于标记为导出的 traits,例如这样
#[ferment_macro::export]
pub trait IHaveChainSettings {
fn name(&self) -> String;
}
您还可以使用以逗号分隔的 trait 名称的宏
#[ferment_macro::export(IHaveChainSettings)]
pub enum ChainType {
MainNet,
TestNet,
DevNet(DevnetType)
}
这将公开特定类型的 trait 方法的绑定
对于使用 ferment::export
标记的结构
#[derive(Clone)]
#[ferment_macro::export]
pub struct LLMQSnapshot {
pub member_list: Vec<u8>,
pub skip_list: Vec<i32>,
pub skip_list_mode: LLMQSnapshotSkipMode,
pub option_vec: Option<Vec<u8>>,
}
将生成以下代码,具有 FFI 兼容的字段和相应的从/到转换
#[doc = "FFI-representation of the "crate::model::snapshot::LLMQSnapshot""]
#[repr(C)]
#[derive(Clone)]
#[allow(non_camel_case_types)]
pub struct LLMQSnapshot {
pub member_list: *mut crate::fermented::generics::Vec_u8,
pub skip_list: *mut crate::fermented::generics::Vec_i32,
pub skip_list_mode: *mut crate::fermented::types::model::snapshot::LLMQSnapshotSkipMode,
pub option_vec: *mut crate::fermented::generics::Vec_u8,
}
impl ferment_interfaces::FFIConversion<crate::model::snapshot::LLMQSnapshot> for LLMQSnapshot {
unsafe fn ffi_from_const(ffi: *const LLMQSnapshot) -> crate::model::snapshot::LLMQSnapshot {
let ffi_ref = &*ffi;
crate::model::snapshot::LLMQSnapshot {
member_list: ferment_interfaces::FFIConversion::ffi_from(ffi_ref.member_list),
skip_list: ferment_interfaces::FFIConversion::ffi_from(ffi_ref.skip_list),
skip_list_mode: ferment_interfaces::FFIConversion::ffi_from(ffi_ref.skip_list_mode),
option_vec: ferment_interfaces::FFIConversion::ffi_from_opt(ffi_ref.option_vec),
}
}
unsafe fn ffi_to_const(obj: crate::model::snapshot::LLMQSnapshot) -> *const LLMQSnapshot {
ferment_interfaces::boxed(LLMQSnapshot {
member_list: ferment_interfaces::FFIConversion::ffi_to(obj.member_list),
skip_list: ferment_interfaces::FFIConversion::ffi_to(obj.skip_list),
skip_list_mode: ferment_interfaces::FFIConversion::ffi_to(obj.skip_list_mode),
option_vec: match obj.option_vec {
Some(vec) => ferment_interfaces::FFIConversion::ffi_to(vec),
None => std::ptr::null_mut(),
},
})
}
unsafe fn destroy(ffi: *mut LLMQSnapshot) {
ferment_interfaces::unbox_any(ffi);
}
}
impl Drop for LLMQSnapshot {
fn drop(&mut self) {
unsafe {
let ffi_ref = self;
ferment_interfaces::unbox_any(ffi_ref.member_list);
ferment_interfaces::unbox_any(ffi_ref.skip_list);
<crate::fermented::types::model::snapshot::LLMQSnapshotSkipMode as ferment_interfaces::FFIConversion<crate::model::snapshot::LLMQSnapshotSkipMode>>::
destroy(ffi_ref.skip_list_mode);
if !ffi_ref.option_vec.is_null() {
ferment_interfaces::unbox_any(ffi_ref.option_vec);
};
}
}
}
#[doc = "# Safety"]
#[allow(non_snake_case)]
#[no_mangle]
pub unsafe extern "C" fn LLMQSnapshot_ctor(
member_list: *mut crate::fermented::generics::Vec_u8,
skip_list: *mut crate::fermented::generics::Vec_i32,
skip_list_mode: *mut crate::fermented::types::model::snapshot::LLMQSnapshotSkipMode,
option_vec: *mut crate::fermented::generics::Vec_u8)
-> *mut LLMQSnapshot {
ferment_interfaces::boxed(LLMQSnapshot {
member_list,
skip_list,
skip_list_mode,
option_vec,
})
}
#[doc = "# Safety"]
#[allow(non_snake_case)]
#[no_mangle]
pub unsafe extern "C" fn LLMQSnapshot_destroy(ffi: *mut LLMQSnapshot) {
ferment_interfaces::unbox_any(ffi);
}
对于使用 export
标记的函数
#[ferment_macro::export]
pub fn address_with_script_pubkey(script: Vec<u8>) -> Option<String> {
Some(format_args!("{0:?}", script).to_string())
}
将生成以下代码
#[doc = "FFI-representation of the "address_with_script_pubkey""]
#[doc = "# Safety"]
#[no_mangle]
pub unsafe extern "C" fn ffi_address_with_script_pubkey(script: *mut crate::fermented::generics::Vec_u8) -> *mut std::os::raw::c_char {
let conversion = ferment_interfaces::FFIConversion::ffi_from(script);
let obj = crate::example::address::address_with_script_pubkey(conversion);
ferment_interfaces::FFIConversion::ffi_to_opt(obj)
}
对于使用 export
标记的类型别名
#[ferment::export]
pub type HashID = [u8; 32];
将在 crate::fermented::types::*
中生成以下代码,具有类似的转换和绑定
#[repr(C)]
#[derive(Clone, Debug)]
pub struct HashID(*mut [u8; 32]);
对于使用 export
标记的 traits
#[ferment_macro::export]
pub trait IHaveChainSettings {
// ..
}
将生成 vtable 和 trait obj
#[repr(C)]
#[derive(Clone)]
#[allow(non_camel_case_types)]
pub struct IHaveChainSettings_VTable {
// ..
}
#[repr(C)]
#[derive(Clone)]
#[allow(non_camel_case_types)]
pub struct IHaveChainSettings_TraitObject {
pub object: *const (),
pub vtable: *const IHaveChainSettings_VTable,
}
并为其实现者生成绑定,如下所示
#[doc = "# Safety"]
#[allow(non_snake_case)]
#[no_mangle]
pub extern "C" fn ChainType_as_IHaveChainSettings_TraitObject(
obj: *const crate::chain::common::chain_type::ChainType)
-> IHaveChainSettings_TraitObject {
IHaveChainSettings_TraitObject {
object: obj as *const (),
vtable: &ChainType_IHaveChainSettings_VTable,
}
}
#[doc = "# Safety"]
#[allow(non_snake_case)]
#[no_mangle]
pub unsafe extern "C" fn ChainType_as_IHaveChainSettings_TraitObject_destroy(obj: IHaveChainSettings_TraitObject) {
ferment_interfaces::unbox_any(obj.object as *mut crate::chain::common::chain_type::ChainType);
}
使用此代码,cbindgen 将能够生成绑定
struct IHaveChainSettings_TraitObject ChainType_as_IHaveChainSettings_TraitObject(const struct ChainType *obj);
void ChainType_as_IHaveChainSettings_TraitObject_destroy(struct IHaveChainSettings_TraitObject obj);
当前限制
- 我们应该使用宏定义来标记所有涉及出口的结构。
- 处理类型别名存在一些困难。因此,如果可能的话,应避免使用它们。因为,为了确保它可以被处理,必须将其包裹在一个未命名的结构体中。这在大多数情况下,比直接使用它使用的类型效率低。也就是说,
pub type KeyID = u32
变为pub struct KeyID_FFI(u32)
。将来会提供支持。
泛型混淆规则
转换遵循一些混淆规则,并为ffi结构提供名称。翻译名称的示例
Vec<u8>
->Vec_u8
Vec<u32>
->Vec_u32
Vec<Vec<u32>>
->Vec_Vec_u32
BTreeMap<HashID, Vec<u32>>
->std_collections_Map_keys_crate_HashID_values_Vec_u32
BTreeMap<HashID, Vec<u32>>
->std_collections_Map_keys_u32_values_Vec_u32
BTreeMap<HashID, BTreeMap<HashID, Vec<u32>>>
->std_collections_Map_keys_crate_HashID_values_std_collections_Map_keys_crate_HashID_values_Vec_u32
- 等等
然后宏实现了这些结构所需的转换。例如,对于 BTreeMap<HashID, Vec<HashID>>
#[repr(C)]
#[derive(Clone)]
#[allow(non_camel_case_types)]
pub struct std_collections_Map_keys_crate_nested_HashID_values_Vec_crate_nested_HashID {
pub count: usize,
pub keys: *mut *mut crate::fermented::types::nested::HashID,
pub values: *mut *mut crate::fermented::generics::Vec_crate_nested_HashID,
}
impl ferment_interfaces::FFIConversion<std::collections::BTreeMap<crate::nested::HashID, Vec<crate::nested::HashID>>>
for std_collections_Map_keys_crate_nested_HashID_values_Vec_crate_nested_HashID {
unsafe fn ffi_from_const(
ffi: *const std_collections_Map_keys_crate_nested_HashID_values_Vec_crate_nested_HashID)
-> std::collections::BTreeMap<crate::nested::HashID, Vec<crate::nested::HashID>> {
let ffi_ref = &*ffi;
ferment_interfaces::from_complex_map(ffi_ref.count, ffi_ref.keys, ffi_ref.values)
}
unsafe fn ffi_to_const(
obj: std::collections::BTreeMap<crate::nested::HashID, Vec<crate::nested::HashID>>)
-> *const std_collections_Map_keys_crate_nested_HashID_values_Vec_crate_nested_HashID {
ferment_interfaces::boxed(Self {
count: obj.len(),
keys: ferment_interfaces::to_complex_vec(obj.keys().cloned()),
values: ferment_interfaces::to_complex_vec(obj.values().cloned()),
})
}
unsafe fn destroy(ffi: *mut std_collections_Map_keys_crate_nested_HashID_values_Vec_crate_nested_HashID) {
ferment_interfaces::unbox_any(ffi);
}
}
impl Drop for std_collections_Map_keys_crate_nested_HashID_values_Vec_crate_nested_HashID {
fn drop(&mut self) {
unsafe {
ferment_interfaces::unbox_any_vec_ptr(self.keys, self.count);
ferment_interfaces::unbox_any_vec_ptr(self.values, self.count);
}
}
}
最终生成的代码放置在配置中指定的文件中,如下所示
pub mod types {
// package relationships are inherited
// so type like crate::some_module::SomeStruct will be expanded like this:
pub mod some_module {
pub struct SomeStruct {
// ...
}
}
}
pub mod generics {
// We expand generic types separately here to avoid duplication
#[allow(non_camel_case_types)]
pub struct std_collections_Map_keys_crate_nested_HashID_values_Vec_crate_nested_HashID {
// ..
}
}
手动转换支持
- 我们可以使用
[ferment_macro::register(SomeFFIIncompatibleStructOrWhatever)]
- 它允许我们手动为类型创建自定义转换。
- 对于像rust std库或任何其他第三方crate中的类型这样的不可发酵代码来说,这尤其重要。
- 示例
依赖关系
~1.5MB
~35K SLoC