diff --git a/csbindgen/src/builder.rs b/csbindgen/src/builder.rs index ba624a7..7d57fa5 100644 --- a/csbindgen/src/builder.rs +++ b/csbindgen/src/builder.rs @@ -1,3 +1,4 @@ +use crate::type_meta::RustType; use std::path::PathBuf; use std::{ error::Error, @@ -32,6 +33,8 @@ pub struct BindgenOptions { pub csharp_use_nint_types: bool, pub csharp_imported_namespaces: Vec, pub csharp_generate_const_filter: fn(const_name: &str) -> bool, + pub csharp_gen_safe_handle: Vec, + pub csharp_type_redirect: fn(rust_type: &RustType) -> RustType, } impl Default for Builder { @@ -57,6 +60,8 @@ impl Default for Builder { csharp_use_nint_types: true, csharp_imported_namespaces: vec![], csharp_generate_const_filter: |_| false, + csharp_gen_safe_handle: vec![], + csharp_type_redirect: |x| x.clone(), }, } } @@ -69,7 +74,9 @@ impl Builder { /// Add an input .rs file(such as generated from bindgen) to generate binding. pub fn input_bindgen_file>(mut self, input_bindgen_file: T) -> Builder { - self.options.input_bindgen_files.push(input_bindgen_file.as_ref().to_path_buf()); + self.options + .input_bindgen_files + .push(input_bindgen_file.as_ref().to_path_buf()); self } @@ -87,8 +94,6 @@ impl Builder { self } - - /// add original extern call type prefix to rust wrapper, /// `return {rust_method_type_path}::foo()` pub fn rust_method_type_path>(mut self, rust_method_type_path: T) -> Builder { @@ -110,6 +115,20 @@ impl Builder { self } + /// configure C# generate SafeHandle, + /// "using {csharp_namespace};" + pub fn csharp_gen_safe_handle>(mut self, class_name: T) -> Builder { + self.options.csharp_gen_safe_handle.push(class_name.into()); + self + } + + /// configure C# type redirect, + /// `[DllImport({csharp_dll_name})]` + pub fn csharp_type_redirect(mut self, func: fn(rust_type: &RustType) -> RustType) -> Builder { + self.options.csharp_type_redirect = func; + self + } + /// configure C# file namespace(default is `CsBindgen`), /// "namespace {csharp_namespace}" pub fn csharp_namespace>(mut self, csharp_namespace: T) -> Builder { @@ -197,12 +216,19 @@ impl Builder { /// configure C# generate const, default is false /// equivalent to csharp_generate_const_filter(|_| csharp_generate_const) #[deprecated(note = "User csharp_generate_const_filter instead")] - pub fn csharp_generate_const(mut self, csharp_generate_const: bool) -> Builder { - self.csharp_generate_const_filter(if csharp_generate_const { |_| true } else { |_| false }) + pub fn csharp_generate_const(self, csharp_generate_const: bool) -> Builder { + self.csharp_generate_const_filter(if csharp_generate_const { + |_| true + } else { + |_| false + }) } /// configure C# generate const filter, default `|_| false` - pub fn csharp_generate_const_filter(mut self, csharp_generate_const_filter: fn(const_name: &str) -> bool) -> Builder { + pub fn csharp_generate_const_filter( + mut self, + csharp_generate_const_filter: fn(const_name: &str) -> bool, + ) -> Builder { self.options.csharp_generate_const_filter = csharp_generate_const_filter; self } diff --git a/csbindgen/src/emitter.rs b/csbindgen/src/emitter.rs index f062b04..5540e2c 100644 --- a/csbindgen/src/emitter.rs +++ b/csbindgen/src/emitter.rs @@ -74,6 +74,178 @@ use ::std::os::raw::*; result } +// convert struct* to SafeHandle +pub fn emit_type_redirect(options: &BindgenOptions, rust_type: &RustType) -> RustType { + if let TypeKind::Pointer(_, inner) = &rust_type.type_kind { + if options + .csharp_gen_safe_handle + .iter() + .any(|name| name.to_string() == inner.type_name) + { + return RustType { + type_name: format!("{}Handle", inner.type_name), + type_kind: TypeKind::Normal, + }; + } + } + + (options.csharp_type_redirect)(&rust_type) +} + +// convert struct* to System.IntPtr +pub fn emit_type_to_intptr(options: &BindgenOptions, rust_type: &RustType) -> RustType { + if let TypeKind::Pointer(_, inner) = &rust_type.type_kind { + if options + .csharp_gen_safe_handle + .iter() + .any(|name| name.to_string() == inner.type_name) + { + return RustType { + type_name: "System.IntPtr".to_string(), + type_kind: TypeKind::Normal, + }; + } + } + + (options.csharp_type_redirect)(&rust_type) +} + +pub fn emit_csharp_safe_handle( + methods: &Vec, + aliases: &AliasMap, + options: &BindgenOptions, +) -> String { + let mut structs_string = String::new(); + + let class_name = &options.csharp_class_name; + let method_prefix = &options.csharp_method_prefix; + + for struct_name in &options.csharp_gen_safe_handle { + let struct_type = emit_type_redirect( + options, + &RustType { + type_name: struct_name.to_string(), + type_kind: TypeKind::Pointer( + PointerType::Box, + Box::new(RustType { + type_name: struct_name.to_string(), + type_kind: TypeKind::Normal, + }), + ), + }, + ); + + let convert_type = emit_type_redirect(options, &struct_type); + + //let name = (options.csharp_type_rename)(escape_name(&item.struct_name)); + let name = escape_name(&convert_type.type_name); + + structs_string.push_str_ln( + format!( + " + public partial class {name} : SafeHandle {{ + public {name}(System.IntPtr ptr) : base(ptr, true) {{ }} + public {name}() : base(IntPtr.Zero, true) {{ }} + public override bool IsInvalid {{ get {{ return this.handle == IntPtr.Zero; }} }}" + ) + .as_str(), + ); + + // generate methods + for item in methods { + let mut method_name = &item.method_name; + let method_name_temp: String; + if method_prefix.is_empty() { + method_name_temp = escape_name(method_name); + method_name = &method_name_temp; + } + + if item.parameters.len() < 1 { + continue; + } + + let first_parameter = &item.parameters[0]; + let first_parameter_type_name = + emit_type_redirect(options, &first_parameter.rust_type).type_name; + + if first_parameter_type_name != name { + continue; + } + + let return_type = match &item.return_type { + Some(x) => { + let redirect_type = emit_type_redirect(options, &x); + + redirect_type.to_csharp_string( + options, + aliases, + false, + method_name, + &"return".to_string(), + ) + } + None => "void".to_string(), + }; + + let mut parameters_str = String::new(); + let mut call_parameters_str = String::from("this.handle"); + + for idx in 1..item.parameters.len() { + let p = &item.parameters[idx]; + let redirect_type = emit_type_redirect(options, &p.rust_type); + + let type_name = + redirect_type.to_csharp_string(options, aliases, false, method_name, &p.name); + + if idx != 1 { + parameters_str += ", "; + } + + // delegate function + if let Some(_) = build_method_delegate_if_required( + &p.rust_type, + options, + aliases, + method_name, + &p.name, + ) { + parameters_str += format!("{class_name}.").as_str(); + } + + parameters_str += + format!("{} {}", type_name, escape_name(p.name.as_str())).as_str(); + + call_parameters_str += ", "; + call_parameters_str += escape_name(p.name.as_str()).as_str(); + } + + structs_string.push_str_ln( + format!( + " public unsafe {return_type} {method_prefix}{method_name}({parameters_str})" + ) + .as_str(), + ); + + structs_string.push_str(" {\n "); + if let Some(_) = &item.return_type { + structs_string.push_str("return "); + } + + structs_string.push_str_ln( + format!("{class_name}.{method_name}({call_parameters_str});").as_str(), + ); + + structs_string.push_str_ln(" }"); + + structs_string.push('\n'); + } + + structs_string.push_str_ln(" }"); + } + + structs_string +} + pub fn emit_csharp( methods: &Vec, aliases: &AliasMap, @@ -158,18 +330,32 @@ pub fn emit_csharp( }; let return_type = match &item.return_type { Some(x) => { - x.to_csharp_string(options, aliases, false, method_name, &"return".to_string()) + let redirect_type = emit_type_redirect(options, &x); + redirect_type.to_csharp_string( + options, + aliases, + false, + method_name, + &"return".to_string(), + ) } None => "void".to_string(), }; + let mut parameter_count = 0; let parameters = item .parameters .iter() .map(|p| { + let redirect_type = if parameter_count == 0 { + emit_type_to_intptr(options, &p.rust_type) + } else { + emit_type_redirect(options, &p.rust_type) + }; + + parameter_count += 1; let mut type_name = - p.rust_type - .to_csharp_string(options, aliases, false, method_name, &p.name); + redirect_type.to_csharp_string(options, aliases, false, method_name, &p.name); if type_name == "bool" { type_name = "[MarshalAs(UnmanagedType.U1)] bool".to_string(); } @@ -215,7 +401,8 @@ pub fn emit_csharp( structs_string.push_str_ln(" [FieldOffset(0)]"); } - let type_name = field.rust_type.to_csharp_string( + let redirect_type = (options.csharp_type_redirect)(&field.rust_type); + let type_name = redirect_type.to_csharp_string( options, aliases, true, @@ -268,6 +455,8 @@ pub fn emit_csharp( structs_string.push('\n'); } + let class_helper_string = emit_csharp_safe_handle(methods, aliases, options); + let mut enum_string = String::new(); for item in enums { let repr = match &item.repr { @@ -321,7 +510,7 @@ pub fn emit_csharp( } else { let value = if type_name == "float" { format!("{}f", item.value) - }else { + } else { item.value.to_string() }; @@ -367,6 +556,7 @@ namespace {namespace} {structs_string} {enum_string} +{class_helper_string} }} " ); diff --git a/csbindgen/src/lib.rs b/csbindgen/src/lib.rs index 036ffd0..8224d76 100644 --- a/csbindgen/src/lib.rs +++ b/csbindgen/src/lib.rs @@ -3,7 +3,7 @@ mod builder; mod emitter; mod field_map; mod parser; -mod type_meta; +pub mod type_meta; mod util; use alias_map::AliasMap; @@ -49,8 +49,7 @@ pub(crate) fn generate( collect_struct(&file_ast, &mut structs); collect_enum(&file_ast, &mut enums); - collect_const(&file_ast, &mut consts,options.csharp_generate_const_filter); - + collect_const(&file_ast, &mut consts, options.csharp_generate_const_filter); } // collect using_types diff --git a/csbindgen/src/type_meta.rs b/csbindgen/src/type_meta.rs index baa19a9..4b7f831 100644 --- a/csbindgen/src/type_meta.rs +++ b/csbindgen/src/type_meta.rs @@ -200,7 +200,13 @@ impl RustType { Generic(inner) => { emit_type_name(&mut sb); sb.push('<'); - sb.push_str(inner.iter().map(|t|t.to_rust_string(type_path).as_str().to_owned()).join(", ").as_str()); + sb.push_str( + inner + .iter() + .map(|t| t.to_rust_string(type_path).as_str().to_owned()) + .join(", ") + .as_str(), + ); sb.push('>'); } }; @@ -226,9 +232,9 @@ impl RustType { "i16" => "short", "i32" => "int", "i64" => "long", - "i128" => "Int128", // .NET 7 - "isize" if use_nint_types => "nint", // C# 9.0 - "isize" => "System.IntPtr", // C# 9.0 + "i128" => "Int128", // .NET 7 + "isize" if use_nint_types => "nint", // C# 9.0 + "isize" => "System.IntPtr", // C# 9.0 "u8" => "byte", "u16" => "ushort", "u32" => "uint", @@ -360,28 +366,35 @@ impl RustType { // function pointer can not annotate ? so emit inner only if self.type_name == "Option" { sb.push_str( - inner.first().unwrap() - .to_csharp_string( - options, - alias_map, - emit_from_struct, - method_name, - parameter_name, - ) - .as_str(), - ); - } else { - sb.push_str(type_csharp_string.as_str()); - sb.push('<'); - sb.push_str( - inner.iter().map(|x| - x.to_csharp_string( + inner + .first() + .unwrap() + .to_csharp_string( options, alias_map, emit_from_struct, method_name, parameter_name, - )).join(", ").as_str(), + ) + .as_str(), + ); + } else { + sb.push_str(type_csharp_string.as_str()); + sb.push('<'); + sb.push_str( + inner + .iter() + .map(|x| { + x.to_csharp_string( + options, + alias_map, + emit_from_struct, + method_name, + parameter_name, + ) + }) + .join(", ") + .as_str(), ); sb.push('>'); } @@ -496,7 +509,8 @@ pub fn build_method_delegate_if_required( .iter() .enumerate() .map(|(index, p)| { - let cs = p.rust_type.to_csharp_string( + let redirect_type = (options.csharp_type_redirect)(&p.rust_type); + let cs = redirect_type.to_csharp_string( options, alias_map, emit_from_struct, @@ -520,24 +534,25 @@ pub fn build_method_delegate_if_required( Some(delegate_code) } } - TypeKind::Generic(inner) => {let del = inner - .iter() - .filter_map(|x| { - build_method_delegate_if_required( - x, - options, - alias_map, - method_name, - parameter_name, - ) - }) - .join(", "); + TypeKind::Generic(inner) => { + let del = inner + .iter() + .filter_map(|x| { + build_method_delegate_if_required( + x, + options, + alias_map, + method_name, + parameter_name, + ) + }) + .join(", "); if del.is_empty() { None } else { Some(del) } - }, + } _ => None, } }