C2CS

repository·main·Indexed 43 days ago

https://github.com/bottlenoselabs/c2cs

A deterministic code generator that converts C header files (.h) into C# bindings (.cs) without the use of AI. It provides a reliable workflow for creating interop layers between C and C#, utilizing a two-step process with c2ffi to extract platform-specific metadata into JSON files before generating the final C# code. C2CS supports a wide range of platforms including Windows, macOS, Linux, iOS, tvOS, and Android, and provides the Interop.Runtime namespace for idiomatic C-to-C# interoperability.

Tokens
2.6K
Snippets
4
Records
13
Agent score
55%

What's inside C2CS

  1. Overview of C2CS

    main
    C2CS is a deterministic code generator designed to create C# library bindings from C header (.h) files. It produces .cs files without the use of AI, ensuring 100% predictable and repeatable output. This tool is used to bridge C code into C# environments.
  2. How to handle restrictive or closed-source system headers

    main

    Some platforms have system headers that are closed-source or have licenses prohibiting redistribution for cross-compilation. Because C headers can contain actual source code, you cannot simply copy them to a different OS to perform cross-compilation.

    To solve this, C2CS uses a two-step workflow to decouple metadata extraction from code generation:

    1. Metadata Extraction (ast): Run the ast tool (part of c2ffi) on the host operating system where the target system headers are legally available (e.g., running on Windows to read Windows headers). This tool extracts metadata (bit-widths, function names, structs, enums, etc.) into a C FFI .json file. Crucially, this .json file does not contain any source code, only metadata.
    2. C# Generation (cs): Move the generated .json files to any other operating system and use the main c2cs program to read these files and generate the final C# code for your target platforms.
  3. Interoperability patterns: Dynamic vs Static linking

    main

    When calling C platform code from a .NET application, you can choose between three primary interoperability patterns depending on your platform requirements and performance needs:

    1. Dynamic Loading & Linking

    Used when the C library (.dll, .dylib, or .so) is an external file.

    • Dynamic Loading: The library is loaded via dlopen (Unix) or LoadLibrary (Windows), and functions are resolved via dlsym or GetProcAddress. This involves an extra level of indirection (using function pointers) and is the standard behavior for DllImport in JIT environments.
    • Dynamic Linking (DirectPInvoke): Available with Ahead-of-Time (AOT) compilation (like NativeAOT in .NET 5+). This allows for direct platform invocation, which is more performant as it removes the indirection of the extra pointer. This requires the use of the [DllImport] attribute.

    2. Static Linking

    Used when the C library (.lib or .a) is compiled directly into the executable.

    • Requirement: Requires Ahead-of-Time (AOT) compilation.
    • Use Case: Necessary for 'walled garden' platforms like iOS or Xbox, where dynamic loading/linking is restricted for security and control reasons.
  4. Optimize C# to C interoperability using blittable structs

    main

    When performing interoperability between C# and C (via P/Invoke), you should aim for pass-through marshalling to maximize performance and minimize complexity.

    To achieve this, use blittable structs instead of classes.

    Why use blittable structs?

    • Performance: Blittable types have an identical bit representation in both managed (C#) and unmanaged (C) contexts. This allows the Common Language Runtime (CLR) to perform a rapid memory copy (bit-blit) rather than expensive data transformations.
    • Simplicity: Using classes for interop requires navigating complex and unintuitive default marshalling behaviors in Microsoft's CLR or Mono, which can lead to errors like double-frees.
    • Predictability: Using structs allows you to avoid the overhead and non-deterministic behavior of the Garbage Collector (GC) during the interop boundary.

    Note: When using blittable structs for interop, you are responsible for manual memory management (similar to C), as there are no hidden allocations or control flow performed by the CLR on your behalf.

  5. Understand the C2CS Platform Matrix

    main

    The Platform Matrix defines the compatibility between target operating systems, architectures, and the required Software Development Environments (SDE) for generating C# code.

    Key columns in the matrix:

    • Open (🔓 or 🔒): Indicates if system headers can be distributed to other platforms under an open-source license (🔓) or if they must be manually made accessible to C2CS on the host platform (🔒).
    • OS: The target operating system (e.g., Windows, macOS, Linux, iOS, tvOS, Android).
    • Arch: The target computer architecture (e.g., ARM64, X64, X86).
    • SDE: The required software development environment to build native libraries (e.g., MinGW, MSVC, XCode, Android Studio).
    • Clang Target: The specific Clang target triple used for the platform.
    • .NET RID: The official .NET Runtime Identifier (RID) for the target platform.
  6. Use the Interop.Runtime namespace

    main
    When C2CS generates bindings, it typically produces a file named Runtime.g.cs. This file contains the Interop.Runtime namespace, which provides helper structs, methods, and 'glue' code designed to make C-to-C# interoperability more idiomatic and easier to manage.
  7. Build C2CS from source

    main

    To build the project from the repository, ensure you have the following prerequisites:

    General Prerequisites:

    • .NET 9 SDK
    • Clone the repository with submodules: git clone --recurse-submodules https://github.com/bottlenoselabs/c2cs.git

    Platform Specifics:

    • Windows: Git Bash and Microsoft Visual C++ (MSVC) Build Tools + C/C++ SDK.
    • macOS: Xcode Command Line Tools (xcode-select --install), Xcode (from App Store), Homebrew, and CMake (brew install cmake).
    • Linux: Software build tools (GCC, Clang) and CMake.

    Build Commands:

    • CLI: dotnet build
    • IDE: Open ./C2CS.sln in Visual Studio or Rider.
    git clone --recurse-submodules https://github.com/bottlenoselabs/c2cs.git
    dotnet build
  8. Requirements for generating C# code for a target platform

    main

    To generate C# bindings for a specific C library on a target platform, you must provide three key pieces of information to the generation process:

    1. Clang target triple: A string in the format arch-vendor-os-environment (e.g., x86_64-pc-windows-msvc) that tells Clang how to interpret the C code for that specific platform.
    2. System C header files: The .h files belonging to the target platform. You must provide the root directory of these files so Clang can resolve types and definitions correctly (e.g., ensuring uint64_t has the correct bit-width for the target OS).
    3. NET Runtime Identifier (RID): A valid .NET RID (e.g., win-x64, linux-arm64) that matches the corresponding Clang target to ensure the resulting .NET application can run on the intended platform.
  9. Install the C2CS NuGet tool

    main

    C2CS is distributed as a .NET global tool. You must have the .NET 9 SDK installed to use it.

    To install the latest stable release:

    dotnet tool install bottlenoselabs.c2cs.tool --global

    To install the latest pre-release version (including builds from MyGet):

    dotnet tool install bottlenoselabs.c2cs.tool --global --add-source https://www.myget.org/F/bottlenoselabs/api/v3/index.json --version "*-*"

    If the dotnet tool command fails to find a specific version you expect, try clearing your NuGet caches:

    dotnet nuget locals all --clear
    dotnet tool install bottlenoselabs.c2cs.tool --global
  10. Generate C# bindings using C2CS and c2ffi

    main

    To generate C# bindings for a C library, follow this workflow:

    1. Extract Platform FFIs: Use c2ffi to extract platform-specific Foreign Function Interfaces (FFIs). Use the extract command with a configuration file. Example: c2ffi extract --config <config-file>.json

    2. Merge FFIs: Once you have extracted the FFIs for all target platforms (e.g., Windows, macOS, Linux) into a directory, merge them into a single cross-platform FFI JSON file using c2ffi merge. Example: c2ffi merge --inputDirectoryPath <directory> --outputFilePath <output>.json

    3. Execute C2CS: Run the c2cs tool with a configuration file to generate the final C# bindings. Example: c2cs --config <config-file>.json

    c2cs --config config-generate-cs.json
  11. Handle Locked (🔒) System Headers for Windows, macOS, and Android

    main

    When a platform is marked as locked (🔒), the system header files cannot be distributed. You must make them manually accessible to C2CS on the host machine to read C code for that target.

    Windows (MSVC)

    If using the MSVC SDE, you must download and install the Windows SDKs. The important directories are typically located at: %ProgramFiles(x86)%\Windows Kits\10\Include

    macOS, iOS, and tvOS (XCode)

    To access SDKs for Apple platforms, you must install XCode via the App Store on Apple-branded hardware.

    Android (Android Studio)

    To access Android SDKs, you must install Android Studio.

  12. Configure C2CS logging levels

    main

    By default, C2CS logs at the Information level. To enable Debug level logging, create an appsettings.json file in the directory where C2CS is running. You can customize the log format and verbosity using the following structure:

    {
        "Logging": {
            "Console": {
                "LogLevel": {
                    "Default": "Warning",
                    "C2CS": "Debug"
                },
                "FormatterOptions": {
                    "ColorBehavior": "Enabled",
                    "SingleLine": true,
                    "IncludeScopes": true,
                    "TimestampFormat": "yyyy-dd-MM HH:mm:ss ",
                    "UseUtcTimestamp": true
                }
            }
        }
    }
    {
        "Logging": {
            "Console": {
                "LogLevel": {
                    "Default": "Warning",
                    "C2CS": "Debug"
                }
            }
        }
    }