Generate C# bindings from C headers using libclang with modern LibraryImport
Project description
C# Binding Generator
Note: Most of the code in this repository was "vibe coded" with AI assistance, primarily using Claude Sonnet 4.5, GPT-4o, GPT-5 mini and Grok Code Fast 1. It is possible other models were also used in the mix. The author started this as an experiment to test how far AI has come in software development, but ended up creating an actually useful tool.
What is this?
A Python-based tool that automatically generates C# P/Invoke bindings from C header files using libclang. It produces modern C# code using LibraryImport attributes and type-safe unsafe pointers for struct parameters.
The tool is configured primarily through XML configuration files, providing powerful features like renaming, filtering, and macro constant extraction.
Features
- XML Configuration: Declare bindings configuration in a structured XML file
- Modern C# Code Generation: Uses
LibraryImport(not deprecatedDllImport) - Per-Library Binding: Each header can specify its own library name for correct P/Invoke attributes
- Type-Safe Pointers: Generates typed pointers (
SDL_Window*) instead of genericnint - Automatic Type Mapping: Intelligently maps C types to C# equivalents
- Renaming Support: Simple and regex-based renaming rules to transform C names to C# conventions
- Removal Support: Filter out unwanted functions, types, or patterns
- Macro Constants: Extract C
#defineconstants as C# enums with optional[Flags]attribute - String Handling: Provides both raw pointer and helper string methods for
char*returns - Struct Generation: Creates explicit layout structs with proper field offsets
- Union Support: Converts C unions to C# structs with
LayoutKind.Explicitand field offsets - Typedef Resolution: Properly resolves struct-to-struct typedefs through the typedef chain
- Multi-File Output: Automatically splits bindings into separate files per library
- Include Directory Support: Specify additional header search paths
- Opaque Type Support: Handles opaque struct typedefs (like
SDL_Window) - Visibility Control: Generate public or internal bindings
- Namespace Control: Specify different namespaces per library
- Custom Class Names: Name the static class containing P/Invoke methods
Documentation
- XML Configuration - Complete guide to XML configuration (recommended)
- Architecture - Internal design and how the generator works
- Include Directories - Managing header search paths
- Multi-File Output - Understanding generated file structure
- Troubleshooting - Common issues and solutions
Installation
# Clone the repository
git clone <repository-url>
cd CsBindingGenerator
# Create and activate virtual environment
python3 -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install the package
pip install -e .
Quick Start
1. Create Configuration File
Create cs-bindings.xml in your project directory:
<bindings visibility="public">
<!-- Include directories where headers can be found -->
<include_directory path="/usr/include/SDL3"/>
<!-- Rename rules to transform C names to C# names -->
<rename from="SDL_(.*)" to="$1" regex="true"/>
<!-- Extract flag constants as enums -->
<constants name="WindowFlags" pattern="SDL_WINDOW_.*" type="ulong" flags="true"/>
<!-- Define the library -->
<library name="SDL3" namespace="SDL" class="SDL">
<include file="/usr/include/SDL3/SDL.h"/>
</library>
</bindings>
2. Generate Bindings
cs_binding_generator # Automatically finds cs-bindings.xml
Or specify a custom config file:
cs_binding_generator --config my-bindings.xml --output ./Generated
3. Use in Your Project
using SDL;
// The generated bindings use modern LibraryImport
var window = SDL.CreateWindow("My Game", 800, 600, WindowFlags.WINDOW_SHOWN | WindowFlags.WINDOW_RESIZABLE);
Configuration Examples
Multiple Libraries
<bindings visibility="internal">
<include_directory path="/usr/include/libtcod"/>
<include_directory path="/usr/include/SDL3"/>
<!-- Remove SDL_ and TCOD_ prefixes -->
<rename from="SDL_(.*)" to="$1" regex="true"/>
<rename from="TCOD_(.*)" to="$1" regex="true"/>
<!-- Remove unwanted memory functions -->
<remove pattern="SDL_malloc"/>
<remove pattern="SDL_free"/>
<!-- Extract constants -->
<constants name="WindowFlags" pattern="SDL_WINDOW_.*" type="ulong" flags="true"/>
<constants name="InitFlags" pattern="SDL_INIT_.*" type="uint"/>
<!-- SDL3 library -->
<library name="SDL3" namespace="SDL3" class="SDL">
<include file="/usr/include/SDL3/SDL.h"/>
</library>
<!-- LibTCOD library (uses SDL3 types) -->
<library name="libtcod" namespace="Libtcod" class="Tcod">
<using namespace="SDL3"/>
<include file="/usr/include/libtcod/libtcod.h"/>
</library>
</bindings>
This generates:
bindings.cs- Assembly attributesSDL3.cs- SDL3 bindings with renamed functions and extracted constantslibtcod.cs- LibTCOD bindings with SDL3 using statement
Advanced Renaming
<bindings>
<!-- Specific renames (applied first) -->
<rename from="SDL_aligned_alloc" to="AlignedAlloc"/>
<rename from="SDL_aligned_free" to="AlignedFree"/>
<!-- General pattern (applied after specific renames) -->
<rename from="SDL_SCANCODE_(.*)" to="Scancode$1" regex="true"/>
<rename from="SDL_(.*)" to="$1" regex="true"/>
<library name="SDL3">
<include file="/usr/include/SDL3/SDL.h"/>
</library>
</bindings>
Generated Output Example
When processing multiple libraries with XML configuration, each function gets the correct LibraryImport attribute:
Input Configuration:
<bindings>
<rename from="SDL_(.*)" to="$1" regex="true"/>
<rename from="TCOD_(.*)" to="$1" regex="true"/>
<!-- Extract window flags as enum with [Flags] attribute -->
<constants name="WindowFlags" pattern="SDL_WINDOW_.*" type="ulong" flags="true"/>
<library name="SDL3" namespace="GameLibs" class="SDL">
<include file="/usr/include/SDL3/SDL.h"/>
</library>
<library name="libtcod" namespace="GameLibs" class="Tcod">
<include file="/usr/include/libtcod/libtcod.h"/>
</library>
</bindings>
Generated C# (SDL3.cs):
namespace GameLibs;
[Flags]
public enum WindowFlags : ulong
{
FULLSCREEN = unchecked((ulong)(0x0000000000000001)),
OPENGL = unchecked((ulong)(0x0000000000000002)),
HIDDEN = unchecked((ulong)(0x0000000000000008)),
// ...
}
public static unsafe partial class SDL
{
[LibraryImport("SDL3", EntryPoint = "SDL_Init", StringMarshalling = StringMarshalling.Utf8)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial int Init(uint flags);
[LibraryImport("SDL3", EntryPoint = "SDL_CreateWindow", StringMarshalling = StringMarshalling.Utf8)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial Window* CreateWindow(string title, int w, int h, ulong flags);
}
Generated C# (libtcod.cs):
using SDL3;
namespace GameLibs;
public static unsafe partial class Tcod
{
[LibraryImport("libtcod", EntryPoint = "TCOD_init", StringMarshalling = StringMarshalling.Utf8)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial void init(int w, int h, string title);
}
Command Line Options
The tool uses XML configuration files for all binding settings. Command-line arguments are limited to runtime options:
# Use default cs-bindings.xml in current directory
cs_binding_generator
# Specify custom config file
cs_binding_generator --config my-bindings.xml
# Specify output directory
cs_binding_generator --config bindings.xml --output ./Generated
Available Command-Line Options
-C, --config CONFIG_FILE: XML configuration file (default:cs-bindings.xmlin current directory)-o, --output DIRECTORY: Output directory for generated files (default: current directory)--ignore-missing: Continue processing even if some header files are not found--clang-path PATH: Path to libclang library (if not in default location)-V, --version: Show version number and exit
Note: All binding configuration (input files, include directories, namespaces, renames, etc.) must be specified in the XML configuration file. See XML Configuration for details.
How It Works
Architecture
- Configuration Parsing: Reads XML config file to get libraries, renames, removals, and constants
- Parsing: Uses libclang to parse C header files into an AST (Abstract Syntax Tree)
- Type Discovery: Pre-scans the AST to identify opaque types (empty struct typedefs)
- Macro Extraction: Scans header files for
#definemacros matching constant patterns - Code Generation: Walks the AST and generates C# code for:
- Enums → C# enums (including extracted macro constants)
- Structs/Unions → C# structs with
[StructLayout(LayoutKind.Explicit)] - Functions → C# static partial methods with
[LibraryImport] - Opaque types → Empty C# structs for type-safe handles
- Post-Processing: Applies rename and removal rules
- Multi-File Output: Splits generated code into separate files per library
Type Mapping
| C Type | C# Type | Notes |
|---|---|---|
void |
void |
|
int, long |
int |
|
unsigned int |
uint |
|
float, double |
float, double |
|
char* (param) |
string |
Auto-marshalled |
char* (return) |
nuint |
Use helper method for string |
void* |
nint |
Generic pointer |
struct Foo* |
Foo* |
Typed unsafe pointer |
union Bar |
Bar |
Struct with LayoutKind.Explicit |
const struct Foo* |
Foo* |
Const stripped |
size_t |
nuint |
|
bool |
bool |
With marshalling attribute |
Generated Code Example
Input C:
#define SDL_WINDOW_FULLSCREEN 0x00000001
#define SDL_WINDOW_OPENGL 0x00000002
typedef struct SDL_Window SDL_Window;
SDL_Window* SDL_CreateWindow(const char* title, int x, int y, int w, int h, uint32_t flags);
void SDL_DestroyWindow(SDL_Window* window);
const char* SDL_GetWindowTitle(SDL_Window* window);
Configuration:
<bindings>
<rename from="SDL_(.*)" to="$1" regex="true"/>
<constants name="WindowFlags" pattern="SDL_WINDOW_.*" type="uint" flags="true"/>
<library name="SDL3" namespace="SDL">
<include file="SDL.h"/>
</library>
</bindings>
Output C#:
namespace SDL;
[Flags]
public enum WindowFlags : uint
{
FULLSCREEN = unchecked((uint)(0x00000001)),
OPENGL = unchecked((uint)(0x00000002)),
}
public struct Window
{
}
public static unsafe partial class NativeMethods
{
[LibraryImport("SDL3", EntryPoint = "SDL_CreateWindow", StringMarshalling = StringMarshalling.Utf8)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial Window* CreateWindow(string title, int x, int y, int w, int h, uint flags);
[LibraryImport("SDL3", EntryPoint = "SDL_DestroyWindow", StringMarshalling = StringMarshalling.Utf8)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial void DestroyWindow(Window* window);
[LibraryImport("SDL3", EntryPoint = "SDL_GetWindowTitle", StringMarshalling = StringMarshalling.Utf8)]
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
public static partial nuint GetWindowTitle(Window* window);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string? GetWindowTitleString(Window* window)
{
var ptr = GetWindowTitle(window);
return ptr == 0 ? null : Marshal.PtrToStringUTF8((nint)ptr);
}
}
Testing
The project includes comprehensive tests using pytest:
# Run all tests
pytest tests/ -v
# Run with coverage
pytest tests/ --cov=cs_binding_generator
# Run specific test
pytest tests/test_generator.py::TestCSharpBindingsGenerator::test_generate_with_flags_attribute -v
Real-World Example: SDL3 + LibTCOD
This tool was developed and tested by generating bindings for SDL3 and LibTCOD. The generated files contain:
- Full API coverage for both libraries
- Type-safe window, renderer, and other opaque handle types
- Proper struct layouts with field offsets
- String marshalling helpers
- Extracted flag enums from macros
- Clean, renamed C# names without prefixes
See test_dotnet/LibtcodTest/cs-bindings.xml for a complete working example.
Requirements
- Python 3.11+
- libclang
- clang headers installed on your system
Installing libclang
Ubuntu/Debian:
sudo apt install libclang-dev python3-clang
macOS:
brew install llvm
Arch Linux:
sudo pacman -S clang python-clang
Project Structure
CsBindingGenerator/
├── cs_binding_generator/
│ ├── __init__.py
│ ├── main.py # CLI entry point
│ ├── generator.py # Main orchestration
│ ├── type_mapper.py # C to C# type mapping
│ ├── code_generators.py # C# code generation
│ ├── config.py # XML configuration parser
│ └── constants.py # Shared constants
├── tests/
│ ├── test_generator.py
│ ├── test_type_mapper.py
│ ├── test_code_generators.py
│ ├── test_xml_config.py
│ └── ...
├── docs/
│ ├── XML_CONFIG.md # XML configuration guide
│ ├── ARCHITECTURE.md
│ ├── INCLUDE_DIRECTORIES.md
│ ├── MULTI_FILE_OUTPUT.md
│ └── TROUBLESHOOTING.md
└── README.md
Limitations
- Variadic functions are not supported (skipped)
- Complex macros with expressions are not extracted
- Bitfields in structs are not supported
- Function pointers are mapped to
nint - Requires manual handling of callbacks
Contributing
Since this was an AI-assisted project, contributions are welcome! The codebase is designed to be readable and maintainable despite its AI origins.
License
See LICENSE file for details.
Acknowledgments
- Built with the power of AI (Claude Sonnet 4.5 and Grok Code Fast 1)
- Uses libclang for C parsing
- Inspired by the need for better SDL3 C# bindings
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cs_binding_generator-1.0.1.dev38.tar.gz.
File metadata
- Download URL: cs_binding_generator-1.0.1.dev38.tar.gz
- Upload date:
- Size: 197.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7661dc6c6ffa0e427d1f553e14e20cf80b5c54bcd4d1a6e36966c81a23ce6258
|
|
| MD5 |
b062180e6c72c3e2a7f00868de95c24d
|
|
| BLAKE2b-256 |
c3e416b7fb42bd4b78ddb4d6258636f4f447d9b50e3f13f71d1bd6ccb0051ca6
|
Provenance
The following attestation bundles were made for cs_binding_generator-1.0.1.dev38.tar.gz:
Publisher:
publish.yml on cs-binding-generator/cs-binding-generator
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cs_binding_generator-1.0.1.dev38.tar.gz -
Subject digest:
7661dc6c6ffa0e427d1f553e14e20cf80b5c54bcd4d1a6e36966c81a23ce6258 - Sigstore transparency entry: 1191601215
- Sigstore integration time:
-
Permalink:
cs-binding-generator/cs-binding-generator@88b2816f6c202558376e71b38d485f6fdf534a7b -
Branch / Tag:
refs/heads/master - Owner: https://github.com/cs-binding-generator
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@88b2816f6c202558376e71b38d485f6fdf534a7b -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file cs_binding_generator-1.0.1.dev38-py3-none-any.whl.
File metadata
- Download URL: cs_binding_generator-1.0.1.dev38-py3-none-any.whl
- Upload date:
- Size: 34.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3788749841b75984419f882b6103b0210534c228ad54567f10606ec3bc69f9a8
|
|
| MD5 |
f3ade36868d6e18f70bd61f643426cb2
|
|
| BLAKE2b-256 |
f1a3fd1965366fc996525f06e9d2a8856a9b7cb5792c8003a072abddaf7c0df1
|
Provenance
The following attestation bundles were made for cs_binding_generator-1.0.1.dev38-py3-none-any.whl:
Publisher:
publish.yml on cs-binding-generator/cs-binding-generator
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cs_binding_generator-1.0.1.dev38-py3-none-any.whl -
Subject digest:
3788749841b75984419f882b6103b0210534c228ad54567f10606ec3bc69f9a8 - Sigstore transparency entry: 1191601220
- Sigstore integration time:
-
Permalink:
cs-binding-generator/cs-binding-generator@88b2816f6c202558376e71b38d485f6fdf534a7b -
Branch / Tag:
refs/heads/master - Owner: https://github.com/cs-binding-generator
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@88b2816f6c202558376e71b38d485f6fdf534a7b -
Trigger Event:
workflow_dispatch
-
Statement type: