ClangSharp

repository·main·Indexed 22 days ago

https://github.com/dotnet/clangsharp

C# bindings for Clang that enable the development of C/C++ tooling within the .NET ecosystem. It provides three levels of abstraction: a raw unsafe API, a type-safe abstraction, and a C++ type hierarchy abstraction. The project includes the ClangSharpPInvokeGenerator .NET tool for auto-generating P/Invoke bindings from C header files, as well as helper packages like libclang and libClangSharp.

Tokens
14.5K
Snippets
16
Records
41
Agent score
79%

What's inside ClangSharp

  1. Overview of ClangSharp packages

    main

    ClangSharp consists of several packages depending on your requirements:

    • clangsharp: The core C# bindings for Clang.
    • ClangSharpPInvokeGenerator: A .NET tool used to auto-generate bindings by parsing Clang C header files.
    • libclang: A convenience meta-package providing the native libClang library for several platforms.
    • libClangSharp: A helper meta-package that exposes many Clang APIs missing from the standard libClang library.
  2. What are the features of ClangSharp?

    main

    ClangSharp provides C# bindings for Clang C headers, allowing you to build tooling around C/C++ code. It offers three levels of abstraction:

    1. Raw Unsafe API: Provides high-performance access to the underlying C APIs.
    2. Type-Safe Abstraction: Uses distinct types for pointers that are internally identical (e.g., CXIndex and CXTranslationUnit) to improve type safety.
    3. C++ Type Hierarchy Abstraction: Attempts to mirror the Clang C++ Type Hierarchy where possible.

    The C# API is designed to be nearly identical to the Clang C APIs, with minor syntax adjustments (e.g., clang_getDiagnosticSpelling in C becomes clang.getDiagnosticSpelling in C#).

  3. Use ClangSharp as a library to parse C/C++ ASTs

    main

    Beyond generating bindings, ClangSharp can be used directly as a library to parse C/C++ code and inspect the resulting Abstract Syntax Tree (AST).

    Note: This is an advanced use case. The Clang APIs are the source of truth, so familiarity with Clang is required. For details on how Clang C functions and the C++ AST map to ClangSharp.Interop and ClangSharp surfaces, including IDisposable lifetime management and package references, refer to the Using ClangSharp as a library documentation.

  4. Manage lifetime and disposal of Clang objects

    main

    ClangSharp objects that own native resources implement IDisposable. You must wrap them in using blocks to ensure resources are released, equivalent to calling clang_dispose* in C.

    Key types to manage:

    • CXIndex: Created via CXIndex.Create(); must be disposed.
    • TranslationUnit: Obtained via TranslationUnit.GetOrCreate(...); disposing it releases the underlying CXTranslationUnit.

    Warning: Using a cursor or type after its owning TranslationUnit has been disposed results in undefined behavior.

  5. Use wildcards and catch-all rules in configuration

    main

    Many name-matching options (like --with-* or --without-*) support glob patterns:

    • *: Matches any run of characters, including qualification separators.
    • ?: Matches a single character.

    Catch-all and Overrides:

    • Catch-all: Use * as a bare value to apply a rule to everything. For valued options, use the syntax *=value (e.g., --with-access-specifier *=Internal).
    • Exclusion: Every --with-<name> option has a paired --without-<name> option to opt a specific declaration back out of a catch-all rule.
    • Precedence: An exact match always wins over a glob. Among globs, the most specific (most literal characters) wins.
    • Include/Exclude: Use -i (include) and -e (exclude) together to opt everything in and then exclude specific items piecemeal. Both accept globs.
  6. How libClang maps to ClangSharp.Interop

    main

    The ClangSharp.Interop layer maps libClang C functions to C# as follows:

    • Static Methods: C functions prefixed with clang_ are exposed as static methods on the clang class, with the clang_ prefix dropped and the first character preserved (e.g., clang_getCursorType(CXCursor C) becomes clang.getCursorType(cursor)).
    • Instance Members: Common getters are often surfaced as instance members on CX* types for more natural syntax (e.g., cursor.Type instead of clang.getCursorType(cursor)).
    • Handle Types: CX* handle types (like CXIndex, CXTranslationUnit, CXCursor) retain their original names.
  7. Use `<code` elements for embedded C# expressions

    main

    When the binding requires C# code that cannot be represented by structured XML (such as initializer expressions, generated helper bodies, or complex logic), the generator emits the raw C# text inside a <code> element. This is commonly seen inside <value> elements for enums/constants or <body elements for functions.

    <value>
      <code>1_024.0</code>
    </value>
  8. How the Clang C++ AST maps to ClangSharp

    main

    The high-level ClangSharp layer mirrors the Clang C++ class hierarchy:

    • Base Types: Cursor is the base type. Decl, Stmt, Expr, and Type (along with subclasses like FunctionDecl, CallExpr, and ReturnStmt) derive from it.
    • AST Entry Point: You obtain the root of the AST from a TranslationUnit via the TranslationUnitDecl property.
    • Object Lifecycle: Instances are cached. You should not use new to create these types; instead, use TranslationUnit.GetOrCreate(...), which returns the same managed object for a given underlying handle. This makes reference equality meaningful.
  9. Use glob patterns for name-matching options

    main

    Most name-matching options (like --with-access-specifier or --with-callconv) accept glob patterns:

    • *: Matches any run of characters, including :: or . separators.
    • ?: Matches a single character.

    Rules for matching:

    1. Exact matches always win over globs.
    2. Among globs, the most specific (most literal characters) wins.
    3. A match is successful if it hits the qualified, parameter-truncated, or remapped name.

    Common Patterns:

    • Catch-all: Use *=value to apply a rule to everything (e.g., --with-callconv *=Winapi).
    • Prefix matching: Use PFN* to match all callback types.
    • Symmetric Opt-out: Every --with-<name> option has a paired --without-<name> option to exclude specific declarations from a catch-all rule.
    # Example: Make all 'Flags' types internal and all 'PFN' callbacks Winapi
    --with-access-specifier
    *Flags=Internal
    --with-callconv
    PFN*=Winapi
    # Example: Make every type whose name ends in "Flags" internal, and give every "PFN" callback the Winapi convention
    --with-access-specifier
    *Flags=Internal
    --with-callconv
    PFN*=Winapi
    
    # Example: Apply a rule to everything
    --with-callconv
    *=Winapi
    
    # Example: Opt-out of a catch-all
    --with-access-specifier
    *=Internal
    --without-access-specifier
    Foo
    --without-access-specifier
    Bar*
  10. Understand the two layers of ClangSharp

    main

    ClangSharp provides two distinct surfaces for interacting with Clang:

    1. ClangSharp.Interop: A low-level layer providing 1:1 bindings to the libClang stable C API. It uses clang_* prefixed static methods on a clang class and CX* handle types (e.g., CXCursor, CXType).
    2. ClangSharp: A high-level layer that mirrors the Clang C++ AST. It provides managed objects like Cursor, Decl, Stmt, Expr, and Type (and their subclasses like FunctionDecl or CallExpr) which are easier to work with in .NET.

    Because these layers mirror upstream Clang, you should refer to the official Clang documentation for understanding the semantics of the APIs.

  11. Split shared and per-target settings

    main

    To manage large binding projects, categorize your ClangSharpPInvokeGenerator options into shared and per-target settings to avoid duplication and ensure consistency.

    Shared Settings (settings.rsp)

    Use these for properties that should be identical across the entire project:

    • Clang arguments: Architecture (-m64) and warning suppressions (e.g., -Wno-comment, -Wno-deprecated-declarations).
    • Generator modes: --config mode families, --generate, and --log switches.
    • Macros: --define-macro values that shape header exposure (e.g., UNICODE, INITGUID).
    • Search paths: --include-directory paths for local headers and SDKs.
    • Conventions: --header-file and project-wide call convention rules like --with-callconv *=Winapi.
    • Remappings: Topical remapping files (e.g., remap-handles.rsp) to ensure types like HANDLE are mapped consistently everywhere.

    Per-target Settings (generate.rsp)

    Use these for properties unique to a specific header:

    • Input/Output: --file (the umbrella header), --traverse, --namespace, --method-class-name, --output, and --test-output.
    • Target-specific overrides: --exclude, --with-attribute, and --with-library-path.
  12. Understand the XML binding document structure

    main

    A generated XML file is a single <bindings> document. The hierarchy typically follows this pattern:

    • <bindings>: The root element.
    • <comment>: Contains the verbatim header text if --header-file was configured.
    • <namespace name="...">: Represents the declared namespace (from -n).
    • <class name="..." ...>: A static holder for free functions (from -m or --method-class-name). Types like structs and enums are placed directly under the <namespace>, not inside this class.

    In --multi-file mode, each generated file is its own complete <bindings> document.

    <?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
    <bindings>
      <namespace name="ClangSharp.Test">
        <class name="Methods" access="public" static="true">
          <function name="MyFunction" access="public" lib="ClangSharpPInvokeGenerator" convention="Cdecl" static="true" unsafe="true">
            <type>void</type>
            <param name="color">
              <type>float*</type>
            </param>
          </function>
        </class>
      </namespace>
    </bindings>