Library / SDK
xoofx/CppAst.NET avatar
xoofx/CppAst.NET

CppAst.NET: a managed C++ AST for interop code generation

CppAst is a .NET library providing a C/C++ parser for header files powered by Clang/libclang with access to the full AST, comments and macros

625 stars79 forksC#BSD-2-Clause

At a glance

What is it?
CppAst.NET wraps libclang through ClangSharp and gives .NET code a managed model of a C++ header, with comments, macros and attributes attached, built for generating P/Invoke and interop bindings rather than for compiling C++.
Who is it for?
Adopt CppAst.NET if you generate P/Invoke or interop bindings from C++ headers and want a managed AST with comments and macros attached. Do not adopt it expecting a complete C++ semantic model, since function bodies are source spans and template types are best effort, and set the RuntimeIdentifier property or the native libclang asset will not restore.
Can I use it commercially?
Yes. BSD-2-Clause 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 26 days ago.
What is it written in?
Mainly C#, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A managed model over libclang, deliberately not complete

CppAst is a .NET library providing a C/C++ parser for header files, powered by Clang and libclang, with access to the full AST, comments and macros. The library states its own boundary in the known issues section, and it is worth reading before you design around it. CppAst is a lightweight model over libclang and intentionally does not expose every Clang cursor or statement node. The parsing itself goes through ClangSharp, the managed wrapper around Clang and libclang, and the version in use is Clang and libclang 21.1.8.x. The target framework is net8.0, and for netstandard2.0 you should stay on the 0.14.0 version of the package. The result is a set of plain .NET model classes you can walk from C#, which is the shape you want when the C++ side is a foreign library you want to bind rather than a codebase you want to refactor.

Built for P/Invoke and interop code generation

The purpose statement is a single sentence and it tells you what kind of user this is for. The target primary usage of the library is to serve as a simple foundation for domain oriented PInvoke and interop codegen. That is a much narrower goal than a general C++ parser, and it explains the feature list, which is organised around what a binding generator needs to know. The managed model covers declarations for namespaces, classes, structs and unions, Objective-C interfaces, enums, fields and global variables, functions and methods, typedefs and using aliases, and include directives. The type system is described as broad, spanning primitive, record, enum, typedef, pointer, reference, array, function, block-function, qualified, template and unexposed or dependent types. On top of that there is C++ metadata for the usual suspects, class, inheritance, constructor and destructor, operator, conversion, friend, inline, final, defaulted, deleted, template and specialisation information. Nothing here is a C# code emitter, the library is the analysis layer and you write the generator.

Set the RuntimeIdentifier or the native asset will not restore

The setup step is a small but real piece of work, and the documentation gives it exactly. After installing the NuGet package you configure your project to select a platform runtime identifier through the RuntimeIdentifier property, so that the native libclang asset is restored:

xml
  <PropertyGroup>
    <!-- Workaround for issue https://github.com/microsoft/ClangSharp/issues/129 -->
    <RuntimeIdentifier Condition="'$(RuntimeIdentifier)' == '' AND '$(PackAsTool)' != 'true'">$(NETCoreSdkRuntimeIdentifier)</RuntimeIdentifier>
  </PropertyGroup>

The comment points at an upstream issue in ClangSharp, which tells you this is a workaround rather than a design choice of the library. The condition matters as much as the property, since the identifier is only filled in when the project has not set one itself and the build is not producing a tool. The practical symptom of getting this wrong is a build that succeeds and then fails at run time, because the native library the bindings need was never copied to the output folder, so a project that packages CppAst as a dotnet tool needs the property left alone, which is what the PackAsTool condition takes care of.

Parse a snippet and walk the model

The quick start is a single call to CppParser.Parse, which accepts C++ text directly:

C#
// Parse C++ files
var compilation = CppParser.Parse(@"
enum MyEnum { MyEnum_0, MyEnum_1 };
void function0(int a, int b);
struct MyStruct { int field0; int field1;};
typedef MyStruct* MyStructPtr;
"
);

The result is a compilation object with collections per declaration kind, and the example walks them in turn, printing the diagnostics first, then every enum, every function, every class and struct, and every typedef:

C#
// Print diagnostic messages
foreach (var message in compilation.Diagnostics.Messages)
    Console.WriteLine(message);

// Print All enums
foreach (var cppEnum in compilation.Enums)
    Console.WriteLine(cppEnum);

