diff --git a/README.md b/README.md index 3a16279..a8d6dc8 100644 --- a/README.md +++ b/README.md @@ -224,7 +224,7 @@ csbindgen::Builder::default() .input_bindgen_file("src/lz4.rs") // required .method_filter(|x| { x.starts_with("LZ4") } ) // optional, default: |x| !x.starts_with('_') .rust_method_prefix("csbindgen_") // optional, default: "csbindgen_" - .rust_file_header("use super::lz4;") // optional, default: "" + .rust_file_header("use super::lz4::*;") // optional, default: "" .rust_method_type_path("lz4") // optional, default: "" .csharp_dll_name("lz4") // required .csharp_class_name("NativeMethods") // optional, default: NativeMethods @@ -369,6 +369,8 @@ Rust types will map these C# types. | `*const T` | `T*` | | `*mut *mut T` | `T**` | | `*const *const T` | `T**` | +| `*mut *const T` | `T**` | +| `*const *mut T` | `T**` | csbindgen is designed to return primitives that do not cause marshalling. It is better to convert from pointers to Span yourself than to do the conversion implicitly and in a black box. This is a recent trend, such as the addition of [DisableRuntimeMarshalling](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.disableruntimemarshallingattribute) from .NET 7. @@ -814,7 +816,53 @@ finally } ``` -C# to Rust would be a bit simpler to send, just pass byte* and length. In Rust, use `std::slice::from_raw_parts` to create slice. Again, the important thing is that memory allocated in Rust must release in Rust and memory allocated in C# must release in C#. +C# to Rust would be a bit simpler to send, just pass byte* and length. In Rust, use `std::slice::from_raw_parts` to create slice. + +```rust +#[no_mangle] +pub unsafe extern "C" fn csharp_to_rust_string(utf16_str: *const u16, utf16_len: i32) { + let slice = std::slice::from_raw_parts(utf16_str, utf16_len as usize); + let str = String::from_utf16(slice).unwrap(); + println!("{}", str); +} + +#[no_mangle] +pub unsafe extern "C" fn csharp_to_rust_utf8(utf8_str: *const u8, utf8_len: i32) { + let slice = std::slice::from_raw_parts(utf8_str, utf8_len as usize); + let str = String::from_utf8_unchecked(slice.to_vec()); + println!("{}", str); +} + + +#[no_mangle] +pub unsafe extern "C" fn csharp_to_rust_bytes(bytes: *const u8, len: i32) { + let slice = std::slice::from_raw_parts(bytes, len as usize); + let vec = slice.to_vec(); + println!("{:?}", vec); +} +``` + +```csharp +var str = "foobarbaz:あいうえお"; // JPN(Unicode) +fixed (char* p = str) +{ + NativeMethods.csharp_to_rust_string((ushort*)p, str.Length); +} + +var str2 = Encoding.UTF8.GetBytes("あいうえお:foobarbaz"); +fixed (byte* p = str2) +{ + NativeMethods.csharp_to_rust_utf8(p, str2.Length); +} + +var bytes = new byte[] { 1, 10, 100, 255 }; +fixed (byte* p = bytes) +{ + NativeMethods.csharp_to_rust_bytes(p, bytes.Length); +} +``` + +Again, the important thing is that memory allocated in Rust must release in Rust and memory allocated in C# must release in C#. Build Tracing --- diff --git a/csbindgen-tests/build.rs b/csbindgen-tests/build.rs index f1cf244..0d934b3 100644 --- a/csbindgen-tests/build.rs +++ b/csbindgen-tests/build.rs @@ -61,10 +61,10 @@ fn main() -> Result<(), Box> { csbindgen::Builder::default() .input_extern_file("src/lib.rs") - .csharp_class_name("LibRust") + .csharp_class_name("NativeMethods") .csharp_dll_name("csbindgen_tests") .csharp_use_function_pointer(true) - .generate_csharp_file("../dotnet-sandbox/method_call.cs") + .generate_csharp_file("../dotnet-sandbox/NativeMethods.cs") .unwrap(); // csbindgen::Builder::new() diff --git a/csbindgen-tests/src/lib.rs b/csbindgen-tests/src/lib.rs index f1ba6be..44bebfc 100644 --- a/csbindgen-tests/src/lib.rs +++ b/csbindgen-tests/src/lib.rs @@ -25,7 +25,6 @@ mod lz4_ffi; // #[allow(non_camel_case_types)] // mod bullet3_ffi; - // #[allow(dead_code)] // #[allow(non_snake_case)] // #[allow(non_camel_case_types)] @@ -37,7 +36,6 @@ mod lz4_ffi; // #[allow(non_camel_case_types)] // mod quiche_ffi; - // #[allow(dead_code)] // #[allow(non_snake_case)] // #[allow(non_camel_case_types)] @@ -49,10 +47,6 @@ mod lz4_ffi; // #[allow(non_camel_case_types)] // mod zstd_ffi; - - - - #[allow(non_camel_case_types)] pub type LONG_PTR = ::std::os::raw::c_longlong; #[allow(non_camel_case_types)] @@ -73,6 +67,33 @@ pub unsafe extern "C" fn nullpointer_test(p: *const u8) { }; } +#[no_mangle] +pub unsafe extern "C" fn csharp_to_rust_string(utf16_str: *const u16, utf16_len: i32) { + let slice = std::slice::from_raw_parts(utf16_str, utf16_len as usize); + + let str = String::from_utf16(slice).unwrap(); + + println!("{}", str); +} + +#[no_mangle] +pub unsafe extern "C" fn csharp_to_rust_utf8(utf8_str: *const u8, utf8_len: i32) { + let slice = std::slice::from_raw_parts(utf8_str, utf8_len as usize); + let str = String::from_utf8_unchecked(slice.to_vec()); + println!("{}", str); +} + + +#[no_mangle] +pub unsafe extern "C" fn csharp_to_rust_bytes(bytes: *const u8, len: i32) { + let slice = std::slice::from_raw_parts(bytes, len as usize); + let vec = slice.to_vec(); + println!("{:?}", vec); +} + + + + #[no_mangle] pub extern "C" fn callback_test(cb: extern "C" fn(a: i32) -> i32) -> i32 { cb(100) @@ -85,11 +106,11 @@ pub extern "C" fn csharp_to_rust(cb: extern "C" fn(x: i32, y: i32) -> i32) { } #[no_mangle] -pub extern "C" fn rust_to_csharp() -> extern fn(x: i32, y: i32) -> i32 { +pub extern "C" fn rust_to_csharp() -> extern "C" fn(x: i32, y: i32) -> i32 { sum // return rust method } -extern "C" fn sum(x:i32, y:i32) -> i32 { +extern "C" fn sum(x: i32, y: i32) -> i32 { x + y } diff --git a/csbindgen/Cargo.toml b/csbindgen/Cargo.toml index 09c239e..44ba37e 100644 --- a/csbindgen/Cargo.toml +++ b/csbindgen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "csbindgen" -version = "1.0.0" +version = "1.1.0" edition = "2021" authors = [ "Yoshifumi Kawai ", diff --git a/dotnet-sandbox/method_call.cs b/dotnet-sandbox/NativeMethods.cs similarity index 91% rename from dotnet-sandbox/method_call.cs rename to dotnet-sandbox/NativeMethods.cs index bf09e5d..bf4f2ee 100644 --- a/dotnet-sandbox/method_call.cs +++ b/dotnet-sandbox/NativeMethods.cs @@ -8,7 +8,7 @@ using System.Runtime.InteropServices; namespace CsBindgen { - internal static unsafe partial class LibRust + internal static unsafe partial class NativeMethods { const string __DllName = "csbindgen_tests"; @@ -21,6 +21,15 @@ namespace CsBindgen [DllImport(__DllName, EntryPoint = "nullpointer_test", CallingConvention = CallingConvention.Cdecl)] public static extern void nullpointer_test(byte* p); + [DllImport(__DllName, EntryPoint = "csharp_to_rust_string", CallingConvention = CallingConvention.Cdecl)] + public static extern void csharp_to_rust_string(ushort* utf16_str, int utf16_len); + + [DllImport(__DllName, EntryPoint = "csharp_to_rust_utf8", CallingConvention = CallingConvention.Cdecl)] + public static extern void csharp_to_rust_utf8(byte* utf8_str, int utf8_len); + + [DllImport(__DllName, EntryPoint = "csharp_to_rust_bytes", CallingConvention = CallingConvention.Cdecl)] + public static extern void csharp_to_rust_bytes(byte* bytes, int len); + [DllImport(__DllName, EntryPoint = "callback_test", CallingConvention = CallingConvention.Cdecl)] public static extern int callback_test(delegate* unmanaged[Cdecl] cb); diff --git a/dotnet-sandbox/Program.cs b/dotnet-sandbox/Program.cs index 734546a..48a1e11 100644 --- a/dotnet-sandbox/Program.cs +++ b/dotnet-sandbox/Program.cs @@ -14,21 +14,45 @@ using System.Text; unsafe { - LibRust.call_bindgen_lz4(); - LibRust.alias_test1(null); - + //NativeMethods.call_bindgen_lz4(); + + + var str = "foobarbaz:あいうえお"; // JPN(Unicode) + fixed (char* p = str) + { + NativeMethods.csharp_to_rust_string((ushort*)p, str.Length); + } + + var str2 = Encoding.UTF8.GetBytes("あいうえお:foobarbaz"); + fixed (byte* p = str2) + { + NativeMethods.csharp_to_rust_utf8(p, str2.Length); + } + + var bytes = new byte[] { 1, 10, 100, 255 }; + fixed (byte* p = bytes) + { + NativeMethods.csharp_to_rust_bytes(p, bytes.Length); + } + + + + //NativeMethods.csharp_to_rust_utf8 + //NativeMethods.alias_test1(null); + + // C# -> Rust, pass static UnmanagedCallersOnly method with `&` [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] static int Sum(int x, int y) => x + y; - LibRust.csharp_to_rust(&Sum); + NativeMethods.csharp_to_rust(&Sum); // Rust -> C#, get typed delegate* - var f = LibRust.rust_to_csharp(); + var f = NativeMethods.rust_to_csharp(); var v = f(20, 30); Console.WriteLine(v); // 50 @@ -50,18 +74,18 @@ unsafe //Console.WriteLine(cc); - var context = LibRust.create_context(); + // var context = LibRust.create_context(); - // do anything... + // // do anything... - LibRust.delete_context(context); + // LibRust.delete_context(context); - var ctx = LibRust.create_counter_context(); // ctx = void* - - LibRust.insert_counter_context(ctx, 10); - LibRust.insert_counter_context(ctx, 20); + //var ctx = LibRust.create_counter_context(); // ctx = void* - LibRust.delete_counter_context(ctx); + //LibRust.insert_counter_context(ctx, 10); + //LibRust.insert_counter_context(ctx, 20); + + //LibRust.delete_counter_context(ctx); //LibRust.insert_counter_context(ctx, 20); //LibRust.insert_counter_context(ctx, 30);