diff --git a/csbindgen-tests/build.rs b/csbindgen-tests/build.rs index 029ba7f..ddf1226 100644 --- a/csbindgen-tests/build.rs +++ b/csbindgen-tests/build.rs @@ -42,7 +42,7 @@ fn main() { csbindgen::Builder::default() .input_bindgen_file("src/lz4.rs") - .method_filter(|x| { x.starts_with("LZ4")} ) + .method_filter(|x| x.starts_with("LZ4")) .rust_method_prefix("csbindgen_") .rust_file_header("use super::lz4;") .rust_method_type_path("lz4") diff --git a/csbindgen-tests/src/lib.rs b/csbindgen-tests/src/lib.rs index 409d4f8..8d7a633 100644 --- a/csbindgen-tests/src/lib.rs +++ b/csbindgen-tests/src/lib.rs @@ -1,3 +1,5 @@ +use std::ffi::{c_char, CString}; + #[allow(dead_code)] #[allow(non_snake_case)] #[allow(non_camel_case_types)] @@ -9,6 +11,22 @@ mod lz4; #[allow(non_camel_case_types)] mod lz4_ffi; + + +#[no_mangle] +#[allow(improper_ctypes_definitions)] +pub extern "C" fn ignore_nop() -> (i32, i32) { + println!("hello ignore!"); + (1, 2) +} + + +#[no_mangle] +pub extern "C" fn nop() -> () { + println!("hello nop!"); +} + + #[no_mangle] pub extern "C" fn my_add(x: i32, y: i32) -> i32 { x + y @@ -32,25 +50,57 @@ pub extern "C" fn my_bool( true } -// #[no_mangle] -// pub unsafe extern "C" fn new(x: *mut *mut Vec) { -// let v = Box::new(Vec::new()); -// *x = Box::into_raw(v); -// } - #[no_mangle] -pub unsafe extern "C" fn unsafe_return_string() -> *const u8 { - todo!(); +pub extern "C" fn alloc_c_string() -> *mut c_char { + let str = CString::new("foo bar baz").unwrap(); + str.into_raw() } #[no_mangle] -pub unsafe extern "C" fn unsafe_return_string2() -> *const u8 { - todo!(); +pub extern "C" fn free_c_string(str: *mut c_char) { + unsafe { CString::from_raw(str) }; } #[no_mangle] -pub extern "C" fn unsafe_destroy_string(s: *mut String) { - unsafe { Box::from_raw(s) }; +pub extern "C" fn alloc_u8_string() -> *mut ByteBuffer { + let str = format!("foo bar baz"); + let buf = ByteBuffer::from_vec(str.into_bytes()); + Box::into_raw(Box::new(buf)) +} + +#[no_mangle] +pub unsafe extern "C" fn free_u8_string(buffer: *mut ByteBuffer) { + let buf = Box::from_raw(buffer); + // drop inner buffer, if you need String, use String::from_utf8_unchecked(buf.destroy_into_vec()) instead. + buf.destroy(); +} + +#[no_mangle] +pub extern "C" fn alloc_u8_buffer() -> *mut ByteBuffer { + let vec: Vec = vec![1, 10, 100]; + let buf = ByteBuffer::from_vec(vec); + Box::into_raw(Box::new(buf)) +} + +#[no_mangle] +pub unsafe extern "C" fn free_u8_buffer(buffer: *mut ByteBuffer) { + let buf = Box::from_raw(buffer); + // drop inner buffer, if you need Vec, use buf.destroy_into_vec() instead. + buf.destroy(); +} + +#[no_mangle] +pub extern "C" fn alloc_i32_buffer() -> *mut ByteBuffer { + let vec: Vec = vec![1, 10, 100, 1000, 10000]; + let buf = ByteBuffer::from_vec_struct(vec); + Box::into_raw(Box::new(buf)) +} + +#[no_mangle] +pub unsafe extern "C" fn free_i32_buffer(buffer: *mut ByteBuffer) { + let buf = Box::from_raw(buffer); + // drop inner buffer, if you need Vec, use buf.destroy_into_vec_struct::() instead. + buf.destroy(); } #[no_mangle] @@ -64,6 +114,20 @@ pub extern "C" fn delete_context(context: *mut Context) { unsafe { Box::from_raw(context) }; } + +#[no_mangle] +pub extern "C" fn call_bindgen() { + let path = std::env::current_dir().unwrap(); + println!("starting dir: {}", path.display()); // csbindgen/csbindgen-tests + + csbindgen::Builder::default() + .input_extern_file("../../../../csbindgen-tests/src/lib.rs") + .csharp_class_name("LibRust") + .csharp_dll_name("csbindgen_tests") + .generate_csharp_file("../../../../dotnet-sandbox/method_call.cs") + .unwrap(); +} + #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct Context { @@ -72,24 +136,97 @@ pub struct Context { #[test] fn build_test() { - // let path = std::env::current_dir().unwrap(); - // println!("starting dir: {}", path.display()); // csbindgen/csbindgen-tests + let path = std::env::current_dir().unwrap(); + println!("starting dir: {}", path.display()); // csbindgen/csbindgen-tests // // unsafe { // // let num = lz4::LZ4_versionNumber(); // // println!("lz4 num: {}", num); // // } - // csbindgen::Builder::default() - // .input_bindgen_file("src/lz4.rs") - // .rust_method_prefix("csbindgen_") - // .rust_file_header("use super::lz4;") - // .rust_method_type_path("lz4") - // .csharp_class_name("LibLz4") - // .csharp_dll_name("csbindgen_tests") - // .csharp_dll_name_if("UNITY_IOS && !UNITY_EDITOR", "__Internal") - // .csharp_entry_point_prefix("csbindgen_") - // .csharp_method_prefix("") - // .generate_to_file("src/lz4_ffi.rs", "../dotnet-sandbox/lz4_bindgen.cs") - // .unwrap(); + + csbindgen::Builder::default() + .input_extern_file("csbindgen-tests/src/lib.rs") + .csharp_class_name("LibRust") + .csharp_dll_name("csbindgen_tests") + .generate_csharp_file("dotnet-sandbox/method_call.cs") + .unwrap(); +} + +#[repr(C)] +pub struct ByteBuffer { + ptr: *mut u8, + length: i32, + capacity: i32, +} + +impl ByteBuffer { + pub fn len(&self) -> usize { + self.length + .try_into() + .expect("buffer length negative or overflowed") + } + + pub fn from_vec(bytes: Vec) -> Self { + let length = i32::try_from(bytes.len()).expect("buffer length cannot fit into a i32."); + let capacity = + i32::try_from(bytes.capacity()).expect("buffer capacity cannot fit into a i32."); + + // keep memory until call delete + let mut v = std::mem::ManuallyDrop::new(bytes); + + Self { + ptr: v.as_mut_ptr(), + length, + capacity, + } + } + + pub fn from_vec_struct(bytes: Vec) -> Self { + let element_size = std::mem::size_of::() as i32; + + let length = (bytes.len() as i32) * element_size; + let capacity = (bytes.capacity() as i32) * element_size; + + let mut v = std::mem::ManuallyDrop::new(bytes); + + Self { + ptr: v.as_mut_ptr() as *mut u8, + length, + capacity, + } + } + + pub fn destroy_into_vec(self) -> Vec { + if self.ptr.is_null() { + vec![] + } else { + let capacity: usize = self + .capacity + .try_into() + .expect("buffer capacity negative or overflowed"); + let length: usize = self + .length + .try_into() + .expect("buffer length negative or overflowed"); + + unsafe { Vec::from_raw_parts(self.ptr, length, capacity) } + } + } + + pub fn destroy_into_vec_struct(self) -> Vec { + if self.ptr.is_null() { + vec![] + } else { + let element_size = std::mem::size_of::() as i32; + let length = (self.length * element_size) as usize; + let capacity = (self.capacity * element_size) as usize; + + unsafe { Vec::from_raw_parts(self.ptr as *mut T, length, capacity) } + } + } + + pub fn destroy(self) { + drop(self.destroy_into_vec()); + } } diff --git a/csbindgen/src/emitter.rs b/csbindgen/src/emitter.rs index 636e3ab..dc6f750 100644 --- a/csbindgen/src/emitter.rs +++ b/csbindgen/src/emitter.rs @@ -154,7 +154,7 @@ pub fn emit_csharp( structs_string .push_str_ln(format!(" [StructLayout(LayoutKind.{layout_kind})]").as_str()); - structs_string.push_str_ln(format!(" {accessibility} unsafe struct {name}").as_str()); + structs_string.push_str_ln(format!(" {accessibility} unsafe partial struct {name}").as_str()); structs_string.push_str_ln(" {"); for field in &item.fields { if item.is_union { diff --git a/csbindgen/src/parser.rs b/csbindgen/src/parser.rs index fbcce5c..a70e20f 100644 --- a/csbindgen/src/parser.rs +++ b/csbindgen/src/parser.rs @@ -31,7 +31,8 @@ pub fn collect_extern_method(ast: &syn::File, options: &BindgenOptions) -> Vec Option } let rust_type = parse_type(&t.ty); + if rust_type.type_name.is_empty(){ + println!("Csbindgen can't handle this parameter type so ignore generate, method_name: {} parameter_name: {}", method_name, parameter_name); + return None; + } parameters.push(Parameter { name: parameter_name, @@ -76,6 +81,7 @@ fn parse_method(item: FnItem, options: &BindgenOptions) -> Option if let ReturnType::Type(_, b) = &sig.output { let rust_type = parse_type(b); if rust_type.type_name.is_empty() { + println!("Csbindgen can't handle this return type so ignore generate, method_name: {}", method_name); return None; } @@ -239,6 +245,13 @@ fn parse_type(t: &syn::Type) -> RustType { parse_type(&t.elem).type_name // maybe ok, only retrieve type_name } + syn::Type::Tuple(t) => { + if t.elems.len() == 0 { + "()".to_string() + } else { + "".to_string() + } + } _ => "".to_string(), }; diff --git a/csbindgen/src/type_meta.rs b/csbindgen/src/type_meta.rs index fc3450d..ef81a32 100644 --- a/csbindgen/src/type_meta.rs +++ b/csbindgen/src/type_meta.rs @@ -56,7 +56,7 @@ pub struct RustType { pub struct RustStruct { pub struct_name: String, pub fields: Vec, - pub is_union: bool, + pub is_union: bool } impl RustType { diff --git a/dotnet-sandbox/Program.cs b/dotnet-sandbox/Program.cs index c10caf3..8077d23 100644 --- a/dotnet-sandbox/Program.cs +++ b/dotnet-sandbox/Program.cs @@ -13,26 +13,74 @@ using System.Text; unsafe { - var a = false; - var b = false; - var c = false; - Console.WriteLine(Encoding.Default); + LibRust.call_bindgen(); + - var p = LibRust.unsafe_return_string(); + var cString = LibRust.alloc_c_string(); + var u8String = LibRust.alloc_u8_string(); + var u8Buffer = LibRust.alloc_u8_buffer(); + var i32Buffer = LibRust.alloc_i32_buffer(); + try + { + var str = new String((sbyte*)cString); + Console.WriteLine(str); - var s = Encoding.UTF8.GetString(p, 5); - Console.WriteLine(s); + Console.WriteLine("----"); + + var str2 = Encoding.UTF8.GetString(u8String->AsSpan()); + Console.WriteLine(str2); + + Console.WriteLine("----"); + + var buffer3 = u8Buffer->AsSpan(); + foreach (var item in buffer3) + { + Console.WriteLine(item); + } + + Console.WriteLine("----"); + + var i32Span = i32Buffer->AsSpan(); + foreach (var item in i32Span) + { + Console.WriteLine(item); + } + } + finally + { + LibRust.free_c_string(cString); + LibRust.free_u8_string(u8String); + LibRust.free_u8_buffer(u8Buffer); + LibRust.free_i32_buffer(i32Buffer); + } + + + //var buf = LibRust.return_raw_buffer(); + //try + //{ + + // var span = buf->AsSpan(); - var z = LibRust.my_bool(true, false, true, &a, &b, &c); - Console.WriteLine(a); - Console.WriteLine(b); - Console.WriteLine(c); - Console.WriteLine(z); + // var str = Encoding.UTF8.GetString(span); + // Console.WriteLine(str); + + // //foreach (var item in span) + // //{ + // // Console.WriteLine(item); + // //} + + //} + //finally + //{ + // LibRust.delete_raw_buffer(buf); + //} + + } @@ -55,4 +103,20 @@ public static unsafe partial class LibraryImportNativeMethods public struct Foo { [MarshalAs(UnmanagedType.U1)] public bool A; +} + +namespace CsBindgen +{ + partial struct ByteBuffer + { + public unsafe Span AsSpan() + { + return new Span(ptr, length); + } + + public unsafe Span AsSpan() + { + return MemoryMarshal.CreateSpan(ref Unsafe.AsRef(ptr), length / Unsafe.SizeOf()); + } + } } \ No newline at end of file diff --git a/dotnet-sandbox/lz4_bindgen.cs b/dotnet-sandbox/lz4_bindgen.cs index f500195..440e078 100644 --- a/dotnet-sandbox/lz4_bindgen.cs +++ b/dotnet-sandbox/lz4_bindgen.cs @@ -293,7 +293,7 @@ namespace CsBindgen } [StructLayout(LayoutKind.Sequential)] - public unsafe struct LZ4_stream_t_internal + public unsafe partial struct LZ4_stream_t_internal { public fixed uint hashTable[4096]; public byte* dictionary; @@ -304,7 +304,7 @@ namespace CsBindgen } [StructLayout(LayoutKind.Explicit)] - public unsafe struct LZ4_stream_u + public unsafe partial struct LZ4_stream_u { [FieldOffset(0)] public fixed byte minStateSize[16416]; @@ -313,7 +313,7 @@ namespace CsBindgen } [StructLayout(LayoutKind.Sequential)] - public unsafe struct LZ4_streamDecode_t_internal + public unsafe partial struct LZ4_streamDecode_t_internal { public byte* externalDict; public byte* prefixEnd; @@ -322,7 +322,7 @@ namespace CsBindgen } [StructLayout(LayoutKind.Explicit)] - public unsafe struct LZ4_streamDecode_u + public unsafe partial struct LZ4_streamDecode_u { [FieldOffset(0)] public fixed byte minStateSize[32]; @@ -331,7 +331,7 @@ namespace CsBindgen } [StructLayout(LayoutKind.Sequential)] - public unsafe struct LZ4HC_CCtx_internal + public unsafe partial struct LZ4HC_CCtx_internal { public fixed uint hashTable[32768]; public fixed ushort chainTable[65536]; @@ -348,7 +348,7 @@ namespace CsBindgen } [StructLayout(LayoutKind.Explicit)] - public unsafe struct LZ4_streamHC_u + public unsafe partial struct LZ4_streamHC_u { [FieldOffset(0)] public fixed byte minStateSize[262200]; @@ -357,7 +357,7 @@ namespace CsBindgen } [StructLayout(LayoutKind.Sequential)] - public unsafe struct LZ4F_frameInfo_t + public unsafe partial struct LZ4F_frameInfo_t { public int blockSizeID; public int blockMode; @@ -369,7 +369,7 @@ namespace CsBindgen } [StructLayout(LayoutKind.Sequential)] - public unsafe struct LZ4F_preferences_t + public unsafe partial struct LZ4F_preferences_t { public LZ4F_frameInfo_t frameInfo; public int compressionLevel; @@ -379,26 +379,26 @@ namespace CsBindgen } [StructLayout(LayoutKind.Sequential)] - public unsafe struct LZ4F_cctx_s + public unsafe partial struct LZ4F_cctx_s { public fixed byte _unused[1]; } [StructLayout(LayoutKind.Sequential)] - public unsafe struct LZ4F_compressOptions_t + public unsafe partial struct LZ4F_compressOptions_t { public uint stableSrc; public fixed uint reserved[3]; } [StructLayout(LayoutKind.Sequential)] - public unsafe struct LZ4F_dctx_s + public unsafe partial struct LZ4F_dctx_s { public fixed byte _unused[1]; } [StructLayout(LayoutKind.Sequential)] - public unsafe struct LZ4F_decompressOptions_t + public unsafe partial struct LZ4F_decompressOptions_t { public uint stableDst; public uint skipChecksums; diff --git a/dotnet-sandbox/method_call.cs b/dotnet-sandbox/method_call.cs index 2c1003e..201560a 100644 --- a/dotnet-sandbox/method_call.cs +++ b/dotnet-sandbox/method_call.cs @@ -11,6 +11,9 @@ namespace CsBindgen { const string __DllName = "csbindgen_tests"; + [DllImport(__DllName, EntryPoint = "nop", CallingConvention = CallingConvention.Cdecl)] + public static extern void nop(); + [DllImport(__DllName, EntryPoint = "my_add", CallingConvention = CallingConvention.Cdecl)] public static extern int my_add(int x, int y); @@ -18,14 +21,29 @@ namespace CsBindgen [return: MarshalAs(UnmanagedType.U1)] public static extern bool my_bool([MarshalAs(UnmanagedType.U1)] bool x, [MarshalAs(UnmanagedType.U1)] bool y, [MarshalAs(UnmanagedType.U1)] bool z, bool* xr, bool* yr, bool* zr); - [DllImport(__DllName, EntryPoint = "unsafe_return_string", CallingConvention = CallingConvention.Cdecl)] - public static extern byte* unsafe_return_string(); + [DllImport(__DllName, EntryPoint = "alloc_c_string", CallingConvention = CallingConvention.Cdecl)] + public static extern byte* alloc_c_string(); - [DllImport(__DllName, EntryPoint = "unsafe_return_string2", CallingConvention = CallingConvention.Cdecl)] - public static extern byte* unsafe_return_string2(); + [DllImport(__DllName, EntryPoint = "free_c_string", CallingConvention = CallingConvention.Cdecl)] + public static extern void free_c_string(byte* str); - [DllImport(__DllName, EntryPoint = "unsafe_destroy_string", CallingConvention = CallingConvention.Cdecl)] - public static extern void unsafe_destroy_string(String* s); + [DllImport(__DllName, EntryPoint = "alloc_u8_string", CallingConvention = CallingConvention.Cdecl)] + public static extern ByteBuffer* alloc_u8_string(); + + [DllImport(__DllName, EntryPoint = "free_u8_string", CallingConvention = CallingConvention.Cdecl)] + public static extern void free_u8_string(ByteBuffer* buffer); + + [DllImport(__DllName, EntryPoint = "alloc_u8_buffer", CallingConvention = CallingConvention.Cdecl)] + public static extern ByteBuffer* alloc_u8_buffer(); + + [DllImport(__DllName, EntryPoint = "free_u8_buffer", CallingConvention = CallingConvention.Cdecl)] + public static extern void free_u8_buffer(ByteBuffer* buffer); + + [DllImport(__DllName, EntryPoint = "alloc_i32_buffer", CallingConvention = CallingConvention.Cdecl)] + public static extern ByteBuffer* alloc_i32_buffer(); + + [DllImport(__DllName, EntryPoint = "free_i32_buffer", CallingConvention = CallingConvention.Cdecl)] + public static extern void free_i32_buffer(ByteBuffer* buffer); [DllImport(__DllName, EntryPoint = "create_context", CallingConvention = CallingConvention.Cdecl)] public static extern Context* create_context(); @@ -33,15 +51,26 @@ namespace CsBindgen [DllImport(__DllName, EntryPoint = "delete_context", CallingConvention = CallingConvention.Cdecl)] public static extern void delete_context(Context* context); + [DllImport(__DllName, EntryPoint = "call_bindgen", CallingConvention = CallingConvention.Cdecl)] + public static extern void call_bindgen(); + } [StructLayout(LayoutKind.Sequential)] - internal unsafe struct Context + internal unsafe partial struct Context { [MarshalAs(UnmanagedType.U1)] public bool foo; } + [StructLayout(LayoutKind.Sequential)] + internal unsafe partial struct ByteBuffer + { + public byte* ptr; + public int length; + public int capacity; + } + } \ No newline at end of file