MCPcopy Index your code
hub / github.com/Cysharp/csbindgen

github.com/Cysharp/csbindgen @1.9.8

Chat with this repo
repository ↗ · DeepWiki ↗ · release 1.9.8 ↗ · + Follow
9,449 symbols 13,076 edges 134 files 3,067 documented · 32% updated 42d ago1.9.8 · 2026-05-21★ 9312 open issues
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

csbindgen

Crates Api Rustdoc

Generate C# FFI from Rust for automatically brings native code and C native library to .NET and Unity.

Automatically 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.

When used with Rust's excellent C integration, you can also bring C libraries into C#. rust-bindgen is a proven .h parsing library with extensive track record. By using it together with csbindgen, you can generate code to call C code. In this case, you don't need a Rust native library - all you need is the C/C++ binary and C#.

Additionally, Rust has an excellent toolchain for cross-platform builds, and the cc crate and cmake crate allow C source code to be integrated into the build. When used together with these, it also supports consolidating multiple native calls and simplifying them into Rust calls.

showcase: * lz4_bindgen.cs : LZ4 compression library C# binding * zstd_bindgen.cs : Zstandard compression library C# binding * quiche_bindgen.cs : cloudflare/quiche QUIC and HTTP/3 library C# binding * bullet3_bindgen.cs : Bullet Physics SDK C# binding * sqlite3_bindgen.cs : SQLite C# binding * Cysharp/YetAnotherHttpHandler : brings the power of HTTP/2 (and gRPC) to Unity and .NET Standard * Cysharp/NativeCompressions : LZ4 and Zstandard binding to Unity and .NET Standard * Cysharp/MagicPhysX : .NET PhysX 5 binding to all platforms(win, osx, linux)

Getting Started

Install on Cargo.toml as build-dependencies and set up bindgen::Builder on build.rs.

[package]
name = "example"
version = "0.1.0"

[lib]
crate-type = ["cdylib"]

[build-dependencies]
csbindgen = "1.9.6"

Rust to C#.

You can bring Rust FFI code to C#.

// lib.rs, simple FFI code
#[no_mangle]
pub extern "C" fn my_add(x: i32, y: i32) -> i32 {
    x + y
}

Setup csbindgen code to build.rs, use builder and input_extern_file.

fn main() {
    csbindgen::Builder::default()
        .input_extern_file("lib.rs")
        .csharp_dll_name("example")
        .generate_csharp_file("../dotnet/NativeMethods.g.cs")
        .unwrap();
}

csharp_dll_name is for specifying [DllImport({DLL_NAME}, ...)] on the C# side, which should match the name of the dll binary. See #library-loading section for how to resolve the dll file path.

[!NOTE] In this example, the value of csharp_dll_name is output by the Rust project you set up. 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. - Windows: example.dll - Linux: libexample.so - macOS: libexample.dylib

The filename without the extension should be specified to DllImport. Be careful that by default, rust compiler prefixes filenames with "lib" in some environments. So if you want to try this example as is on macOS, csharp_dll_name would be "libexample".

Then, let's run cargo build it will generate this C# code.

// NativeMethods.g.cs
using System;
using System.Runtime.InteropServices;

namespace CsBindgen
{
    internal static unsafe partial class NativeMethods
    {
        const string __DllName = "example";

        [DllImport(__DllName, EntryPoint = "my_add", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
        public static extern int my_add(int x, int y);
    }
}

C (to Rust) to C

For example, build lz4 compression library.

C to C

It's almost the same as Rust to C#, but add a reference to bindgen, and first add bindgen generation to build.rs. Then in csbindgen's builder, specify input_bindgen_file to load the file.

[package]
name = "example"
version = "0.1.0"

[lib]
crate-type = ["cdylib"]

[build-dependencies]
csbindgen = "1.9.6"
bindgen = "0.72.1"
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    // using bindgen, generate binding code
   bindgen::Builder::default()
        .header("c/lz4/lz4.h")
        .default_enum_style(bindgen::EnumVariation::Rust {
            non_exhaustive: false,
        })
        .generate()?
        .write_to_file("lz4.rs")?;

    // csbindgen code, generate C# dll import
    csbindgen::Builder::default()
        .input_bindgen_file("lz4.rs") // read from bindgen generated code
     // .csharp_generate_const_filter(|x| x.starts_with("LZ4_")) // use csharp_generate_const_filter if you want to generate const
        .generate_csharp_file("../dotnet/NativeMethods.lz4.g.cs")?;

