3 个版本

0.1.4 2023 年 12 月 7 日
0.1.1 2023 年 10 月 22 日
0.1.0 2023 年 10 月 22 日

#850 in Rust 模式

每月 34 次下载

MIT 许可证

270KB
5.5K SLoC

ferment

FFI 的语法树变形工具(工作进行中)

允许生成符合 FFI 的 rust 类型(结构、枚举、类型、函数)的等效物。

该项目是一个由多个 crate 组成的 rust-workspace

  1. ferment-interfaces: 提供从/到 FFI 兼容类型转换方法以及一些辅助函数和结构的 traits
  2. ferment-macro: 一个过程宏,它仅仅捕获基于 syn 的目标代码项。
  3. ferment-example: 提供 basic 示例。
  4. ferment-example-nested: 提供依赖于 fermented crate 的示例。
  5. ferment: 使用 syn crate 的强大功能对 FFI 兼容的语法树进行变形的工具。

过程宏由 2 个宏组成

  1. export - 用于结构/枚举/函数/类型
  2. 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