From e4b25c3d733449a10d84a7dd048146aab9f36ab7 Mon Sep 17 00:00:00 2001 From: neuecc Date: Mon, 17 Apr 2023 20:01:20 +0900 Subject: [PATCH] ReadMe 1.7.0 multi input_bindgen_file support bitflags crate support tuple struct support unit struct new csharp_disable_emit_dll_name option GroupedNativeMethods code generator --- .../GroupedNativeMethodsGenerator.cs | 27 +-- README.md | 164 ++++++++++++++++-- csbindgen-tests/Cargo.toml | 1 + csbindgen-tests/build.rs | 1 - csbindgen-tests/src/lib.rs | 86 ++++----- csbindgen/src/parser.rs | 13 +- dotnet-sandbox/NativeMethods.cs | 26 ++- dotnet-sandbox/Program.cs | 14 +- 8 files changed, 255 insertions(+), 77 deletions(-) diff --git a/GroupedNativeMethodsGenerator/GroupedNativeMethodsGenerator.cs b/GroupedNativeMethodsGenerator/GroupedNativeMethodsGenerator.cs index 17f1fa1..1bfdf7a 100644 --- a/GroupedNativeMethodsGenerator/GroupedNativeMethodsGenerator.cs +++ b/GroupedNativeMethodsGenerator/GroupedNativeMethodsGenerator.cs @@ -141,13 +141,19 @@ using System.Runtime.InteropServices; static string ConvertMethodName(string typeName, string methodName, string removePrefix, string removeSuffix, bool removeUntilTypeName, bool fixMethodName) { + if (!fixMethodName) return methodName; + if (removeUntilTypeName) { var match = methodName.IndexOf(typeName); if (match != -1) { - methodName = methodName.Substring(match + typeName.Length); - goto FINAL; + var substringMethodName = methodName.Substring(match + typeName.Length); + if (substringMethodName.Trim(' ', '_') != "") + { + methodName = substringMethodName; + goto FINAL; + } } } @@ -163,18 +169,15 @@ using System.Runtime.InteropServices; methodName = Regex.Replace(methodName, $"{Regex.Escape(removeSuffix)}$", ""); } - methodName = methodName.Trim('_'); + methodName = methodName.Trim('_', ' '); - if (fixMethodName) + var split = methodName.Split('_'); + methodName = string.Concat(split.Select(x => { - var split = methodName.Split('_'); - methodName = string.Concat(split.Select(x => - { - if (x.Length == 0) return x; - if (x.Length == 1) return char.ToUpper(x[0]).ToString(); - return char.ToUpper(x[0]) + x.Substring(1); - })); - } + if (x.Length == 0) return x; + if (x.Length == 1) return char.ToUpper(x[0]).ToString(); + return char.ToUpper(x[0]) + x.Substring(1); + })); return methodName; } diff --git a/README.md b/README.md index b7c83ff..661208c 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Install on `Cargo.toml` as `build-dependencies` and set up `bindgen::Builder` on ```toml [build-dependencies] -csbindgen = "1.6.0" +csbindgen = "1.7.0" ``` ### Rust to C#. @@ -163,6 +163,7 @@ csbindgen::Builder::default() .csharp_entry_point_prefix("") // optional, default: "" .csharp_method_prefix("") // optional, default: "" .csharp_use_function_pointer(true) // optional, default: true + .csharp_disable_emit_dll_name(false) // optional, default: false .csharp_dll_name_if("UNITY_IOS && !UNITY_EDITOR", "__Internal") // optional, default: "" .generate_csharp_file("../dotnet-sandbox/NativeMethods.cs") // required .unwrap(); @@ -192,7 +193,9 @@ namespace {csharp_namespace} `csharp_dll_name_if` is optional. If specified, `#if` allows two DllName to be specified, which is useful if the name must be `__Internal` at iOS build. -`input_extern_file` allows mulitple call, if you need to add dependent struct, use this. +`csharp_disable_emit_dll_name` is optional, if set to true then don't emit `const string __DllName`. It is useful for generate same class-name from different builder. + +`input_extern_file` and `input_bindgen_file` allow mulitple call, if you need to add dependent struct, use this. ```rust csbindgen::Builder::default() @@ -341,6 +344,56 @@ internal static unsafe partial class NativeMethods If Unity, configure Platform settings in each native library's inspector. +Grouping Extension Methods +--- +In an object-oriented style, it is common to create methods that take a pointer to a state (this) as their first argument. With csbindgen, you can group these methods using extension methods by specifying a Source Generator on the C# side. + +Install csbindgen from NuGet, and specify [GroupedNativeMethods] for the partial class of the generated extension methods. + +> PM> Install-Package [csbindgen](https://www.nuget.org/packages/csbindgen) + +```csharp +// create new file and write same type-name with same namespace +namespace CsBindgen +{ + // append `GroupedNativeMethods` attribute + [GroupedNativeMethods()] + internal static unsafe partial class NativeMethods + { + } +} +``` + +```csharp +// original methods +[DllImport(__DllName, EntryPoint = "counter_context_insert", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] +public static extern void counter_context_insert(counter_context* context, int value); + +// generated methods +public static void Insert(this ref global::CsBindgen.counter_context @context, int @value) + +// ---- + +counter_context* context = NativeMethods.create_counter_context(); + +// standard style +NativeMethods.counter_context_insert(context, 10); + +// generated style +context->Insert(10); +``` + +`GroupedNativeMethods` has four configuration parameters. + +```csharp +public GroupedNativeMethodsAttribute( + string removePrefix = "", + string removeSuffix = "", + bool removeUntilTypeName = true, + bool fixMethodName = true) +``` + +`removeUntilTypeName` will remove until find type-name in method-name. For example `foo_counter_context_insert(countext_context* foo)` -> `Insert`. As a result, it is recommended to use a naming convention where the same type name is placed immediately before the verb. Type Marshalling --- @@ -383,6 +436,7 @@ Rust types will map these C# types. | `#[repr(C)]Struct` | `[StructLayout(LayoutKind.Sequential)]Struct` | | `#[repr(C)]Union` | `[StructLayout(LayoutKind.Explicit)]Struct` | | `#[repr(u*/i*)]Enum` | `Enum` | +| [bitflags!](https://crates.io/crates/bitflags) | `[Flags]Enum` | | `extern "C" fn` | `delegate* unmanaged[Cdecl]<>` or `Func<>/Action<>` | | `Option` | `delegate* unmanaged[Cdecl]<>` or `Func<>/Action<>` | | `*mut T` | `T*` | @@ -442,6 +496,62 @@ internal unsafe partial struct MyVector3 } ``` +Also supports tuple struct, it will generate `Item*` fields in C#. + +``` +#[repr(C)] +pub struct MyIntVec3(i32, i32, i32); +``` + +```csharp +[StructLayout(LayoutKind.Sequential)] +internal unsafe partial struct MyIntVec3 +{ + public int Item1; + public int Item2; + public int Item3; +} +``` + +It also supports unit struct, but there is no C# struct that is synonymous with Rust's unit struct (0 byte), so it cannot be materialized. Instead of using void*, it is recommended to use typed pointers. + +``` +// 0-byte +#[repr(C)] +pub struct CounterContext; +``` + +```csharp +// 1-byte +[StructLayout(LayoutKind.Sequential)] +internal unsafe partial struct CounterContext +{ +} +``` + +```rust +// recommend to use as pointer, in C#, holds CounterContext* +#[no_mangle] +pub extern "C" fn create_counter_context() -> *mut CounterContext { + let ctx = Box::new(InternalCounterContext { + set: HashSet::new(), + }); + Box::into_raw(ctx) as *mut CounterContext +} + +#[no_mangle] +pub unsafe extern "C" fn counter_context_insert(context: *mut CounterContext, value: i32) { + let mut counter = Box::from_raw(context as *mut InternalCounterContext); + counter.set.insert(value); + Box::into_raw(counter); +} + +#[no_mangle] +pub unsafe extern "C" fn destroy_counter_context(context: *mut CounterContext) { + _ = Box::from_raw(context as *mut InternalCounterContext); +} +``` + ### Union `Union` will generate `[FieldOffset(0)]` struct. @@ -492,6 +602,34 @@ internal enum ByteTest : byte } ``` +### bitflags Enum + +csbindgen supports [bitflags](https://crates.io/crates/bitflags) crate. + +```rust +bitflags! { + #[repr(C)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + struct EnumFlags: u32 { + const A = 0b00000001; + const B = 0b00000010; + const C = 0b00000100; + const ABC = Self::A.bits() | Self::B.bits() | Self::C.bits(); + } +} +``` + +```csharp +[Flags] +internal enum EnumFlags : uint +{ + A = 0b00000001, + B = 0b00000010, + C = 0b00000100, + ABC = A | B | C, +} +``` + ### Function You can receive, return function to/from C#. @@ -591,40 +729,42 @@ NativeMethods.delete_context(context); You can also pass memory allocated by C# to Rust (use `fixed` or `GCHandle.Alloc(Pinned)`). The important thing is that memory allocated in Rust must release in Rust and memory allocated in C# must release in C#. -If you want to pass a non FFI Safe struct, cast it to `*mut c_void`. Then C# will treat it as a `void*`. csbindgen does not support Opaque Type. +If you want to pass a non FFI Safe struct, cast it to `*mut c_void`. Then C# will treat it as a `void*`. csbindgen does not support Opaque Type. Additionally, by returning a unit struct instead of `c_void`, you can create a typed handler. ```rust #[no_mangle] -pub extern "C" fn create_counter_context() -> *mut c_void { - let ctx = Box::new(CounterContext { +pub extern "C" fn create_counter_context() -> *mut CounterContext { + let ctx = Box::new(InternalCounterContext { set: HashSet::new(), }); Box::into_raw(ctx) as *mut c_void } #[no_mangle] -pub unsafe extern "C" fn insert_counter_context(context: *mut c_void, value: i32) { - let mut counter = Box::from_raw(context as *mut CounterContext); +pub unsafe extern "C" fn insert_counter_context(context: *mut CounterContext, value: i32) { + let mut counter = Box::from_raw(context as *mut InternalCounterContext); counter.set.insert(value); Box::into_raw(counter); } #[no_mangle] -pub unsafe extern "C" fn delete_counter_context(context: *mut c_void) { - let counter = Box::from_raw(context as *mut CounterContext); +pub unsafe extern "C" fn delete_counter_context(context: *mut CounterContext) { + let counter = Box::from_raw(context as *mut InternalCounterContext); for value in counter.set.iter() { println!("counter value: {}", value) } } #[repr(C)] -pub struct CounterContext { +pub struct CounterContext; + +// no repr(C) +pub struct InternalCounterContext { pub set: HashSet, } ``` ```csharp -// in C#, ctx = void* var ctx = NativeMethods.create_counter_context(); NativeMethods.insert_counter_context(ctx, 10); @@ -633,6 +773,8 @@ NativeMethods.insert_counter_context(ctx, 20); NativeMethods.delete_counter_context(ctx); ``` +In this case, recommed to use with [Grouping Extension Methods](#grouping-extension-methods). + If you want to pass null-pointer, in rust side, convert to Option by `as_ref()`. ```rust diff --git a/csbindgen-tests/Cargo.toml b/csbindgen-tests/Cargo.toml index 202e2d2..6e01bff 100644 --- a/csbindgen-tests/Cargo.toml +++ b/csbindgen-tests/Cargo.toml @@ -14,6 +14,7 @@ path = "src/lib.rs" [dependencies] csbindgen = { path = "../csbindgen" } +bitflags = "2.1.0" # physx-sys = "0.11.0" [build-dependencies] diff --git a/csbindgen-tests/build.rs b/csbindgen-tests/build.rs index a2367d6..83c4b05 100644 --- a/csbindgen-tests/build.rs +++ b/csbindgen-tests/build.rs @@ -64,7 +64,6 @@ fn main() -> Result<(), Box> { // .csharp_use_function_pointer(true) .generate_to_file("src/lz4_ffi.rs", "../dotnet-sandbox/lz4_bindgen.cs") .unwrap(); - // csbindgen::Builder::default() // .input_bindgen_file("src/sqlite3.rs") diff --git a/csbindgen-tests/src/lib.rs b/csbindgen-tests/src/lib.rs index 1e73fb1..d4d5213 100644 --- a/csbindgen-tests/src/lib.rs +++ b/csbindgen-tests/src/lib.rs @@ -1,6 +1,6 @@ use std::{ collections::HashSet, - ffi::{c_char, c_long, c_ulong, c_void, CString}, + ffi::{c_char, c_long, c_ulong, CString}, }; #[allow(dead_code)] @@ -66,8 +66,6 @@ mod lz4_ffi; // println!("{:?}", hoge); // } - - // #[no_mangle] // pub extern "C" fn string_char(str: char) { // println!("{}", str); @@ -100,43 +98,46 @@ pub struct JPH_ContactManifold { pub extern "C" fn JPH_PruneContactPoints( ioContactPointsOn1: *mut JPH_ContactPoints, ioContactPointsOn2: *mut JPH_ContactManifold, -) -{ +) { todo!(); } +use bitflags::bitflags; + +bitflags! { + #[repr(C)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] + struct EnumFlags: u32 { + const A = 0b00000001; + const B = 0b00000010; + const C = 0b00000100; + const ABC = Self::A.bits() | Self::B.bits() | Self::C.bits(); + } +} /// my comment! #[no_mangle] -pub extern "C" fn comment_one() { -} - +extern "C" fn comment_one(_flags: EnumFlags) {} /// Multiline Comments /// # GOTO /// Here /// Foo /// Bar -/// +/// /// TO -/// +/// /// ZZZ -pub extern "C" fn long_jpn_comment() { -} - - +pub extern "C" fn long_jpn_comment() {} #[repr(C)] -pub struct my_int_vec3(i32,i32,i32); - -pub extern "C" fn use_vec3(_v3: my_int_vec3) { - -} +pub struct my_int_vec3(i32, i32, i32); +pub extern "C" fn use_vec3(_v3: my_int_vec3) {} #[repr(C)] pub struct NfcCard { - pub delegate: unsafe extern "C" fn(ByteArray) -> ByteArray + pub delegate: unsafe extern "C" fn(ByteArray) -> ByteArray, } #[no_mangle] @@ -150,11 +151,11 @@ pub struct ByteArray { #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct event { - pub a: i32 -} + pub a: i32, +} #[no_mangle] -pub extern "C" fn event(event: event ) { +pub extern "C" fn event(event: event) { println!("{:?}", event); } @@ -283,27 +284,30 @@ pub extern "C" fn my_add(x: i32, y: i32) -> i32 { x + y } +#[repr(C)] +pub struct CounterContext; + #[no_mangle] -pub extern "C" fn create_counter_context() -> *mut c_void { - let ctx = Box::new(CounterContext { +pub extern "C" fn create_counter_context() -> *mut CounterContext { + let ctx = Box::new(InternalCounterContext { set: HashSet::new(), }); - Box::into_raw(ctx) as *mut c_void + Box::into_raw(ctx) as *mut CounterContext } #[no_mangle] -pub unsafe extern "C" fn insert_counter_context(context: *mut c_void, value: i32) { - let mut counter = Box::from_raw(context as *mut CounterContext); +pub unsafe extern "C" fn counter_context_insert(context: *mut CounterContext, value: i32) { + let mut counter = Box::from_raw(context as *mut InternalCounterContext); counter.set.insert(value); Box::into_raw(counter); } #[no_mangle] -pub unsafe extern "C" fn delete_counter_context(context: *mut c_void) { - let counter = Box::from_raw(context as *mut CounterContext); - for value in counter.set.iter() { - println!("counter value: {}", value) - } +pub unsafe extern "C" fn destroy_counter_context(context: *mut CounterContext) { + _ = Box::from_raw(context as *mut InternalCounterContext); + // for value in counter.set.iter() { + // println!("counter value: {}", value) + // } } #[no_mangle] @@ -329,8 +333,8 @@ pub struct MyVector3 { pub z: f32, } -#[repr(C)] -pub struct CounterContext { + // not repr(C) +pub struct InternalCounterContext { pub set: HashSet, } @@ -472,7 +476,6 @@ fn build_test() { // .generate_csharp_file("dotnet-sandbox/NativeMethods.cs") // .unwrap(); - csbindgen::Builder::new() .input_bindgen_file("csbindgen-tests/src/physx/physx_generated.rs") .input_bindgen_file("csbindgen-tests/src/physx/x86_64-pc-windows-msvc/structgen.rs") @@ -567,12 +570,11 @@ pub struct CallbackTable { pub foobar: extern "C" fn(i: i32) -> i32, } - // fn run_physix(){ // unsafe { // let foundation = physx_create_foundation(); // let physics = physx_create_physics(foundation); - + // let mut scene_desc = PxSceneDesc_new(PxPhysics_getTolerancesScale(physics)); // scene_desc.gravity = PxVec3 { // x: 0.0, @@ -580,8 +582,6 @@ pub struct CallbackTable { // z: 0.0, // }; - - // let dispatcher = phys_PxDefaultCpuDispatcherCreate( // 1, // null_mut(), @@ -590,9 +590,9 @@ pub struct CallbackTable { // ); // scene_desc.cpuDispatcher = dispatcher.cast(); // scene_desc.filterShader = get_default_simulation_filter_shader(); - + // let scene = PxPhysics_createScene_mut(physics, &scene_desc); - + // // Your physics simulation goes here // } -// } \ No newline at end of file +// } diff --git a/csbindgen/src/parser.rs b/csbindgen/src/parser.rs index fba9e34..f7ddeb1 100644 --- a/csbindgen/src/parser.rs +++ b/csbindgen/src/parser.rs @@ -1,6 +1,6 @@ use crate::{alias_map::AliasMap, builder::BindgenOptions, field_map::FieldMap, type_meta::*}; use regex::Regex; -use std::{collections::HashSet}; +use std::collections::HashSet; use syn::{ForeignItem, Item, Pat, ReturnType}; enum FnItem { @@ -164,6 +164,14 @@ pub fn collect_struct(ast: &syn::File, result: &mut Vec) { fields, is_union: false, }); + } else if let syn::Fields::Unit = &t.fields { + let struct_name = t.ident.to_string(); + let fields: Vec = Vec::new(); + result.push(RustStruct { + struct_name, + fields, + is_union: false, + }); } } } @@ -249,7 +257,7 @@ pub fn collect_enum(ast: &syn::File, result: &mut Vec) { let token_string = t.mac.tokens.to_string(); - let match1 = Regex::new("pub struct ([^ ]+) : ([^ ]+)") + let match1 = Regex::new("struct ([^ ]+) : ([^ ]+)") .unwrap() .captures(token_string.as_str()) .unwrap(); @@ -269,6 +277,7 @@ pub fn collect_enum(ast: &syn::File, result: &mut Vec) { .as_str() .to_string() .replace("Self :: ", "") + .replace(" . bits ()", "") .replace(" . bits", "") .trim() .to_string(), diff --git a/dotnet-sandbox/NativeMethods.cs b/dotnet-sandbox/NativeMethods.cs index 707df9f..b0d2e0f 100644 --- a/dotnet-sandbox/NativeMethods.cs +++ b/dotnet-sandbox/NativeMethods.cs @@ -18,7 +18,7 @@ namespace CsBindgen /// my comment! [DllImport(__DllName, EntryPoint = "comment_one", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] - public static extern void comment_one(); + public static extern void comment_one(EnumFlags _flags); /// Multiline Comments # GOTO Here Foo Bar TO ZZZ [DllImport(__DllName, EntryPoint = "long_jpn_comment", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] @@ -91,13 +91,13 @@ namespace CsBindgen public static extern int my_add(int x, int y); [DllImport(__DllName, EntryPoint = "create_counter_context", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] - public static extern void* create_counter_context(); + public static extern counter_context* create_counter_context(); - [DllImport(__DllName, EntryPoint = "insert_counter_context", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] - public static extern void insert_counter_context(void* context, int value); + [DllImport(__DllName, EntryPoint = "counter_context_insert", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern void counter_context_insert(counter_context* context, int value); - [DllImport(__DllName, EntryPoint = "delete_counter_context", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] - public static extern void delete_counter_context(void* context); + [DllImport(__DllName, EntryPoint = "destroy_counter_context", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern void destroy_counter_context(counter_context* context); [DllImport(__DllName, EntryPoint = "pass_vector3", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] public static extern void pass_vector3(MyVector3 v3); @@ -186,6 +186,11 @@ namespace CsBindgen public int a; } + [StructLayout(LayoutKind.Sequential)] + internal unsafe partial struct counter_context + { + } + [StructLayout(LayoutKind.Explicit)] internal unsafe partial struct MyUnion { @@ -225,6 +230,15 @@ namespace CsBindgen } + [Flags] + internal enum EnumFlags : uint + { + A = 0b00000001, + B = 0b00000010, + C = 0b00000100, + ABC = A | B | C, + } + internal enum IntEnumTest : sbyte { A = 1, diff --git a/dotnet-sandbox/Program.cs b/dotnet-sandbox/Program.cs index 125ae24..4964e4e 100644 --- a/dotnet-sandbox/Program.cs +++ b/dotnet-sandbox/Program.cs @@ -23,8 +23,18 @@ unsafe - var ctx = NativeMethods.create_context(); - ctx->DeleteContext2(); + var handler = NativeMethods.create_counter_context(); + + handler->Insert(10); + handler->Insert(20); + handler->Insert(30); + + NativeMethods.destroy_counter_context(handler); + + + + + //ctx->DeleteContext2();