Experimental Native C API
Experimental and unreleased. This page documents the C API sources at CNA snapshot c1c316b9 (branch apple/m4-stabilization, still stamped 0.1.0-alpha.1). The ABI is pre-1.0 and its minor versions may break compatibility. No CNA CI workflow builds the C library. The run evidence is local: the CNA.NET campaign built and used libcna_c_api on Linux from an earlier CNA commit (ABI 0.44.0), and CNA’s Apple campaign built this branch’s library on a physical Mac mini M4 and ran every C API test there (119 of 119 in the SOFTWARE and SDL_RENDERER configurations). CNA’s own release gate for the C ABI says “Not ready”, because 631 in-scope public C++ declarations still lack C mappings (planned work, not missing exports of the declared C routes). Unless a sentence says otherwise, what follows was read from source, not built by us.
Snapshot status and scope
CNA_BUILD_C_API is an opt-in CMake option (default OFF) for a native C linkage layer under CNA/C/. At source level, that layer uses fixed-width values, versioned structures, opaque generation-checked handles, explicit result codes, count/copy string and buffer protocols, and callbacks instead of exposing C++ classes or STL types. The exported surface does not vary with configuration: a route whose backend is compiled out still exists and answers CNA_RESULT_NOT_SUPPORTED.
| Fact | Alpha.1 tag | This snapshot |
|---|---|---|
| C ABI version | 0.7.0 | 0.46.0 (encoded 0x00002E00) |
| Public C headers | 59 | 60 (adds cnb.h) |
Declared cna_* routes | 2,861 | 3,210 — the declared name set equals the recorded exported-symbol baseline set, with an empty difference in both directions |
| Recorded ABI baseline | not stated | 197 struct layouts, 295 scalar types, 1,463 integer constants, 14 string constants, 141 colour constants, 3,210 exports (tools/c-api/abi_baseline.json; checked against the built Mach-O library on arm64 macOS, where the declared and exported sets agree exactly) |
| Semantic inventory | 6,712 rows (421 headers) | 8,142 symbols in 469 in-scope headers — see Measured coverage; the scope model changed, so the two are not directly comparable |
| Release gate | report and workflow disagreed | Not ready: the committed report records 9 of 10 criteria met; the unmet one is 631 planned public C++ declarations without a C mapping (not re-run for this page) |
| Built by CNA’s CI? | no | no — the five c-api-*.yml workflows are build-free (headers, JSON and Doxygen only) |
| Built and run anywhere? | no | yes, outside CNA’s CI: the CNA.NET campaign built libcna_c_api on Linux x86_64 from the earlier CNA commit 8a7e13ff5 (ABI 0.44.0) and ran its own test suites and the XNA samples against it; CNA’s Apple campaign (October 2026) built this branch’s library as a Mach-O dylib and static archive on a physical Mac mini M4 and ran every C API test (119/119 in the SOFTWARE and SDL_RENDERER configurations), including the installed-package consumer |
The first rows are facts about the source surface and its recorded metadata. The last row is evidence recorded in campaign ledgers (CNA.NET’s, 4 October 2026, and CNA’s plans/plan_apple_m4.md, tasks AM4-209 to AM4-214), not by CNA’s CI and not by the authors of these pages. Treat the C library as a pre-release that you build and test yourself.
From the alpha.1 compile blocker to a built library
At the alpha.1 tag the library could not compile: RendererIdentities in modules/c-api/src/CnaCApiCoreExt.cpp held one row fewer than the canonical C++ renderer count, and the file deliberately checks the two against each other (an isolated GCC 14.2/Ninja build reproduced comparison reduces to (49 == 50); with networking off, configuration failed on a missing GamerServices include instead).
At this snapshot the table has 14 rows against 14 canonical identities, and the ceiling constant equals the highest published value. Both compile-time checks hold:
static_assert(RendererIdentities.size() == CanonicalRendererCount(),
"A renderer was added to CNA::GraphicsRendererType without a C identity.");
static_assert(CNA_GRAPHICS_RENDERER_MAXIMUM == HighestPublishedIdentity(),
"CNA_GRAPHICS_RENDERER_MAXIMUM must name the highest-valued published renderer "
"identity.");
The networking trap is a deliberate configure-time error naming the option: CNA_BUILD_C_API=ON requires CNA_ENABLE_NET=ON, because the C API exports the GamerServices routes and links the GamerServices module unconditionally.
How confident can you be? On Linux x86_64 the library is no longer only a source-level design: CNA.NET builds it with -DCNA_GRAPHICS_RENDERER=OPENGLES3 -DCNA_BUILD_C_API=ON -DCNA_EASYGL_COMPILED_EFFECTS=ON and its final regression run (CSX-153) records the native C API smoke programs and the managed integration suites passing against it. Android (NDK) and Emscripten builds of the library are used by CNA.NET’s Android-emulator and headless-Chromium hosts. On macOS (Apple silicon) CNA’s Apple campaign built the library as a Mach-O dylib and static archive on a physical Mac mini M4 and ran every C API test, including the installed-package consumer (119/119 in the SOFTWARE and SDL_RENDERER configurations); no CI job does this. Windows shared-library builds have not been run, and the C API cannot be configured for iOS today (it requires CNA_ENABLE_NET=ON, which an iOS build turns off).
Build, package and consume
Enabling CNA_BUILD_C_API=ON turns on C as a project language, uses C17 for CNA’s implementation targets (with C extensions off inside modules/c-api, and -Wall -Wextra -Wpedantic -Werror there), and forces position-independent code. The public headers are held to a C99 consumer floor, checked against a matrix of language modes and toolchains (C99/C11/C17 and C++11/14/17 are required cells; 23 declared cells across 7 toolchains).
| Setting or target | What it does |
|---|---|
CNA_BUILD_C_API | Opt-in, default OFF. The three shipped CMakePresets.json presets that set it (dev, unit and release-modules) all set it OFF; no CI workflow enables it. |
CNA_ENABLE_NET | Must stay ON (its default) when the C API is enabled; otherwise a configure-time FATAL_ERROR. |
cna_c_api / CNA::CApi | The shared library libcna_c_api.so (under Emscripten a static library that a host such as CNA.NET’s browser runtime links in). On ELF platforms a linker version script exports cna_* and nothing else; the symbol-version node is CNA_C_API_0.1 and is deliberately never bumped with minors. |
CNA::CApiStatic / CNA_C_API_BUILD_STATIC | Optional static archive, default ON on Linux and macOS hosts with Python 3: the whole closure is partially linked into one relocatable object (ld64’s ld -r -all_load on macOS), every non-cna_* symbol is localized (nmedit -s on macOS), and the build fails if any survives (except GCC’s STB_GNU_UNIQUE symbols on ELF). Linking it defines CNA_C_API_STATIC, which empties the export macro. |
Install component CNACApi | The library, the public include/ tree, the SDL3 libraries CNA builds (installed beside the library, whose run path is $ORIGIN on Linux and @loader_path on macOS, so neither LD_LIBRARY_PATH nor a DYLD_* variable is needed; on macOS the library is libcna_c_api.dylib, install name @rpath/libcna_c_api.dylib, with its Mach-O compatibility and current versions taken from the ABI version), and the CMake package CNAConfig.cmake / CNAConfigVersion.cmake / CNACTargets.cmake under lib/cmake/CNA. FFmpeg is never shipped; it is a system dependency only when CNA_ENABLE_VIDEO selected it. |
| Package version | Equal to the C ABI version (read out of abi.h at configure time) with SameMajorVersion compatibility. |
| Android | The C API configures and builds with the Android NDK alone; the GamerServices service client and the online relay then refuse with CNA_RESULT_NOT_SUPPORTED. |
| macOS | The same package as a Mach-O dylib and static archive, measured locally on Apple silicon (a physical Mac mini M4, macOS 27, Xcode 27); see CNA’s CONSUMING.md, macOS. Libraries CNA does not build stay system dependencies (libcurl, FFmpeg, Opus, zstd, e.g. from Homebrew); the SDK’s own libcurl has no WebSocket support, so a build that found it refuses the online relay by name. A windowed renderer needs the game created and run on the process’s main thread. |
The C++ framework itself still has no install() rules; only this C package is installable. To build and install just the C ABI from a source checkout of the apple/m4-stabilization branch (with the sibling checkouts described in Building):
cmake -S . -B build -DCNA_BUILD_C_API=ON # CNA_ENABLE_NET defaults to ON
cmake --build build --target cna_c_api
cmake --install build --component CNACApi --prefix /opt/cna
and, in a consumer project that has never heard of the CNA source tree, the shape CNA’s own example uses:
cmake_minimum_required(VERSION 3.20)
project(my_cna_game LANGUAGES C)
find_package(CNA 0.1 CONFIG REQUIRED)
add_executable(my_cna_game main.c)
set_target_properties(my_cna_game PROPERTIES C_STANDARD 99 C_STANDARD_REQUIRED ON C_EXTENSIONS OFF)
target_link_libraries(my_cna_game PRIVATE CNA::CApi)
Without CMake, the library is a plain shared object with C linkage: cc -std=c99 -I/opt/cna/include main.c -L/opt/cna/lib -lcna_c_api. See the CNA repository’s CONSUMING.md for the full consumption notes; where it says every minor within a major is additive, or that the device is legal only inside a callback, the version history and the threading section below are newer.
Linux/ELF and macOS/Mach-O. The consumer test CApi_InstalledConsumer installs the CNACApi component into a staging prefix, configures the hello_cna example as a standalone project against it, builds it shared and static, and runs both with no LD_LIBRARY_PATH or DYLD_* variable. It is a local CTest registration on Linux and macOS, not a CI job; on macOS it also reads the installed dylib’s load commands for LC_RPATH @loader_path. The export set is enforced by a version script on ELF and by ld64’s exported-symbols list on macOS: exactly the 3,210 cna_* names. The macOS half was measured on Apple silicon only; Windows shared-library builds are not verified.
WebAssembly artifact
Under Emscripten the target cna_c_api_wasm emits an ES6 module (cna_c_api.mjs plus .wasm, createCnaCApi factory, ALLOW_MEMORY_GROWTH=1, ASYNCIFY=0) whose export list is generated from the headers by tools/c-api/generate_wasm_exports.py, and the static C archive can also be linked into another runtime’s WebAssembly module, as CNA.NET’s browser host does. Its ABI notes (by-value uint64_t arrives as BigInt, copied strings carry no NUL) are in CNA’s WASM_ARTIFACT.md. A browser host drives the game one frame at a time with cna_game_run_frame_ext from requestAnimationFrame; a threaded build (CNA_ENABLE_EMSCRIPTEN_THREADS=ON) can run the game thread as a browser worker. Three CTest registrations cover the module (CApi_WasmLinkContract, CApi_WasmModuleSmoke, CApi_WasmBrowserProbe); none runs in CI.
The C ABI version is not the product version
| Identity | Alpha.1 value | This snapshot | Purpose |
|---|---|---|---|
| CNA product | 0.1.0-alpha.1 | 0.1.0-alpha.1 (unchanged; no newer tag exists) | Which CNA source release is documented. |
| Native C ABI | 0.7.0 | 0.46.0 | Compatibility of the exported C binary contract. |
#include <CNA/C/cna.h>
if (cna_get_abi_version() != CNA_ABI_VERSION) {
return 1;
}
CNA_ABI_VERSION_MAJOR, _MINOR and _PATCH are fixed-width macros, encoded by CNA_ABI_VERSION_ENCODE as major<<16 | minor<<8 | patch. Do not compare them with CNA_VERSION_*; the latter describe the C++ product build. The exact-equality check above is the strictest and, while the ABI is 0.x, the safest one — it is also what CNA.NET does; CNA’s example accepts any equal-or-newer minor of the same major, which is a looser bet (see the next section).
ABI version policy and history
While the ABI is 0.x, an incompatible change requires a minor-version increment, release notes and a regenerated baseline; from 1.x on only additive changes are permitted within a major. ABI 1.0 is a separate decision the release gate explicitly does not make. CMake’s find_package(CNA 0.1 CONFIG) accepts any same-major version at or above the one requested, so CMake accepting the package is not a compatibility promise across 0.x minors: since alpha.1, 0.28.0 and 0.29.0 and the steps from 0.30.0 to 0.35.0 were incompatible, 0.40.0 and 0.42.0 to 0.44.0 changed rules without changing exports, and the current 0.46.0 declares a different constant set from 0.44.0, so a binding written against older headers must be regenerated or re-reviewed. A program that must not break should compare the minor it was written against at run time, not only the major.
| ABI | What changed (CNA’s own summary) |
|---|---|
| 0.1.0 – 0.6.0 | Initial ABI; additive routes; 0.3.0 was not additive (all 94 CNA_Bool-taking routes now refuse a byte outside 0/1); portable native-window handle routes (0.5.0); standardized empty-shader-source refusal (0.6.0). |
| 0.7.0 | Six PBR / morph-target routes — the alpha.1 value. |
| 0.8.0 – 0.9.0 | New renderer and capability identities (moved both MAXIMUM sentinels); owned-GraphicsDevice create/destroy, render-target ContentLost subscription; seven documented contracts changed in 0.9.0. |
| 0.10.0 – 0.19.0 | 0.10–0.17: the .cnb container, document and asset schemas, the loader registry with .cnj/source compile front ends, and reflective content readers (eight additive generations). 0.18: a Dictionary<string,object> object-dictionary handle. 0.19: Load<Model> and the Tag a content processor wrote. |
| 0.20.0 – 0.27.0 | Renderer-identity constant changes (0.20.0 incompatible, mostly reverted in 0.22.0); new device-type, 3D-texture-sampling, SPIR-V-dialect and base-instance identities (0.21–0.26). |
| 0.28.0 | Incompatible. The renderer-identity constant set changed and CNA_GRAPHICS_RENDERER_MAXIMUM moved; a numeric value is never reused. |
| 0.29.0 | Incompatible. Removed the SpriteBatch 2D-mesh route (cna_sprite_batch_draw_mesh_ext) and its CNA_SpriteMeshEXT struct. |
| 0.30.0 – 0.35.0 | Incompatible. The non-XNA graphics extension routes were reduced to the retro effects and debug drawing of graphics_ext.h (0.30.0); the renderer-identity constant set changed again (0.31.0, 0.33.0); the Guide and GamerServices dispatcher follow the configured services and refuse unconfigured social and sign-in calls, with two picture-copy routes added (0.32.0); avatar extension routes and two Guide setters removed (0.33.0 and 0.34.0 on a parallel line); 0.35.0 merged both lines. |
| 0.36.0 – 0.39.0 | Additive: cna_packet_reader_copy_data_ext (0.36); four queries for the canonical .NET exception type and parameter name behind a failure (0.37); cna_game_run_frame_ext, a host-driven run one frame at a time (0.38); cna_game_set_foreign_thread_calls_ext, an opt-in queue that runs other threads’ calls on the game thread (0.39). |
| 0.40.0 – 0.44.0 | Rule changes plus one route: the graphics-adapter routes accept the game handle before its first callback (0.40); cna_game_run_foreign_thread_calls_ext lets a waiting game thread drain that queue (0.41); a game lends its device after creation and before its run begins (0.42); the two fire-and-forget sound-play routes answer on any thread (0.43); Activated/Deactivated handlers may use the device (0.44.0). |
| 0.46.0 | This snapshot. The renderer-identity constants are exactly the 14 current renderers, and CNA_NATIVE_WINDOW_SYSTEM_UIKIT (9) is added: on iOS/iPadOS cna_game_window_get_native_window_ext reports SDL’s UIWindow*. No route, structure or exported symbol differs from 0.44.0, but a binding written against older headers must be regenerated or re-reviewed against these; CNA.NET admitted 0.46.0 after such a compatibility review. |
The full table lives in CNA’s ABI_VERSIONING.md. The recorded baseline is enforced by a generator (generate_abi_baseline.py, registered as the CTest gates CApiAbiHeaderBaseline and, where a library exists, CApiAbiBaseline).
Renderer identity values
CNA_GraphicsRendererType is a stable, sparse integer identity: a renderer’s value never changes and a reserved value is never reused, so the numbers from CNA_GRAPHICS_RENDERER_UNKNOWN (0) to CNA_GRAPHICS_RENDERER_MAXIMUM (currently 43, FNA3D) contain gaps. The 14 identities keep fixed values (for example CNA_GRAPHICS_RENDERER_VULKAN is 8), and every route that takes an identity refuses a reserved value or anything above MAXIMUM with CNA_RESULT_INVALID_ARGUMENT. Because some reserved values lie above the current maximum, the next new identity will take 52, not MAXIMUM + 1. scripts/check_renderer_identities.py fails the build if a reserved value is reused.
An identity is not a support claim. Two builds carrying the same identity can answer capability queries differently, so branch on cna_graphics_device_supports_capability and the renderer-feature queries, never on the renderer name or number. The list of identities and what each one supports is on the Renderers page.
Programming model
| Concern | C contract |
|---|---|
| Objects | CNA_Handle (uint64_t, CNA_INVALID_HANDLE = 0) values are opaque, typed and generation-checked: slot in the low 32 bits, generation in the high 32. A zero, stale, wrong-kind or foreign handle answers CNA_RESULT_INVALID_HANDLE. Release owned handles child-before-parent; cna_game_destroy answers CNA_RESULT_INVALID_STATE while an owned graphics child is alive. One active game per process. |
| Failures | Fallible calls return CNA_Result (0 success … 14 buffer too small); no C++ exception crosses the ABI. The failing thread keeps a diagnostic: cna_error_get_last_info, cna_error_get_last_message_size, cna_error_copy_last_message. The query routes never overwrite it. |
| Booleans | CNA_Bool is uint8_t; only 0 and 1 are valid and every route taking one refuses other bytes with CNA_RESULT_INVALID_ARGUMENT. |
| Strings/buffers | UTF-8 CNA_StringView { data, byte_length } in; count-then-copy out. The count excludes any terminator, and a too-small buffer is refused with CNA_RESULT_BUFFER_TOO_SMALL and no partial write. |
| Versioned structs | Extensible inputs start with struct_size and struct_version; zero the struct, set both, and CNA reads only the fields your size says you know. |
| Callbacks | Game lifecycle and events cross the boundary through function pointers and user-data values. Callbacks run synchronously on the game’s thread with borrowed handles; returning a failure stops the game and the enclosing call reports CNA_RESULT_CALLBACK. Never throw or longjmp out of one. |
| Threading | Lifecycle, game, graphics, content, input, media, device and resource-destruction calls belong to the runtime’s creation thread; a wrong-thread call answers CNA_RESULT_THREAD unless one of the opt-in rules in Threads, frames and host-driven runs applies. |
| Capabilities | Query the selected/active renderer and its capabilities; the C layer does not invent a second renderer-selection system. The renderer-capability profile is reachable through cna_graphics_device_get_renderer_feature_support_ext, _get_renderer_limit_ext, _get_surface_format_support_ext and the count-then-copy capability report (_get_capability_report_size_ext, _copy_capability_report_ext). |
The shipped example modules/c-api/examples/c/hello_cna.c (354 lines) is the reference: it checks the ABI, creates a callback-driven game, borrows the graphics device only during a legal callback, queries capabilities, exercises count-then-copy, provokes and reads two deliberate errors, creates and draws a texture, and shuts down in lifetime order. It is guarded so that it exits cleanly with no display.
Threads, frames and host-driven runs
By default every lifecycle, game, graphics, content, input, media, device and resource-destruction call belongs to the runtime’s creation thread, and a call from another thread answers CNA_RESULT_THREAD. The ABI generations since 0.36 add controlled exceptions to that rule, each opt-in or narrowly scoped:
| Need | Route or rule | Since |
|---|---|---|
Drive the game from a host loop (a browser’s requestAnimationFrame, an editor) | cna_game_run_frame_ext(game, &running) once per frame. The first call initializes and delivers begin_run; the call that finds the game exited delivers exiting and then end_run. The C++ equivalent is Game::RunFrameEXT. | 0.38 |
| Call CNA from a worker thread | After cna_game_set_foreign_thread_calls_ext, a call refused only for coming from another thread is queued: the caller blocks, the game thread runs it at the start of its next update or draw (up to 8 ms per step) and returns the result and error record. Driving or destroying the game is still refused; turning the feature off fails pending calls with CNA_RESULT_INVALID_STATE. | 0.39 |
| Wait for such a worker inside a callback | cna_game_run_foreign_thread_calls_ext drains the queue while the game thread waits. A game thread that waits for a worker without draining deadlocks. | 0.41 |
| Use the graphics device outside a callback | The device is lent between cna_game_create and the start of the run, on the creating thread (0.42), and inside Activated/Deactivated handlers (0.44); between frames it is still refused. | 0.42, 0.44 |
| Play a sound from any thread | cna_sound_effect_play and cna_sound_effect_play_with_settings only; other audio routes keep the thread check. | 0.43 |
| Read the adapters early | The cna_graphics_adapter_* routes accept the game handle before the first callback. | 0.40 |
| Know which .NET exception a failure means | Four queries return the canonical exception type and parameter name behind the last failure (failures the C layer raises itself name none). | 0.37 |
CNA.NET is the main consumer of these routes: its browser host uses the host-driven run, and XNA games that start threads use the foreign-thread queue. CNA’s own CALLBACKS_AND_THREADING.md predates them; the header comments in runtime.h are authoritative.
Route families and the largest headers
The 60 headers under modules/c-api/include/CNA/C/ carry extern "C" guards, except the umbrella CNA/C/cna.h (which only includes the others) and the constants-only named_colors.h. The largest, by declared routes (lines starting CNA_C_API):
| Header | Routes | What it carries |
|---|---|---|
effects.h | 290 | Effects: BasicEffect, skinned, environment-map and PBR effects, custom shader effects, plus effect parameters, techniques and annotations. |
cnb.h new | 272 | CNA’s .cnb compiled-content container: identities, byte-level constants, checksums, whole-file arithmetic, read limits and chunk compression (per the header’s own description). See CNB Format. |
gamer_services.h | 239 | GamerServices: signed-in gamers and their property dictionaries, achievements, leaderboards, the Guide and the Avatar renderer and animations. |
models.h | 216 | Models: meshes, bones, skinned models and skinning data, the animation player and morph targets. |
media_library.h | 148 | The XNA media library: picture albums and collections, albums, artists, genres and playlists. |
sensors.h | 144 | Sensor readings: accelerometer, gyroscope, compass, motion and attitude. |
vectors.h | 137 | Vector2/3/4 value types: accessors, initializers and transforms. |
net_sessions.h | 104 | NetworkSession: creation, find and join, gamers, properties and the session lifecycle. |
The opt-in CNAEXT extensions (retro effects and debug drawing) have 44 routes in graphics_ext.h. Not in the C ABI at all: Diagnostics and the Inspector have no C routes, and the build-time Content Pipeline, the Windows-Phone shell module, the platform substrate and the renderer implementations are out of runtime C scope by owner decision (see Measured coverage).
Your first C program
Tutorial 129 walks through a complete first program against ABI 0.46.0: install the CNACApi component, find_package(CNA 0.1 CONFIG REQUIRED), link CNA::CApi at C99, check the ABI, create a game with callbacks, borrow the graphics device, read the renderer name with count-then-copy, print the error diagnostic, and tear down children before the game. The tutorial is honest about the same caveat as this page: no CNA CI job builds the library, and the docs’ authors did not build it.
Measured coverage
CNA’s generated inventory (COVERAGE.md, produced by tools/c-api/generate_coverage_inventory.py) derives the complete public C++ declaration list from modules/*/include and maps every symbol through coverage_mappings.json. At this snapshot it reports 469 headers in scope, 8,142 symbols: 7,053 implemented in C, 15 approved partial mappings, 631 planned (not yet mapped) and 443 not applicable to a C consumer; 451 headers are explicitly excluded. The planned rows sit mostly in graphics (255) and content (204). These numbers describe the checked-in generated inventory; they do not convert partial or not-applicable rows into full C++ parity.
Not comparable with alpha.1’s inventory. The alpha.1 inventory started from 421 headers and 6,712 rows (6,317 implemented, 15 partial, 380 not applicable). The scope model has since changed: the build-time Content Pipeline (107 headers), the Windows-Phone shell module (9), the platform substrate (29), renderer implementations (115) and every Internal/Detail path are now classified as out of runtime C scope by recorded owner decisions. Read the new figures as a snapshot of the current model, not as growth from 6,712.
The 631 planned rows are the live backlog and the reason the committed release-gate report reads Not ready. CNA’s own coverage audit says an earlier “about 3,000 planned” figure was mostly mis-modelled evidence and that the genuinely missing runtime bindings measured 259 logical APIs before the scanner was repaired.
Gates and CI: what is actually checked
The C ABI has a large gate suite, but almost all of it is header-, JSON- or documentation-level. Nothing in it compiles the library.
| Mechanism | Where it runs | What it proves |
|---|---|---|
c-api-abi-baseline.yml, c-api-compat-matrix.yml, c-api-coverage-gate.yml, c-api-limitations.yml, c-api-release-gate.yml | GitHub Actions, ubuntu-24.04, automatic on push/PR to next, develop, main (path-filtered) plus manual dispatch | Recorded layouts and constants; every public header compiles alone and through the umbrella in the declared language modes; the coverage inventory; the limitations report; the release verdict. Build-free. |
12 header/JSON gate tests in ModuleProbes.cmake (CApiAbiHeaderBaseline, CApiDeclaredExports, CApiReleaseGate, …) | Ordinary CNA build (deliberately not inside if(CNA_BUILD_C_API)) | The same facts, checkable without building the C library. |
| 93 pure-C programs, 4 C++ tests, 1 libFuzzer source (104 registered tests) | Only where a developer configures -DCNA_BUILD_C_API=ON | Route behaviour, handle lifetime, boolean contract, threading and UTF-8 oracles. Not GoogleTest; they are not part of the site’s test-definition counts. |
CApi_InstalledConsumer | Local CTest, Linux/ELF and macOS/Mach-O, timeout 900 s | Install, find_package, build hello_cna shared and static, run both. |
| Route test coverage ratchet | Ordinary build | Every exported route is named by at least one test or example source (budget of uncovered routes: 0). |
The CNA.NET campaign’s final regression run on Linux x86_64 (CSX-153, 4 October 2026) records the native C API test set passing on a library built from an earlier CNA commit (ABI 0.44.0). CNA’s Apple campaign records every C API test passing on macOS for this branch: 119 of 119 in the SOFTWARE and SDL_RENDERER configurations on a physical Mac mini M4, with the exported symbols equal to the 3,210 declared routes and the installed package consumed shared and static. We did not reproduce either run; the definitive check for your toolchain is one out-of-source build of -DCNA_BUILD_C_API=ON followed by ctest -R '^CApi'.
Language bindings: maintained and archived
CNA is implemented in C++23, and this experimental C ABI is the one foundation every other language builds on. C++ is CNA’s native implementation language, not a binding. CNA actively maintains two language bindings: C, which is this ABI itself, and C#, through CNA.NET. Eight further bindings — Common Lisp, Go, Java, Python, Ruby, Rust, Swift and TypeScript — were developed during CNA’s history and are now archived.
CNA publishes one experimental C ABI and generates no language bindings. CNA’s own plan lists “a language-specific binding, wrapper, package, generator or sample for any language other than C” as a non-goal inside the CNA repository; every other binding is a separate project that consumes this ABI.
Pair versions exactly. While the ABI is 0.x, a minor version may break compatibility, so each binding admits the ABI generations it was qualified against and refuses the rest. CNA.NET admits exactly 0.46.0, the version this snapshot exports: its maintainers moved from 0.44.0 to 0.46.0 after a recorded compatibility review (1,419 consumed exports, no prototype or layout change). The archived bindings admit older generations only.
Maintained bindings: C and C#
| Binding (revision, date) | Status | ABI admitted | What it is |
|---|---|---|---|
C: CNA’s own CNA/C/ headers and cna_c_api library, c1c316b9, 2026-10-09 | experimental; release gate “Not ready” | 0.46.0 | The ABI itself: see Snapshot status and scope. Tutorial 129 is a first program, and cna-multi-language-3d-demo contains a complete small 3D game written in C99 against it. |
cna-dotnet (C#/.NET, “CNA.NET”), 0.46.0 admitted at 8219a6c, October 2026 | beta, source-first; no published packages | exactly 0.46.0 | A thin managed layer over the C ABI: CNA.Interop (P/Invoke), CNA.Framework (idiomatic API) and CNA.XnaCompat, a Microsoft.Xna.Framework-compatible facade that runs XNA 4.0 C# source. See C# with CNA.NET. |
Archived bindings: Common Lisp, Go, Java, Python, Ruby, Rust, Swift and TypeScript
Archived — not maintained. These eight bindings are a real part of CNA’s history, not throwaway experiments: several were taken far, with large test suites, templates and measured coverage of the XNA 4.0 runtime surface. They were archived because keeping each of them current against a C ABI that changes quickly before 1.0 cost more time than the project could give them; that effort now goes into C++, the C ABI and C#. They do not follow new ABI versions. Their source code, templates and full Git history are kept in CNA Lab (libcna/cna-lab, under bindings/), and if there is real community interest in a particular binding, its status can be reconsidered.
Seven of them (Go, Java, Python, Ruby, Rust, Swift and TypeScript) were requalified on Linux against C ABI 0.35.0 on 30 September 2026, immediately before they were archived; Common Lisp stopped earlier, on 8 September 2026. Each row describes the binding at its final revision.
| Archived binding (final revision, date) | Status in its own words | ABI admitted |
|---|---|---|
cna-common-lisp (Common Lisp, SBCL) c09a758, 2026-09-08 | projects “a selected subset” of XNA 4.0; qualified on SBCL on Linux x86-64 | 0.21.0 to 0.23.0 |
cna-go (Go, cgo) 9e46194, 2026-09-30 | 256 of the 257 selected types projected completely | 0.35 or later (qualified against 0.35.0 only) |
cna-java (Java 17, JNI) c2227a1, 2026-09-30 | “early, measured Java 17 projection”; the runtime surface structurally at zero diagnostics. A small Kotlin layer over it is kept beside it. | exactly 0.35 |
cna-python (Python, ctypes) 36242c7, 2026-09-30 | “pre-alpha, measured Python projection” | exactly 0.35 |
cna-ruby (Ruby, Fiddle) 25624a9, 2026-09-30 | 257 of 257 selected types and 2,915 of 2,915 members | exactly 0.35.0 |
cna-rust (Rust) 377af7e, 2026-09-30 | “early, measurable safe Rust projection”; selected profile structurally complete | exactly 0.35 |
cna-swift (Swift 6, Linux) 8fc3c29, 2026-09-30 | “real Swift projection”; 257 selected types strict-complete | 0.35 or later (qualified against 0.35.0 only) |
cna-ts (TypeScript and JavaScript; Node-API and WebAssembly) 80514a5, 2026-09-30 | “the complete XNA 4.0 runtime surface is projected and verified”; ran in headless Chromium | exactly 0.35 |
These rows are evidence for each repository’s own final revision, not for CNA at this snapshot: none of the eight admits ABI 0.46.0 as qualified. If you write your own consumer, the stable markers CNA publishes are: cna_get_abi_version() (encoded 0x00002E00 for 0.46.0), the CNA_ABI_VERSION macro, the CMake package version, the ELF symbol-version node CNA_C_API_0.1 (Linux) and the dylib’s Mach-O compatibility version (macOS), the machine-readable tools/c-api/abi_baseline.json (every struct layout, constant and export), the export/declaration set-difference gate check_declared_exports.py, and the per-artifact manifest from generate_artifact_manifest.py.
Important limitations
- No CNA CI job builds the C library. Run evidence comes from local campaign records — CNA.NET’s (Linux x86_64, plus Android-emulator and headless-Chromium hosts) and CNA’s Apple campaign (a physical Mac mini M4) — not from CNA’s CI and not from us.
- The release gate reads Not ready: 631 in-scope public C++ declarations still lack C mappings, and 443 more are recorded as having no C form (see CNA’s
LIMITATIONS.md). - The declared ABI 0.46.0 is experimental and pre-1.0. Several minor steps since alpha.1 were incompatible and more may follow; ABI 1.0 is a separate future decision.
- Package machinery, run path, export control and the consumer test exist for Linux/ELF and macOS/Mach-O (macOS measured locally on Apple silicon only); Windows shared-library consumption is not verified, and the C ABI is not exercised on iOS (the C API cannot be configured there today).
- Platform substrate interfaces that a C++ implementation would subclass are intentionally not exposed for C callers to implement.
- Some C++ overloads, containers and polymorphic values map to narrower typed routes or snapshots; 15 rows are explicitly partial.
- Thread, callback-borrow and shutdown rules are part of the contract. A numerically valid handle does not permit use on the wrong thread or after its owner shuts down; the cross-thread routes are opt-in and keep their own limits.
- CNA.NET is a separate beta project that pairs with exactly one ABI version, and the eight archived bindings are not updated at all; see Language bindings.
Continue with Tutorial 129: Your first C program against ABI 0.46.0, C# with CNA.NET, versioning, or the verification guide.
Deep dives on this topic
Long-form pages that explain the exact semantics, invariants and evidence behind this subject.
- C API evidence, coverage inventory and release gate — How far the evidence for CNA's C ABI 0.46.0 reaches, what each release-gate criterion really checks, what the gate reports for the 631 public C++ declarations still planned for C mapping, and how the coverage inventory classifies modules.
- Framework services and ecosystem quick reference — A map of CNA's input, audio, media, device, network, gamer-services, storage and sharp-runtime types with their current boundaries and owner pages, plus the bindings and showcase applications around CNA.
- Native C API contract: admission, buffers, retention and route families — What CNA's C ABI 0.46.0 version checks admit, the exact count-then-copy protocol, callback, registration and thread rules, resources that retain others, caller-created devices and all 60 headers by family.
Known issues in this area
Current defects, gaps and limitations at this snapshot that touch this subject.
- CNA-BUG-004: BoundingFrustum::Intersects(Ray) never computes an entry distance: rays starting outside always miss, and an origin on a plane throws — BoundingFrustum::Intersects(Ray) and Ray::Intersects(BoundingFrustum) report no hit for every ray whose origin is outside the frustum, whatever its direction, return 0 for an origin inside, and throw NotImplementedExcept
- CNA-BUG-055: Stale whole-registry renderer counts survive outside check_renderer_identities.py's list: CORE.md and FEATURE_MATRIX.md say 50, core_ext.h 46, ModuleProbes.cmake 42, the C API limitation and mapping records 25, and CHANGELOG 49 for a 50-identity release — CNA has 14 public renderer identities, yet the C API's backend-classification contract and mapping records, a public C header's Doxygen, a CMake test header and the alpha.1 release notes still state obsolete totals that
- CNA-BUG-056: docs/c-api/FEATURE_MATRIX.md and docs/c-api/README.md still present the C ABI as version 0.1.0 while abi.h declares 0.46.0 — The release gate now reads its ABI label from abi.h, but the C API feature matrix is titled '0.1 Feature Matrix' with an ABI row of 'Experimental version 0.1.0', and the C API README describes the library as 0.1.0.
- CNA-BUG-058: LIMITATIONS.md omits the 631 planned public C++ declarations and RELEASE_GATE.md still attributes 654 declarations to CBIND-117 — The generated C API limitations document partitions 7,511 of the 8,142 declarations it counts and never mentions the 631 planned ones, and the release-gate note still says the Content Pipeline task owns 654 declarations
- CNA-BUG-212: cna_graphics_resource_get_graphics_device returns CNA_RESULT_INVALID_HANDLE for every resource created on a caller-created GraphicsDevice — A resource made on a device from cna_graphics_device_create carries that device's handle as its owner token, and the route passes it to the game-only BorrowGameGraphicsDevice, so asking such a valid resource for its devi
- CNA-BUG-213: audio.h and AUDIO.md call cna_dynamic_sound_effect_instance_submit_buffer safe from a producer thread, but every call off the creation thread returns CNA_RESULT_THREAD — The submission routes resolve the instance through HandleRegistry::Get, which refuses any thread other than the handle's creation thread, so the documented canonical producer-thread usage fails; AUDIO.md's own threading
- CNA-BUG-214: docs/c-api/HANDLES.md documents CNA_RESULT_SHUTTING_DOWN for a closing or closed runtime, but no C API route ever returns it — abi.h defines CNA_RESULT_SHUTTING_DOWN and HANDLES.md maps a closing runtime to it, but no route returns it and the registry has no shutdown state, so a stale handle answers CNA_RESULT_INVALID_HANDLE.
- CNA-BUG-218: content_readers.h documentation names cna_get_last_error_message, which does not exist — The Doxygen of cna_object_dictionary_ext_copy_value tells callers to read 'the message cna_get_last_error_message returns', a function declared nowhere; the C API's error accessors are the cna_error_* functions in core.h
- CNA-BUG-220: cna_game_destroy's comment says Shutdown has disposed the canonical graphics device; it has not — A comment in cna_game_destroy claims CGame::Shutdown already disposed the game's canonical GraphicsDevice so C subscribers saw its Disposing event, but Game::Dispose(true) never disposes the Game-owned device.
- CNA-GAP-062: The C ABI has no route for 631 public C++ declarations that the coverage inventory records as planned — COVERAGE.md records 631 in-scope public C++ declarations without a C mapping (graphics 255, content 204, graphics-ext 40 and smaller tails in nine other modules), which is why the C ABI release gate reads Not ready.
- CNA-VGAP-053: No CI workflow builds the C API library or runs its tests — No workflow at this snapshot configures CNA_BUILD_C_API=ON; the C API workflows only run generators, inventories, a release gate and a header compile matrix, so the library and its tests are verified only in local builds
- CNA-VGAP-054: Destroying a caller-created GraphicsDevice while resources on it are still live is allowed by cna_graphics_device_destroy but untested — graphics_device.h says resources on a caller-created device are released with it and the destroy route checks no child count, but every C test destroys the resources first, so what later calls on those resource handles d