Reading the diagnostics before anything else is the pattern to copy, because a header that does not parse cleanly still returns a compilation object and a generator that ignores the diagnostics will happily emit bindings for code that does not exist. The library also parses C and C++ files from disk, not only in-memory text, and the language mode is chosen through CppParserOptions.ParserKind, with C, C++ and Objective-C supported.

Comments, macros and attributes are opt-in

Several of the most useful features are switched on through CppParserOptions, and the defaults matter. Macros are off by default, with ParseMacros enabling access to macro definitions, parameters and tokens, which is a sensible default since macro handling is where parsers usually go wrong. Token-level attributes are available through ParseTokenAttributes but the documentation marks them as still there for compatibility and obsolete, and it suggests preferring system and annotate attributes when possible, which are always accessible. Comment-based attributes come from ParseCommentAttribute, so a project that encodes metadata in comments rather than in attributes can pick that up without changing the header. Attached comments are available too, including Doxygen comments and parameter comment commands, which is what makes it possible to carry documentation into generated bindings. Initialiser expressions are exposed as well, so a constant such as const int x = (1 + 2) << 1 exposes the expression rather than only its value.

What the model does not give you

Four limitations are stated, and each one changes what a generator can promise. Function bodies are exposed as source spans when requested through ParseFunctionBodies, not as a full statement AST, so a generator that needs to inspect logic inside a function has to work on text. Template, dependent and unexposed types are represented on a best effort basis, and the documentation says advanced C++ constructs may require inspecting CppUnexposedType, display names or the diagnostics. Token level attributes are obsolete, as noted above, so new generators should read system and annotate attributes instead. And type sizes and built-in aliases such as size_t follow the configured target triple and ABI, which is why the target configuration helpers exist, including ConfigureForWindowsMsvc and ParseSystemIncludes filtering for system headers. Choose the right target triple or your size_t will be the size of the target you configured, not the one you meant.

Packaging, releases and the neighbouring cppast project

The library is distributed as a NuGet package named CppAst, with a user guide in the doc folder of the repository, and the licence is the BSD 2-Clause, which is short and permissive enough for commercial use with attribution. The author is Alexandre Mutel, known as xoofx, who also maintains related .NET native interop tooling. The release history is steady, with 0.24.0 on 2025-11-20, 0.25.0 on 2026-06-07 and 0.26.0 on 2026-07-13, and the last push was on 2026-09-27 with the repository not archived. There is a sibling project worth knowing about, the C++ project cppast by foonathan, which serves a similar purpose, and the documentation is explicit that CppAst.NET does not share API or any implementation details with it. So if you are choosing between them, the decision is about language and binding, not about which codebase you are reusing. The repository also carries an AGENTS.md and a CLAUDE.md at the top level, which suggests the maintainer is using assistant tooling on the codebase itself.

Editorial conclusion

Adopt CppAst.NET if you generate P/Invoke or interop bindings from C++ headers and want a managed AST with comments and macros attached. Do not adopt it expecting a complete C++ semantic model, since function bodies are source spans and template types are best effort, and set the RuntimeIdentifier property or the native libclang asset will not restore.

Frequently asked questions

What is CppAst.NET used for?

Its stated primary usage is to serve as a foundation for domain oriented PInvoke and interop code generation. It parses C and C++ headers into a managed AST model with comments, macros and attributes so a .NET tool can generate bindings from them.

Why do I need to set RuntimeIdentifier when installing the NuGet package?

The native libclang asset is only restored when the project selects a platform runtime identifier. The documented fix sets RuntimeIdentifier to the SDK value when it is empty and the build is not packaging as a tool, and it is described as a workaround for an upstream ClangSharp issue.

Does CppAst.NET expose full function bodies?

No. Function bodies are exposed as source spans when requested through CppParserOptions.ParseFunctionBodies, not as a full statement AST, so anything needing the logic inside a function has to work on the source text.

How do I get macro definitions from CppAst?

Set CppParserOptions.ParseMacros, which is off by default. Once enabled you get access to macro definitions, parameters and tokens. Comments and attributes are separate options, with ParseTokenAttributes for token level attributes and ParseCommentAttribute for comment based ones.

How do I choose the target platform for CppAst?

Through target configuration helpers such as ConfigureForWindowsMsvc and ParseSystemIncludes filtering for system headers. Type sizes and built-in aliases such as size_t follow the configured target triple and ABI, so the target has to match the platform you are generating for.

Official sources

  1. Issues
  2. License: BSD-2-Clause
  3. README
  4. Releases
  5. xoofx/CppAst.NET on GitHub
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/xoofx-cppast-net.svg)](https://hysenlabs.com/projects/xoofx-cppast-net)

Was this useful?