{"id":13611722,"url":"https://github.com/Cysharp/csbindgen","last_synced_at":"2025-04-13T05:33:36.960Z","repository":{"id":91375509,"uuid":"606846724","full_name":"Cysharp/csbindgen","owner":"Cysharp","description":"Generate C# FFI from Rust for automatically brings native code and C native library to .NET and Unity.","archived":false,"fork":false,"pushed_at":"2025-03-19T06:28:52.000Z","size":6337,"stargazers_count":744,"open_issues_count":15,"forks_count":62,"subscribers_count":15,"default_branch":"main","last_synced_at":"2025-04-10T20:54:50.391Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Cysharp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-02-26T18:32:19.000Z","updated_at":"2025-04-09T06:27:19.000Z","dependencies_parsed_at":"2023-11-15T04:26:30.350Z","dependency_job_id":"5233f9f3-1a22-4fee-97de-18176e28d7ae","html_url":"https://github.com/Cysharp/csbindgen","commit_stats":{"total_commits":98,"total_committers":7,"mean_commits":14.0,"dds":0.2142857142857143,"last_synced_commit":"910cfb6fd33235466a963c7f232b9e2b9bd24ca2"},"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cysharp%2Fcsbindgen","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cysharp%2Fcsbindgen/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cysharp%2Fcsbindgen/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Cysharp%2Fcsbindgen/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Cysharp","download_url":"https://codeload.github.com/Cysharp/csbindgen/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248670513,"owners_count":21142896,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T19:02:01.846Z","updated_at":"2025-04-13T05:33:31.948Z","avatar_url":"https://github.com/Cysharp.png","language":"Rust","funding_links":[],"categories":["Rust","Development tools","FFI Bindings","Uncategorized"],"sub_categories":["FFI","Uncategorized"],"readme":"# csbindgen\n[![Crates](https://img.shields.io/crates/v/csbindgen.svg)](https://crates.io/crates/csbindgen) [![Api Rustdoc](https://img.shields.io/badge/api-rustdoc-blue)](https://docs.rs/csbindgen)\n\nGenerate C# FFI from Rust for automatically brings native code and C native library to .NET and Unity.\n\nAutomatically generates C# `DllImport` code from Rust `extern \"C\" fn` code. Whereas DllImport defaults to the Windows calling convention and requires a lot of configuration for C calls, csbindgen generates code optimized for \"Cdecl\" calls. Also .NET and Unity have different callback invocation methods (.NET uses function pointers, while Unity uses MonoPInvokeCallback), but you can output code for either by configuration.\n\nWhen used with Rust's excellent C integration, you can also bring C libraries into C#.\n\nThere are usually many pains involved in using the C Library with C#. Not only is it difficult to create bindings, but cross-platform builds are very difficult. In this day and age, you have to build for multiple platforms and architectures, windows, osx, linux, android, ios, each with x64, x86, arm.\n\n[Rust](https://www.rust-lang.org/) has an excellent toolchain for cross-platform builds, as well as [cc crate](https://crates.io/crates/cc), [cmake crate](https://crates.io/crates/cmake) allow C source code to be integrated into the build. And [rust-bindgen](https://crates.io/crates/bindgen), which generates bindings from `.h`, is highly functional and very stable.\n\ncsbindgen can easily bring native C libraries into C# through Rust. csbindgen generates Rust extern code and C# DllImport code to work with C# from code generated from C by bindgen. With cc crate or cmake crate, C code is linked to the single rust native library.\n\nshowcase:\n* [lz4_bindgen.cs](https://github.com/Cysharp/csbindgen/blob/47fd97eb379beeb278d7546c6d0b9a404b28fbd1/dotnet-sandbox/lz4_bindgen.cs) : [LZ4](https://github.com/lz4/lz4) compression library C# binding\n* [zstd_bindgen.cs](https://github.com/Cysharp/csbindgen/blob/47fd97eb379beeb278d7546c6d0b9a404b28fbd1/dotnet-sandbox/zstd_bindgen.cs) : [Zstandard](https://github.com/facebook/zstd) compression library C# binding\n* [quiche_bindgen.cs](https://github.com/Cysharp/csbindgen/blob/47fd97eb379beeb278d7546c6d0b9a404b28fbd1/dotnet-sandbox/quiche_bindgen.cs) : [cloudflare/quiche](https://github.com/cloudflare/quiche) QUIC and HTTP/3 library C# binding\n* [bullet3_bindgen.cs](https://github.com/Cysharp/csbindgen/blob/47fd97eb379beeb278d7546c6d0b9a404b28fbd1/dotnet-sandbox/bullet3_bindgen.cs) : [Bullet Physics SDK](https://github.com/bulletphysics/bullet3) C# binding\n* [sqlite3_bindgen.cs](https://github.com/Cysharp/csbindgen/blob/47fd97eb379beeb278d7546c6d0b9a404b28fbd1/dotnet-sandbox/sqlite3_bindgen.cs) : [SQLite](https://www.sqlite.org/index.html) C# binding\n* [Cysharp/YetAnotherHttpHandler](https://github.com/Cysharp/YetAnotherHttpHandler) : brings the power of HTTP/2 (and gRPC) to Unity and .NET Standard\n* [Cysharp/MagicPhysX](https://github.com/Cysharp/MagicPhysX) : .NET PhysX 5 binding to all platforms(win, osx, linux)\n\nGetting Started\n---\nInstall on `Cargo.toml` as `build-dependencies` and set up `bindgen::Builder` on `build.rs`.\n\n```toml\n[package]\nname = \"example\"\nversion = \"0.1.0\"\n\n[lib]\ncrate-type = [\"cdylib\"]\n\n[build-dependencies]\ncsbindgen = \"1.8.0\"\n```\n\n### Rust to C#.\n\nYou can bring Rust FFI code to C#.\n\n```rust\n// lib.rs, simple FFI code\n#[no_mangle]\npub extern \"C\" fn my_add(x: i32, y: i32) -\u003e i32 {\n    x + y\n}\n```\n\nSetup csbindgen code to `build.rs`.\n\n```rust\nfn main() {\n    csbindgen::Builder::default()\n        .input_extern_file(\"lib.rs\")\n        .csharp_dll_name(\"example\")\n        .generate_csharp_file(\"../dotnet/NativeMethods.g.cs\")\n        .unwrap();\n}\n```\n\n`csharp_dll_name` is for specifying `[DllImport({DLL_NAME}, ...)]` on the C# side, which should match the name of the dll binary.\nSee [#library-loading](#library-loading) section for how to resolve the dll file path.\n\n\u003e [!NOTE]\n\u003e In this example, the value of `csharp_dll_name` is output by the Rust project you set up. \n\u003e In the above, `package.name` in the Cargo.toml is set to \"example\". By default, the following binaries should be output to the `target/` folder of the Rust project.\n\u003e  - Windows: example.dll\n\u003e  - Linux: libexample.so\n\u003e  - macOS: libexample.dylib\n\u003e\n\u003e The filename without the extension should be specified to DllImport. Be careful that by default, rust compiler prefixes filenames with \"lib\" in some environments.\n\u003e So if you want to try this example as is on macOS, `csharp_dll_name` would be \"libexample\".\n\nThen, let's run `cargo build` it will generate this C# code.\n\n```csharp\n// NativeMethods.g.cs\nusing System;\nusing System.Runtime.InteropServices;\n\nnamespace CsBindgen\n{\n    internal static unsafe partial class NativeMethods\n    {\n        const string __DllName = \"example\";\n\n        [DllImport(__DllName, EntryPoint = \"my_add\", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]\n        public static extern int my_add(int x, int y);\n    }\n}\n```\n\n### C (to Rust) to C#\n\nFor example, build [lz4](https://github.com/lz4/lz4) compression library.\n\n```rust\n// using bindgen, generate binding code\nbindgen::Builder::default()\n    .header(\"c/lz4/lz4.h\")\n    .generate().unwrap()\n    .write_to_file(\"lz4.rs\").unwrap();\n\n// using cc, build and link c code\ncc::Build::new().file(\"lz4.c\").compile(\"lz4\");\n\n// csbindgen code, generate both rust ffi and C# dll import\ncsbindgen::Builder::default()\n    .input_bindgen_file(\"lz4.rs\")            // read from bindgen generated code\n    .rust_file_header(\"use super::lz4::*;\")     // import bindgen generated modules(struct/method)\n    .csharp_entry_point_prefix(\"csbindgen_\") // adjust same signature of rust method and C# EntryPoint\n    .csharp_dll_name(\"liblz4\")\n    .generate_to_file(\"lz4_ffi.rs\", \"../dotnet/NativeMethods.lz4.g.cs\")\n    .unwrap();\n```\n\nIt will generates like these code.\n\n```rust\n// lz4_ffi.rs\n\n#[allow(unused)]\nuse ::std::os::raw::*;\n\nuse super::lz4::*;\n\n#[no_mangle]\npub unsafe extern \"C\" fn csbindgen_LZ4_compress_default(src: *const c_char, dst: *mut c_char, srcSize:  c_int, dstCapacity:  c_int) -\u003e  c_int\n{\n    LZ4_compress_default(src, dst, srcSize, dstCapacity)\n}\n```\n\n```csharp\n// NativeMethods.lz4.g.cs\n\nusing System;\nusing System.Runtime.InteropServices;\n\nnamespace CsBindgen\n{\n    internal static unsafe partial class NativeMethods\n    {\n        const string __DllName = \"liblz4\";\n\n        [DllImport(__DllName, EntryPoint = \"csbindgen_LZ4_compress_default\", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]\n        public static extern int LZ4_compress_default(byte* src, byte* dst, int srcSize, int dstCapacity);\n    }\n}\n```\n\nFinally import generated module on `lib.rs`.\n\n```rust\n// lib.rs, import generated codes.\n#[allow(dead_code)]\n#[allow(non_snake_case)]\n#[allow(non_camel_case_types)]\n#[allow(non_upper_case_globals)]\nmod lz4;\n\n#[allow(dead_code)]\n#[allow(non_snake_case)]\n#[allow(non_camel_case_types)]\nmod lz4_ffi;\n```\n\n\n\n## Builder options(configure template)\n\n### Builder options: Rust to C#\n\nRust to C#, use the `input_extern_file` -\u003e setup options -\u003e `generate_csharp_file`.\n\n```rust\ncsbindgen::Builder::default()\n    .input_extern_file(\"src/lib.rs\")        // required\n    .csharp_dll_name(\"mynativelib\")         // required\n    .csharp_class_name(\"NativeMethods\")     // optional, default: NativeMethods\n    .csharp_namespace(\"CsBindgen\")          // optional, default: CsBindgen\n    .csharp_class_accessibility(\"internal\") // optional, default: internal\n    .csharp_entry_point_prefix(\"\")          // optional, default: \"\"\n    .csharp_method_prefix(\"\")               // optional, default: \"\"\n    .csharp_use_function_pointer(true)      // optional, default: true\n    .csharp_disable_emit_dll_name(false)    // optional, default: false\n    .csharp_imported_namespaces(\"MyLib\")    // optional, default: empty\n    .csharp_generate_const_filter (|_|false) // optional, default: `|_|false`\n    .csharp_dll_name_if(\"UNITY_IOS \u0026\u0026 !UNITY_EDITOR\", \"__Internal\") // optional, default: \"\"\n    .csharp_type_rename(|rust_type_name| match rust_type_name {     // optional, default: `|x| x`\n        \"FfiConfiguration\" =\u003e \"Configuration\".into(),\n        _ =\u003e x,\n    })\n    .generate_csharp_file(\"../dotnet-sandbox/NativeMethods.cs\")     // required\n    .unwrap();\n```\n\n`csharp_*` configuration will be embedded in the placeholder of the output file.\n\n```csharp\nusing System;\nusing System.Runtime.InteropServices;\nusing {csharp_imported_namespaces};\n\nnamespace {csharp_namespace}\n{\n    {csharp_class_accessibility} static unsafe partial class {csharp_class_name}\n    {\n#if {csharp_dll_name_if(if_symbol,...)}\n        const string __DllName = \"{csharp_dll_name_if(...,if_dll_name)}\";\n#else\n        const string __DllName = \"{csharp_dll_name}\";\n#endif\n    }\n\n    {csharp_generate_const_filter}\n\n    [DllImport(__DllName, EntryPoint = \"{csharp_entry_point_prefix}LZ4_versionNumber\", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]\n    public static extern int {csharp_method_prefix}LZ4_versionNumber();\n}\n```\n\n`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.\n\n`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.\n\n`csharp_generate_const_filter` is optional, if set a filter fun, then generate filter C# `const` field from Rust `const`.\n\n`input_extern_file` and `input_bindgen_file` allow mulitple call, if you need to add dependent struct, use this.\n\n```rust\ncsbindgen::Builder::default()\n    .input_extern_file(\"src/lib.rs\")\n    .input_extern_file(\"src/struct_modules.rs\")\n    .generate_csharp_file(\"../dotnet-sandbox/NativeMethods.cs\");\n```\n\nalso `csharp_imported_namespaces` can call multiple times.\n\n### Unity Callback\n\n`csharp_use_function_pointer` configures how generate function pointer. The default is to generate a `delegate*`, but Unity does not support it; setting it to `false` will generate a `Func/Action` that can be used with `MonoPInvokeCallback`.\n\n```csharp\n// true(default) generates delegate*\n[DllImport(__DllName, EntryPoint = \"callback_test\", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]\npublic static extern int callback_test(delegate* unmanaged[Cdecl]\u003cint, int\u003e cb);\n\n// You can define like this callback method.\n[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]\nstatic int Method(int x) =\u003e x * x;\n\n// And use it.\ncallback_test(\u0026Method);\n\n// ---\n\n// false will generates {method_name}_{parameter_name}_delegate, it is useful for Unity\n[UnmanagedFunctionPointer(CallingConvention.Cdecl)]\npublic delegate int callback_test_cb_delegate(int a);\n\n[DllImport(__DllName, EntryPoint = \"callback_test\", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]\npublic static extern int callback_test(callback_test_cb_delegate cb);\n\n// Unity can define callback method as MonoPInvokeCallback\n[MonoPInvokeCallback(typeof(NativeMethods.callback_test_cb_delegate))]\nstatic int Method(int x) =\u003e x * x;\n\n// And use it.\ncallback_test(Method);\n```\n\n### Builder options: C (to Rust) to C#\n\n`input_bindgen_file` -\u003e setup options -\u003e `generate_to_file` to use C to C# workflow.\n\n```rust\ncsbindgen::Builder::default()\n    .input_bindgen_file(\"src/lz4.rs\")             // required\n    .method_filter(|x| { x.starts_with(\"LZ4\") } ) // optional, default: |x| !x.starts_with('_')\n    .rust_method_prefix(\"csbindgen_\")             // optional, default: \"csbindgen_\"\n    .rust_file_header(\"use super::lz4::*;\")       // optional, default: \"\"\n    .rust_method_type_path(\"lz4\")                 // optional, default: \"\"\n    .csharp_dll_name(\"lz4\")                       // required\n    .csharp_class_name(\"NativeMethods\")           // optional, default: NativeMethods\n    .csharp_namespace(\"CsBindgen\")                // optional, default: CsBindgen\n    .csharp_class_accessibility(\"internal\")       // optional, default: internal\n    .csharp_entry_point_prefix(\"csbindgen_\")      // required, you must set same as rust_method_prefix\n    .csharp_method_prefix(\"\")                     // optional, default: \"\"\n    .csharp_use_function_pointer(true)            // optional, default: true\n    .csharp_imported_namespaces(\"MyLib\")          // optional, default: empty\n    .csharp_generate_const_filter(|_|false)       // optional, default:|_|false\n    .csharp_dll_name_if(\"UNITY_IOS \u0026\u0026 !UNITY_EDITOR\", \"__Internal\")         // optional, default: \"\"\n    .csharp_type_rename(|rust_type_name| match rust_type_name.as_str() {    // optional, default: `|x| x`\n        \"FfiConfiguration\" =\u003e \"Configuration\".into(),\n        _ =\u003e x,\n    })\n    .csharp_file_header(\"#if !UNITY_WEBGL\")       // optional, default: \"\"\n    .csharp_file_footer(\"#endif\")                 // optional, default: \"\"\n    .generate_to_file(\"src/lz4_ffi.rs\", \"../dotnet-sandbox/lz4_bindgen.cs\") // required\n    .unwrap();\n```\n\nIt will be embedded in the placeholder of the output file.\n\n```rust\n#[allow(unused)]\nuse ::std::os::raw::*;\n\n{rust_file_header}\n\n#[no_mangle]\npub unsafe extern \"C\" fn {rust_method_prefix}LZ4_versionNumber() -\u003e  c_int\n{\n    {rust_method_type_path}::LZ4_versionNumber()\n}\n```\n\n`csharp_*` option template is same as Rust to C#, see above documentation.\n\nAdjust `rust_file_header` for match your module configuration, recommend to use `::*`, also using `rust_method_type_path` that add explicitly resolve path.\n\n`method_filter` allows you to specify which methods to exclude; if unspecified, methods prefixed with `_` are excluded by default. C libraries are usually published with a specific prefix. For example, [LZ4](https://github.com/lz4/lz4) is `LZ4`, [ZStandard](https://github.com/facebook/zstd) is `ZSTD_`, [quiche](https://github.com/cloudflare/quiche) is `quiche_`, [Bullet Physics SDK](https://github.com/bulletphysics/bullet3) is `b3`.\n\n`rust_method_prefix` and `csharp_method_prefix` or `csharp_entry_point_prefix` must be adjusted to match the method name to be called.\n\nLibrary Loading\n---\nIf the file path to be loaded needs to be changed depending on the operating system, the following load code can be used.\n\n```csharp\ninternal static unsafe partial class NativeMethods\n{\n    // https://docs.microsoft.com/en-us/dotnet/standard/native-interop/cross-platform\n    // Library path will search\n    // win =\u003e __DllName, __DllName.dll\n    // linux, osx =\u003e __DllName.so, __DllName.dylib\n\n    static NativeMethods()\n    {\n        NativeLibrary.SetDllImportResolver(typeof(NativeMethods).Assembly, DllImportResolver);\n    }\n\n    static IntPtr DllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)\n    {\n        if (libraryName == __DllName)\n        {\n            var path = \"runtimes/\";\n            var extension = \"\";\n\n            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))\n            {\n                path += \"win-\";\n                extension = \".dll\";\n            }\n            else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))\n            {\n                path += \"osx-\";\n                extension = \".dylib\";\n            }\n            else\n            {\n                path += \"linux-\";\n                extension = \".so\";\n            }\n\n            if (RuntimeInformation.ProcessArchitecture == Architecture.X86)\n            {\n                path += \"x86\";\n            }\n            else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)\n            {\n                path += \"x64\";\n            }\n            else if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)\n            {\n                path += \"arm64\";\n            }\n\n            path += \"/native/\" + __DllName + extension;\n\n            return NativeLibrary.Load(Path.Combine(AppContext.BaseDirectory, path), assembly, searchPath);\n        }\n\n        return IntPtr.Zero;\n    }\n}\n```\n\nIf Unity, configure Platform settings in each native library's inspector.\n\nGrouping Extension Methods\n---\nIn 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.\n\nInstall csbindgen from NuGet, and specify [GroupedNativeMethods] for the partial class of the generated extension methods.\n\n\u003e PM\u003e Install-Package [csbindgen](https://www.nuget.org/packages/csbindgen)\n\n```csharp\n// create new file and write same type-name with same namespace\nnamespace CsBindgen\n{\n    // append `GroupedNativeMethods` attribute\n    [GroupedNativeMethods]\n    internal static unsafe partial class NativeMethods\n    {\n    }\n}\n```\n\n```csharp\n// original methods\n[DllImport(__DllName, EntryPoint = \"counter_context_insert\", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]\npublic static extern void counter_context_insert(counter_context* context, int value);\n\n// generated methods\npublic static void Insert(this ref global::CsBindgen.counter_context @context, int @value)\n\n// ----\n\ncounter_context* context = NativeMethods.create_counter_context();\n\n// standard style\nNativeMethods.counter_context_insert(context, 10);\n\n// generated style\ncontext-\u003eInsert(10);\n```\n\n`GroupedNativeMethods` has four configuration parameters.\n\n```csharp\npublic GroupedNativeMethodsAttribute(\n    string removePrefix = \"\",\n    string removeSuffix = \"\",\n    bool removeUntilTypeName = true,\n    bool fixMethodName = true)\n```\n\nThe convention for function names when using this feature is as follows:\n- The first argument must be a pointer type.\n- `removeUntilTypeName` will remove until find type-name in method-name. \n  - For example `foo_counter_context_insert(countext_context* foo)` -\u003e `Insert`. \n  - As a result, it is recommended to use a naming convention where the same type name is placed immediately before the verb. \n\nType Marshalling\n---\nRust types will map these C# types.\n\n| Rust | C# |\n| ---- | -- |\n| `i8` | `sbyte` |\n| `i16` | `short` |\n| `i32` | `int` |\n| `i64` | `long` |\n| `i128` | `Int128` |\n| `isize` | `nint` |\n| `u8` | `byte` |\n| `u16` | `ushort` |\n| `u32` | `uint` |\n| `u64` | `ulong` |\n| `u128` | `UInt128` |\n| `usize` | `nuint` |\n| `f32` | `float` |\n| `f64` | `double` |\n| `bool` | `[MarshalAs(UnmanagedType.U1)]bool` |\n| `char` | `uint` |\n| `()` | `void` |\n| `c_char` | `byte` |\n| `c_schar` | `sbyte` |\n| `c_uchar` | `byte` |\n| `c_short` | `short` |\n| `c_ushort` | `ushort` |\n| `c_int` | `int` |\n| `c_uint` | `uint` |\n| `c_long` | `CLong` |\n| `c_ulong` | `CULong` |\n| `c_longlong` | `long` |\n| `c_ulonglong` | `ulong` |\n| `c_float` | `float` |\n| `c_double` | `double` |\n| `c_void` | `void` |\n| `CString` | `sbyte` |\n| `NonZeroI8` | `sbyte` |\n| `NonZeroI16` | `short` |\n| `NonZeroI32` | `int` |\n| `NonZeroI64` | `long` |\n| `NonZeroI128` | `Int128` |\n| `NonZeroIsize` | `nint` |\n| `NonZeroU8` | `byte` |\n| `NonZeroU16` | `ushort` |\n| `NonZeroU32` | `uint` |\n| `NonZeroU64` | `ulong` |\n| `NonZeroU128` | `UInt128` |\n| `NonZeroUsize` | `nuint` |\n| `#[repr(C)]Struct` | `[StructLayout(LayoutKind.Sequential)]Struct` |\n| `#[repr(C)]Union` | `[StructLayout(LayoutKind.Explicit)]Struct` |\n| `#[repr(u*/i*)]Enum` | `Enum` |\n| [bitflags!](https://crates.io/crates/bitflags) | `[Flags]Enum` |\n| `extern \"C\" fn` | `delegate* unmanaged[Cdecl]\u003c\u003e` or `Func\u003c\u003e/Action\u003c\u003e` |\n| `Option\u003cextern \"C\" fn\u003e` | `delegate* unmanaged[Cdecl]\u003c\u003e` or `Func\u003c\u003e/Action\u003c\u003e` |\n| `*mut T` | `T*` |\n| `*const T` | `T*` |\n| `*mut *mut T` | `T**` |\n| `*const *const T` | `T**` |\n| `*mut *const T` | `T**` |\n| `*const *mut T` | `T**` |\n| `\u0026T` | `T*` |\n| `\u0026mut T` | `T*` |\n| `\u0026\u0026T` | `T**` |\n| `\u0026*mut T` | `T**` |\n| `NonNull\u003cT\u003e` | `T*` |\n| `Box\u003cT\u003e` | `T*` |\n\ncsbindgen 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.\n\nOlder C# version do not support `nint` and `nuint`. You can use `csharp_use_nint_types` to use `IntPtr` and `UIntPtr` in their place:\n\n```rust\n    csbindgen::Builder::default()\n        .input_extern_file(\"lib.rs\")\n        .csharp_dll_name(\"nativelib\")\n        .generate_csharp_file(\"../dotnet/NativeMethods.g.cs\")\n        .csharp_use_nint_types(false)\n        .unwrap();\n```\n\n`c_long` and `c_ulong` will convert to [CLong](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.clong), [CULong](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.culong) struct after .NET 6. If you want to convert in Unity, you will need Shim.\n\n\n\n```csharp\n// Currently Unity is .NET Standard 2.1 so does not exist CLong and CULong\nnamespace System.Runtime.InteropServices\n{\n    internal struct CLong\n    {\n        public int Value; // #if Windows = int, Unix x32 = int, Unix x64 = long\n    }\n\n    internal struct CULong\n    {\n        public uint Value; // #if Windows = uint, Unix x32 = uint, Unix x64 = ulong\n    }\n}\n```\n\n### Struct\n\ncsbindgen supports `Struct`, you can define `#[repr(C)]` struct on method parameter or return value.\n\n```rust\n// If you define this struct...\n#[repr(C)]\npub struct MyVector3 {\n    pub x: f32,\n    pub y: f32,\n    pub z: f32,\n}\n\n#[no_mangle]\npub extern \"C\" fn pass_vector3(v3: MyVector3) {\n    println!(\"{}, {}, {}\", v3.x, v3.y, v3.z);\n}\n```\n\n```csharp\n// csbindgen generates this C# struct\n[StructLayout(LayoutKind.Sequential)]\ninternal unsafe partial struct MyVector3\n{\n    public float x;\n    public float y;\n    public float z;\n}\n```\n\nAlso supports tuple struct, it will generate `Item*` fields in C#.\n\n```\n#[repr(C)]\npub struct MyIntVec3(i32, i32, i32);\n```\n\n```csharp\n[StructLayout(LayoutKind.Sequential)]\ninternal unsafe partial struct MyIntVec3\n{\n    public int Item1;\n    public int Item2;\n    public int Item3;\n}\n```\n\nIt 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.\n\n```\n// 0-byte in Rust\n#[repr(C)]\npub struct MyContext;\n```\n\n```csharp\n// 1-byte in C#\n[StructLayout(LayoutKind.Sequential)]\ninternal unsafe partial struct MyContext\n{\n}\n```\n\n### Union\n\n`Union` will generate `[FieldOffset(0)]` struct.\n\n```rust\n#[repr(C)]\npub union MyUnion {\n    pub foo: i32,\n    pub bar: i64,\n}\n\n#[no_mangle]\npub extern \"C\" fn return_union() -\u003e MyUnion {\n    MyUnion { bar: 53 }\n}\n```\n\n```csharp\n[StructLayout(LayoutKind.Explicit)]\ninternal unsafe partial struct MyUnion\n{\n    [FieldOffset(0)]\n    public int foo;\n    [FieldOffset(0)]\n    public long bar;\n}\n```\n\n### Enum\n\n`#[repr(i*)]` or `#[repr(u*)]` defined `Enum` is supported.\n\n```rust\n#[repr(u8)]\npub enum ByteEnum {\n    A = 1,\n    B = 2,\n    C = 10,\n}\n```\n\n```csharp\ninternal enum ByteTest : byte\n{\n    A = 1,\n    B = 2,\n    C = 10,\n}\n```\n\n### bitflags Enum\n\ncsbindgen supports [bitflags](https://crates.io/crates/bitflags) crate.\n\n```rust\nbitflags! {\n    #[repr(C)]\n    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]\n    struct EnumFlags: u32 {\n        const A = 0b00000001;\n        const B = 0b00000010;\n        const C = 0b00000100;\n        const ABC = Self::A.bits() | Self::B.bits() | Self::C.bits();\n    }\n}\n```\n\n```csharp\n[Flags]\ninternal enum EnumFlags : uint\n{\n    A = 0b00000001,\n    B = 0b00000010,\n    C = 0b00000100,\n    ABC = A | B | C,\n}\n```\n\n### Function\n\nYou can receive, return function to/from C#.\n\n```rust\n#[no_mangle]\npub extern \"C\" fn csharp_to_rust(cb: extern \"C\" fn(x: i32, y: i32) -\u003e i32) {\n    let sum = cb(10, 20); // invoke C# method\n    println!(\"{sum}\");\n}\n\n#[no_mangle]\npub extern \"C\" fn rust_to_csharp() -\u003e extern fn(x: i32, y: i32) -\u003e i32 {\n    sum // return rust method\n}\n\nextern \"C\" fn sum(x:i32, y:i32) -\u003e i32 {\n    x + y\n}\n```\n\nIn default, csbindgen generates `extern \"C\" fn` as `delegate* unmanaged[Cdecl]\u003c\u003e`.\n\n```csharp\n[DllImport(__DllName, EntryPoint = \"csharp_to_rust\", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]\npublic static extern void csharp_to_rust(delegate* unmanaged[Cdecl]\u003cint, int, int\u003e cb);\n\n[DllImport(__DllName, EntryPoint = \"rust_to_csharp\", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]\npublic static extern delegate* unmanaged[Cdecl]\u003cint, int, int\u003e rust_to_csharp();\n```\n\nIt can use in C# like this.\n\n```csharp\n// C# -\u003e Rust, pass static UnmanagedCallersOnly method with `\u0026`\n[UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })]\nstatic int Sum(int x, int y) =\u003e x + y;\n\nNativeMethods.csharp_to_rust(\u0026Sum);\n\n// Rust -\u003e C#, get typed delegate*\nvar f = NativeMethods.rust_to_csharp();\n\nvar v = f(20, 30);\nConsole.WriteLine(v); // 50\n```\n\n\u003e Unity can not use C# 9.0 function pointer, csbindgen has to use `MonoPInvokeCallback` options. see: [Unity Callback](#unity-callback) section.\n\nRust FFI suppots `Option\u003cfn\u003e`, it can receive null pointer.\n\n```rust\n#[no_mangle]\npub extern \"C\" fn nullable_callback_test(cb: Option\u003cextern \"C\" fn(a: i32) -\u003e i32\u003e) -\u003e i32 {\n    match cb {\n        Some(f) =\u003e f(100),\n        None =\u003e -1,\n    }\n}\n```\n\n```csharp\nvar v = NativeMethods.nullable_callback_test(null); // -1\n```\n\n### Pointer\n\nAllocated Rust memory in heap can send to C# via pointer and `Box::into_raw` and `Box::from_raw`.\n\n```rust\n#[no_mangle]\npub extern \"C\" fn create_context() -\u003e *mut Context {\n    let ctx = Box::new(Context { foo: true });\n    Box::into_raw(ctx)\n}\n\n#[no_mangle]\npub extern \"C\" fn delete_context(context: *mut Context) {\n    unsafe { Box::from_raw(context) };\n}\n\n#[repr(C)]\npub struct Context {\n    pub foo: bool,\n    pub bar: i32,\n    pub baz: u64\n}\n```\n\n```csharp\nvar context = NativeMethods.create_context();\n\n// do anything...\n\nNativeMethods.delete_context(context);\n```\n\nYou 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#.\n\nIf you want to pass a non FFI Safe struct reference, csbindgen generates empty C# struct.\n\n```rust\n#[no_mangle]\npub extern \"C\" fn create_counter_context() -\u003e *mut CounterContext {\n    let ctx = Box::new(CounterContext {\n        set: HashSet::new(),\n    });\n    Box::into_raw(ctx)\n}\n\n#[no_mangle]\npub unsafe extern \"C\" fn insert_counter_context(context: *mut CounterContext, value: i32) {\n    let mut counter = Box::from_raw(context);\n    counter.set.insert(value);\n    Box::into_raw(counter);\n}\n\n#[no_mangle]\npub unsafe extern \"C\" fn delete_counter_context(context: *mut CounterContext) {\n    let counter = Box::from_raw(context);\n    for value in counter.set.iter() {\n        println!(\"counter value: {}\", value)\n    }\n}\n\n// no repr(C)\npub struct CounterContext {\n    pub set: HashSet\u003ci32\u003e,\n}\n```\n\n```csharp\n// csbindgen generates this handler type\n[StructLayout(LayoutKind.Sequential)]\ninternal unsafe partial struct CounterContext\n{\n}\n\n// You can hold pointer instance\nCounterContext* ctx = NativeMethods.create_counter_context();\n    \nNativeMethods.insert_counter_context(ctx, 10);\nNativeMethods.insert_counter_context(ctx, 20);\n\nNativeMethods.delete_counter_context(ctx);\n```\n\nIn this case, recommed to use with [Grouping Extension Methods](#grouping-extension-methods).\n\nIf you want to pass null-pointer, in rust side, convert to Option by `as_ref()`.\n\n```rust\n#[no_mangle]\npub unsafe extern \"C\" fn null_pointer_test(p: *const u8) {\n    let ptr = unsafe { p.as_ref() };\n    match ptr {\n        Some(p2) =\u003e print!(\"pointer address: {}\", *p2),\n        None =\u003e println!(\"null pointer!\"),\n    };\n}\n```\n\n```csharp\n// in C#, invoke by null.\nNativeMethods.null_pointer_test(null);\n```\n\n### String and Array(Span)\n\nRust's String, Array(Vec) and C#'s String, Array is different thing. Since it cannot be shared, pass it with a pointer and handle it with slice(Span) or materialize it if necessary.\n\n`CString` is null-terminated string. It can send by `*mut c_char` and received as `byte*` in C#.\n\n```rust\n#[no_mangle]\npub extern \"C\" fn alloc_c_string() -\u003e *mut c_char {\n    let str = CString::new(\"foo bar baz\").unwrap();\n    str.into_raw()\n}\n\n#[no_mangle]\npub unsafe extern \"C\" fn free_c_string(str: *mut c_char) {\n    unsafe { CString::from_raw(str) };\n}\n```\n\n```csharp\n// null-terminated `byte*` or sbyte* can materialize by new String()\nvar cString = NativeMethods.alloc_c_string();\nvar str = new String((sbyte*)cString);\nNativeMethods.free_c_string(cString);\n```\n\nRust's String is UTF-8(`Vec\u003cu8\u003e`) but C# String is UTF-16. Andalso, `Vec\u003c\u003e` can not send to C# so require to convert pointer and control memory manually. Here is the buffer manager for FFI.\n\n```rust\n#[repr(C)]\npub struct ByteBuffer {\n    ptr: *mut u8,\n    length: i32,\n    capacity: i32,\n}\n\nimpl ByteBuffer {\n    pub fn len(\u0026self) -\u003e usize {\n        self.length\n            .try_into()\n            .expect(\"buffer length negative or overflowed\")\n    }\n\n    pub fn from_vec(bytes: Vec\u003cu8\u003e) -\u003e Self {\n        let length = i32::try_from(bytes.len()).expect(\"buffer length cannot fit into a i32.\");\n        let capacity =\n            i32::try_from(bytes.capacity()).expect(\"buffer capacity cannot fit into a i32.\");\n\n        // keep memory until call delete\n        let mut v = std::mem::ManuallyDrop::new(bytes);\n\n        Self {\n            ptr: v.as_mut_ptr(),\n            length,\n            capacity,\n        }\n    }\n\n    pub fn from_vec_struct\u003cT: Sized\u003e(bytes: Vec\u003cT\u003e) -\u003e Self {\n        let element_size = std::mem::size_of::\u003cT\u003e() as i32;\n\n        let length = (bytes.len() as i32) * element_size;\n        let capacity = (bytes.capacity() as i32) * element_size;\n\n        let mut v = std::mem::ManuallyDrop::new(bytes);\n\n        Self {\n            ptr: v.as_mut_ptr() as *mut u8,\n            length,\n            capacity,\n        }\n    }\n\n    pub fn destroy_into_vec(self) -\u003e Vec\u003cu8\u003e {\n        if self.ptr.is_null() {\n            vec![]\n        } else {\n            let capacity: usize = self\n                .capacity\n                .try_into()\n                .expect(\"buffer capacity negative or overflowed\");\n            let length: usize = self\n                .length\n                .try_into()\n                .expect(\"buffer length negative or overflowed\");\n\n            unsafe { Vec::from_raw_parts(self.ptr, length, capacity) }\n        }\n    }\n\n    pub fn destroy_into_vec_struct\u003cT: Sized\u003e(self) -\u003e Vec\u003cT\u003e {\n        if self.ptr.is_null() {\n            vec![]\n        } else {\n            let element_size = std::mem::size_of::\u003cT\u003e() as i32;\n            let length = (self.length * element_size) as usize;\n            let capacity = (self.capacity * element_size) as usize;\n\n            unsafe { Vec::from_raw_parts(self.ptr as *mut T, length, capacity) }\n        }\n    }\n\n    pub fn destroy(self) {\n        drop(self.destroy_into_vec());\n    }\n}\n```\n\n```csharp\n// C# side span utility\npartial struct ByteBuffer\n{\n    public unsafe Span\u003cbyte\u003e AsSpan()\n    {\n        return new Span\u003cbyte\u003e(ptr, length);\n    }\n\n    public unsafe Span\u003cT\u003e AsSpan\u003cT\u003e()\n    {\n        return MemoryMarshal.CreateSpan(ref Unsafe.AsRef\u003cT\u003e(ptr), length / Unsafe.SizeOf\u003cT\u003e());\n    }\n}\n```\n\nWith `ByteBuffer`, you can send `Vec\u003c\u003e` to C#. the pattern for `String`, `Vec\u003cu8\u003e`, `Vec\u003ci32\u003e`, Rust -\u003e C# is as follows.\n\n```rust\n#[no_mangle]\npub extern \"C\" fn alloc_u8_string() -\u003e *mut ByteBuffer {\n    let str = format!(\"foo bar baz\");\n    let buf = ByteBuffer::from_vec(str.into_bytes());\n    Box::into_raw(Box::new(buf))\n}\n\n#[no_mangle]\npub unsafe extern \"C\" fn free_u8_string(buffer: *mut ByteBuffer) {\n    let buf = Box::from_raw(buffer);\n    // drop inner buffer, if you need String, use String::from_utf8_unchecked(buf.destroy_into_vec()) instead.\n    buf.destroy();\n}\n\n#[no_mangle]\npub extern \"C\" fn alloc_u8_buffer() -\u003e *mut ByteBuffer {\n    let vec: Vec\u003cu8\u003e = vec![1, 10, 100];\n    let buf = ByteBuffer::from_vec(vec);\n    Box::into_raw(Box::new(buf))\n}\n\n#[no_mangle]\npub unsafe extern \"C\" fn free_u8_buffer(buffer: *mut ByteBuffer) {\n    let buf = Box::from_raw(buffer);\n    // drop inner buffer, if you need Vec\u003cu8\u003e, use buf.destroy_into_vec() instead.\n    buf.destroy();\n}\n\n#[no_mangle]\npub extern \"C\" fn alloc_i32_buffer() -\u003e *mut ByteBuffer {\n    let vec: Vec\u003ci32\u003e = vec![1, 10, 100, 1000, 10000];\n    let buf = ByteBuffer::from_vec_struct(vec);\n    Box::into_raw(Box::new(buf))\n}\n\n#[no_mangle]\npub unsafe extern \"C\" fn free_i32_buffer(buffer: *mut ByteBuffer) {\n    let buf = Box::from_raw(buffer);\n    // drop inner buffer, if you need Vec\u003ci32\u003e, use buf.destroy_into_vec_struct::\u003ci32\u003e() instead.\n    buf.destroy();\n}\n```\n\n```csharp\nvar u8String = NativeMethods.alloc_u8_string();\nvar u8Buffer = NativeMethods.alloc_u8_buffer();\nvar i32Buffer = NativeMethods.alloc_i32_buffer();\ntry\n{\n    var str = Encoding.UTF8.GetString(u8String-\u003eAsSpan());\n    Console.WriteLine(str);\n\n    Console.WriteLine(\"----\");\n\n    var buffer = u8Buffer-\u003eAsSpan();\n    foreach (var item in buffer)\n    {\n        Console.WriteLine(item);\n    }\n\n    Console.WriteLine(\"----\");\n\n    var i32Span = i32Buffer-\u003eAsSpan\u003cint\u003e();\n    foreach (var item in i32Span)\n    {\n        Console.WriteLine(item);\n    }\n}\nfinally\n{\n    NativeMethods.free_u8_string(u8String);\n    NativeMethods.free_u8_buffer(u8Buffer);\n    NativeMethods.free_i32_buffer(i32Buffer);\n}\n```\n\nC# 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. \n\n```rust\n#[no_mangle]\npub unsafe extern \"C\" fn csharp_to_rust_string(utf16_str: *const u16, utf16_len: i32) {\n    let slice = std::slice::from_raw_parts(utf16_str, utf16_len as usize);\n    let str = String::from_utf16(slice).unwrap();\n    println!(\"{}\", str);\n}\n\n#[no_mangle]\npub unsafe extern \"C\" fn csharp_to_rust_utf8(utf8_str: *const u8, utf8_len: i32) {\n    let slice = std::slice::from_raw_parts(utf8_str, utf8_len as usize);\n    let str = String::from_utf8_unchecked(slice.to_vec());\n    println!(\"{}\", str);\n}\n\n\n#[no_mangle]\npub unsafe extern \"C\" fn csharp_to_rust_bytes(bytes: *const u8, len: i32) {\n    let slice = std::slice::from_raw_parts(bytes, len as usize);\n    let vec = slice.to_vec();\n    println!(\"{:?}\", vec);\n}\n```\n\n```csharp\nvar str = \"foobarbaz:あいうえお\"; // ENG:JPN(Unicode, testing for UTF16)\nfixed (char* p = str)\n{\n    NativeMethods.csharp_to_rust_string((ushort*)p, str.Length);\n}\n\nvar str2 = Encoding.UTF8.GetBytes(\"あいうえお:foobarbaz\");\nfixed (byte* p = str2)\n{\n    NativeMethods.csharp_to_rust_utf8(p, str2.Length);\n}\n\nvar bytes = new byte[] { 1, 10, 100, 255 };\nfixed (byte* p = bytes)\n{\n    NativeMethods.csharp_to_rust_bytes(p, bytes.Length);\n}\n```\n\nAgain, the important thing is that memory allocated in Rust must release in Rust and memory allocated in C# must release in C#.\n\nBuild Tracing\n---\ncsbindgen silently skips over any method with a non-generatable type. If you build with `cargo build -vv`, you will get thse message if not geneated.\n\n* `csbindgen can't handle this parameter type so ignore generate, method_name: {} parameter_name: {}`\n* `csbindgen can't handle this return type so ignore generate, method_name: {}`\n\n\nNon-Generatable method: C variadic/variable arguments method\n---\ncsbindgen doesn't handle C's variadic arguments, which causes undefined behaviors, because this feature is not stable both in C# and Rust.\nThere is a `__arglist` keyword for C's variadic arguments in C#. [`__arglist` has many problems except Windows environment.](https://github.com/dotnet/runtime/issues/48796)\n[There is an issue about C's variadic arguments in Rust.](https://github.com/rust-lang/rust/issues/44930)\n\nLicense\n---\nThis library is licensed under the MIT License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCysharp%2Fcsbindgen","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FCysharp%2Fcsbindgen","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCysharp%2Fcsbindgen/lists"}