    Ok(())
}

In this case, you won't use the Rust binary built. Instead, build the C/C++ library separately using make, cmake, or other tools, and place it accordingly. When targeting OSS libraries, they typically come with make/cmake configurations, so it's best to follow their build procedures to generate the library.

bindgen's default_enum_style generates consts. When handling in C#, it would be more practical to generate them as enums. Also, csbindgen does not generate consts by default. By enabling csharp_generate_const_filter, consts will be generated on the C# side.

Additionally, types and enums that exist in the input file are all trimmed and not generated if they are not used in method definitions. If you want to generate types or enums on the C# side, you explicitly specify them using always_included_types. For example, .always_included_types(["ZL_StandardGraphID", "ZL_StandardNodeID"]).

C to Rust to C

You can incorporate and build C code into a Rust library by using the cc crate or cmake crate. In that case, you need to create a flow where C# calls Rust, and Rust calls C. You can automate this entire flow by generating both Rust and C# files with generate_to_file.

use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    // using bindgen, generate binding code
    bindgen::Builder::default()
        .header("c/lz4/lz4.h")
        .default_enum_style(bindgen::EnumVariation::Rust {
            non_exhaustive: false,
        })
        .generate().unwrap()
        .write_to_file("lz4.rs")?;

    // using cc, build and link c code
    cc::Build::new().file("lz4.c").compile("lz4");

    // csbindgen code, generate both rust ffi and C# dll import
    csbindgen::Builder::default()
        .input_bindgen_file("lz4.rs")            // read from bindgen generated code
        .rust_file_header("use super::lz4::*;")     // import bindgen generated modules(struct/method)
        .csharp_entry_point_prefix("csbindgen_") // adjust same signature of rust method and C# EntryPoint
        .csharp_dll_name("liblz4")
     // .csharp_generate_const_filter(|x| x.starts_with("LZ4_")) // use csharp_generate_const_filter if you want to generate const
        .generate_to_file("lz4_ffi.rs", "../dotnet/NativeMethods.lz4.g.cs")?;
}

It will generates like these code.

// lz4_ffi.rs

#[allow(unused)]
use ::std::os::raw::*;

use super::lz4::*;

#[no_mangle]
pub unsafe extern "C" fn csbindgen_LZ4_compress_default(src: *const c_char, dst: *mut c_char, srcSize:  c_int, dstCapacity:  c_int) ->  c_int
{
    LZ4_compress_default(src, dst, srcSize, dstCapacity)
}
// NativeMethods.lz4.g.cs

using System;
using System.Runtime.InteropServices;

namespace CsBindgen
{
    internal static unsafe partial class NativeMethods
    {
        const string __DllName = "liblz4";

