csbindgen turns Rust and C libraries into C# DllImport calls
Generate C# FFI from Rust for automatically brings native code and C native library to .NET and Unity.
At a glance
- What is it?
- csbindgen generates the C# side of a native interop layer for you. Point it at Rust extern C functions and it writes a DllImport class, or point it at a C header through rust-bindgen and it does the same for an existing C library, which is the path that lets a .NET or Unity project use something like zstd or SQLite without a native library of its own.
- Who is it for?
- csbindgen fits a .NET or Unity team that needs to call into a native library and does not want to hand write the interop layer, and the generated code is small and readable enough to paste into a project and maintain. It does not fit a team that wants the bindings to be a public API surface of their own, since the generated file is a build artifact that changes whenever the native signatures change.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 10 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem it removes
The core of the library is one sentence. It automatically generates C# DllImport code from Rust extern C code, and the generated code is optimised for Cdecl calls, where DllImport otherwise defaults to the Windows calling convention and requires a lot of configuration for C calls. The second problem it solves is the difference between the two hosts you are likely to target. .NET and Unity have different callback invocation methods, since .NET uses function pointers while Unity uses MonoPInvokeCallback, and you can output code for either by configuration. That matters more than it sounds, because a callback that is declared correctly for .NET will simply not be invoked under Unity, and the symptom is a crash or a silently ignored call rather than a compile error.
Rust to C# in three files
The Rust path is the simpler one and it is set up in three places. The package manifest declares the crate type and the build dependency:
[package]
name = "example"
version = "0.1.0"
[lib]
crate-type = ["cdylib"]
[build-dependencies]
csbindgen = "1.9.6"The library file holds ordinary Rust FFI code:
// lib.rs, simple FFI code
#[no_mangle]
pub extern "C" fn my_add(x: i32, y: i32) -> i32 {
x + y
}and the build script drives the generator:
fn main() {
csbindgen::Builder::default()
.input_extern_file("lib.rs")
.csharp_dll_name("example")
.generate_csharp_file("../dotnet/NativeMethods.g.cs")
.unwrap();
}The input_extern_file call points at the Rust source that holds the exported functions, and the csharp_dll_name is what ends up in the DllImport attribute on the C# side, so it has to match the name of the binary.
What the generated class looks like
Running cargo build produces the C# file, and its shape is worth reading because it is the contract you are signing up to. The generated file is a partial unsafe static class with a constant holding the library name and a DllImport attribute per exported function:
[DllImport(__DllName, EntryPoint = "my_add", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
public static extern int my_add(int x, int y);Two attributes carry the weight. CallingConvention.Cdecl is the whole reason the library exists, because it overrides the DllImport default of the Windows calling convention. ExactSpelling stops the runtime from applying the A suffix or W suffix mangling that some platforms use, which would otherwise turn my_add into a name that does not exist in the native binary. The class is marked unsafe, which is what a native call requires, and the generated file is a normal source file that you can commit.
C libraries, through bindgen
The more useful direction is bringing an existing C library to .NET or Unity. The README is careful here, since in this case you will not use the Rust binary that was built. Instead you build the C or C++ library separately using make, cmake or other tools and place it accordingly, and the documentation recommends following the library's own build procedure, because open source libraries typically ship with make or cmake configurations. The build script then does two things, first running rust-bindgen to turn the header into Rust code, and then running csbindgen over that generated file to produce the C# import:
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")?;and the second half reads that file with input_bindgen_file and writes the C# file. rust-bindgen is described as a proven header parsing library with an extensive track record, and the reason it is mentioned at all is that the alternative is hand writing a binding for every struct and enum in the header.
Enums, constants and the trimming rule
Three details in the C path are easy to get wrong and the README spells them out. The first is enum style. bindgen's default enum style generates constants, and when handling the same values in C# it is more practical to generate them as enums, which is what the default_enum_style setting in the example is doing. The second is that csbindgen does not generate constants by default, and enabling csharp_generate_const_filter produces them on the C# side, with the filter in the example commented out and noted as the way to restrict generation to a prefix such as LZ4_. The third is trimming. Types and enums that exist in the input file are all trimmed and not generated if they are not used in method definitions, and if you want a type on the C# side anyway you have to name it explicitly with always_included_types. That default is right for most libraries, since a header often declares far more than any single binding needs, and it keeps the generated file from becoming a second copy of the header.
Naming the binary, and the lib prefix trap
One paragraph in the README exists purely to stop people wasting an afternoon, and it is about the file name. The value of csharp_dll_name is output by the Rust project you set up, and in the example the package name is set to example, so by default the following binaries are produced in the target folder of the Rust project. On Windows that is example.dll, on Linux libexample.so, and on macOS libexample.dylib. The file name without the extension is what has to be given to DllImport, and the warning is that the Rust compiler prefixes file names with lib in some environments, so if you want to try the example as it is on macOS, csharp_dll_name would be libexample. The generated code takes the value you pass and writes it into a constant that the DllImport attribute references, so the whole problem reduces to getting that one string right, and the documentation links to a library loading section for the resolution of the file path.
The showcase and the ecosystem
The README lists the bindings the project has produced, and they are a good argument for the approach. There is a C# binding for the LZ4 compression library, for Zstandard, for the quiche QUIC and HTTP/3 library, for the Bullet Physics SDK, and for SQLite. Around those are the projects built on top, with YetAnotherHttpHandler bringing HTTP/2 and gRPC to Unity and .NET Standard, NativeCompressions providing LZ4 and Zstandard bindings for the same targets, and MagicPhysX a .NET PhysX 5 binding across Windows, macOS and Linux. The pattern is consistent. The native library stays native, and the C# side is a thin generated layer over it, which means there is no managed port to maintain and no performance ceiling from one. The repository reflects the same split, with a dotnet-sandbox and a unity-sandbox for the two host environments, a csbindgen-tests directory, and a separate GroupedNativeMethodsGenerator solution for generating grouped method classes.
The repository and the release state
The tree is a Cargo workspace with resolver version 2 and two members, csbindgen and csbindgen-tests, alongside the.NET and Unity sandboxes, a Directory.Build.props for the shared.NET build properties, an opensource.snk file for strong name signing, a rustfmt.toml and an.editorconfig. The releases are v1.9.8 published on 2026-05-21, v1.9.7 on 2025-11-10 and v1.9.6 on 2025-09-25, so the crate follows the 1.9 line and the cadence is a release every few months. The last push to the repository was on 2026-10-13, the repository is not archived, the licence is MIT, and the crate is published as csbindgen with API documentation on docs.rs. The practical note for an adopter is that the generated file should be committed and reviewed, because it is the part of your project that changes when the native API does.
Editorial conclusion
csbindgen fits a .NET or Unity team that needs to call into a native library and does not want to hand write the interop layer, and the generated code is small and readable enough to paste into a project and maintain. It does not fit a team that wants the bindings to be a public API surface of their own, since the generated file is a build artifact that changes whenever the native signatures change. It does not fit a project that only needs one function, where the setup is heavier than the work. Before you adopt it, read the library naming section, because the DllImport name has to match the actual binary including the lib prefix on Linux and macOS, and note the release state, where 1.9.8 was published on 2026-05-21 and the last push was on 2026-10-13.
Frequently asked questions
What does csbindgen generate?
It generates C# DllImport code from Rust extern C functions, and it can also work from a C header through rust-bindgen so that an existing C or C++ library can be called from .NET or Unity. The generated imports are optimised for the Cdecl calling convention rather than the DllImport default of the Windows convention.
How do I set up csbindgen for a Rust library?
Add it as a build dependency, declare the crate type as cdylib, and call csbindgen::Builder in build.rs with input_extern_file pointing at the source that holds the exported functions, csharp_dll_name set to the binary name and generate_csharp_file set to the output path.
What is the difference between the .NET and Unity output?
The two hosts invoke callbacks differently, with .NET using function pointers and Unity using MonoPInvokeCallback, so the output has to be configured for the host you are targeting. Getting this wrong tends to fail at runtime rather than at compile time.
What file name should I pass to csharp_dll_name?
The binary name without the extension, and be careful about the lib prefix, because the Rust compiler adds it on Linux and macOS. The example produces example.dll on Windows, libexample.so on Linux and libexample.dylib on macOS, so on macOS the value would be libexample.
Is csbindgen still updated?
The repository is not archived and the last push was on 2026-10-13, with v1.9.8 published on 2026-05-21 after v1.9.7 in November 2025 and v1.9.6 in September 2025. The licence is MIT and the crate is csbindgen on crates.io.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/cysharp-csbindgen)Was this useful?