3个版本

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

过程宏 中排名第1143

每月下载量 29

MIT 许可证

4KB

ferment

FFI语法树变形工具(开发中)

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

该项目是一个由几个crate组成的rust工作空间

  1. ferment-interfaces: 提供从/到FFI兼容类型转换方法的一些特性和辅助函数和结构
  2. ferment-macro: 一个过程宏,它只捕获基于syn的项的目标代码。
  3. ferment-example: 提供基本示例。
  4. ferment-example-nested: 提供依赖于ferment 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)
        }
    }
}

示例

对于标记为导出的特性,例如这样

#[ferment_macro::export]
pub trait IHaveChainSettings {
    fn name(&self) -> String;
}

您还可以使用以逗号分隔的特名字符串的宏

#[ferment_macro::export(IHaveChainSettings)]
pub enum ChainType {
    MainNet,
    TestNet,
    DevNet(DevnetType)
}

这将公开特定类型的特性方法绑定

对于带有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 标记的特质

#[ferment_macro::export]
pub trait IHaveChainSettings { 
    // ..
}

将生成 vtable 和特质对象

#[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