        [DllImport(__DllName, EntryPoint = "csbindgen_LZ4_compress_default", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
        public static extern int LZ4_compress_default(byte* src, byte* dst, int srcSize, int dstCapacity);
    }
}

Finally import generated module on lib.rs.

// lib.rs, import generated codes.
#[allow(dead_code)]
#[allow(non_snake_case)]
#[allow(non_camel_case_types)]
#[allow(non_upper_case_globals)]
mod lz4;

#[allow(dead_code)]
#[allow(non_snake_case)]
#[allow(non_camel_case_types)]
mod lz4_ffi;

Builder options(configure template)

Builder options: Rust to C

Rust to C#, use the input_extern_file -> setup options -> generate_csharp_file.

csbindgen::Builder::default()
    .input_extern_file("src/lib.rs")        // required
    .csharp_dll_name("mynativelib")         // required
    .csharp_class_name("NativeMethods")     // optional, default: NativeMethods
    .csharp_namespace("CsBindgen")          // optional, default: CsBindgen
    .csharp_class_accessibility("internal") // optional, default: internal
    .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_imported_namespaces("MyLib")    // optional, default: empty
    .csharp_generate_const_filter (|_|false) // optional, default: `|_|false`
    .csharp_dll_name_if("UNITY_IOS && !UNITY_EDITOR", "__Internal") // optional, default: "", allows multiple call
    .csharp_type_rename(|rust_type_name| match rust_type_name {     // optional, default: `|x| x`
        "FfiConfiguration" => "Configuration".into(),
        _ => x,
    })
    .always_included_types(["ZL_StandardGraphID", "ZL_StandardNodeID"])` // optional, default: []
    .generate_csharp_file("../dotnet-sandbox/NativeMethods.cs")     // required
    .unwrap();

csharp_* configuration will be embedded in the placeholder of the output file.

using System;
using System.Runtime.InteropServices;
using {csharp_imported_namespaces};

namespace {csharp_namespace}
{
    {csharp_class_accessibility} static unsafe partial class {csharp_class_name}
    {
#if {csharp_dll_name_if(if_symbol,...)}
        const string __DllName = "{csharp_dll_name_if(...,if_dll_name)}";
#else
        const string __DllName = "{csharp_dll_name}";
#endif
    }

    {csharp_generate_const_filter}

    [DllImport(__DllName, EntryPoint = "{csharp_entry_point_prefix}LZ4_versionNumber", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
    public static extern int {csharp_method_prefix}LZ4_versionNumber();
}

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. This option allows multiple call.

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.

csharp_generate_const_filter is optional, if set a filter fun, then generate filter C# const field from Rust const.

input_extern_file and input_bindgen_file allow mulitple call, if you need to add dependent struct, use this.

csbindgen::Builder::default()
    .input_extern_file("src/lib.rs")
    .input_extern_file("src/struct_modules.rs")
    .generate_csharp_file("../dotnet-sandbox/NativeMethods.cs");

also csharp_imported_namespaces can call multiple times.

always_included_types is optional. Types and enums that exist in the input file are all trimmed and not generated if they are not used in method definitions. If set name, do not trim and generate type to C#.

Unity Callback

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.

```csharp // true(default) generates delegate [DllImport(__DllName, EntryPoint = "callback_test", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] public static extern int callback_test(delegate unmanaged[Cdecl] cb);

// You can define like this callback method. [UnmanagedCallersOnly(CallConvs = new[] { typeof(CallConvCdecl) })] static int Method(int x) => x * x;

// And use it. callback_test(&Method);

// ---

// false will generates {method_name}_{parameter_name}_delegate, it is useful for Unity [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int callback_test_cb_delegate(int a);

[DllImport(__DllName, EntryPoint = "callback_test", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] public static extern int callback_test(callback_test_cb_delegate cb);

// Unity can define callback m

Extension points exported contracts — how you extend this code

PushStrLn (Interface)
(no doc) [1 implementers]
csbindgen/src/util.rs

Core symbols most depended-on inside this repo

utf8_printf
called by 283
csbindgen-tests/c/sqlite3/shell.c
cli_strcmp
called by 165
csbindgen-tests/c/sqlite3/shell.c
ZSTD_isError
called by 109
csbindgen-tests/c/zstd/common/zstd_common.c
cli_strncmp
called by 109
csbindgen-tests/c/sqlite3/shell.c
MEM_read32
called by 103
csbindgen-tests/c/zstd/common/mem.h
ZSTD_hashPtr
called by 77
csbindgen-tests/c/zstd/compress/zstd_compress_internal.h
appendText
called by 69
csbindgen-tests/c/sqlite3/shell.c
shell_check_oom
called by 65
csbindgen-tests/c/sqlite3/shell.c

Shape

Method 4,167
Function 3,256
Class 1,737
Enum 288
Interface 1

Languages

C#52%
Rust24%
C19%
C++5%

Modules by API surface

dotnet-sandbox/libphysx_csbindgen.cs2,337 symbols
dotnet-sandbox/jolt_bindgen.cs971 symbols
csbindgen-tests/c/sqlite3/shell.c660 symbols
dotnet-sandbox/bullet3_bindgen.cs477 symbols
csbindgen-tests/src/bullet3_ffi.rs439 symbols
dotnet-sandbox/libpng16_csbindgen.cs324 symbols
dotnet-sandbox/sqlite3_bindgen.cs309 symbols
csbindgen-tests/src/sqlite3_ffi.rs287 symbols
csbindgen-tests/src/physx/x86_64-pc-windows-msvc/structgen.rs261 symbols
csbindgen-tests/src/physx/unix/structgen.rs261 symbols
csbindgen-tests/c/zstd/compress/zstd_compress.c231 symbols
csbindgen-tests/src/physx/physx_generated.rs221 symbols

For agents

$ claude mcp add csbindgen \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact