Low-level C# bindings for XDL, the altered SDL 3.4.20 that builds as XDL3, and for SDL_image, SDL_ttf and SDL_mixer built against it as XDL3_image, XDL3_ttf and XDL3_mixer. They are meant for libraries such as Pixely, so they mirror the C API: the same names and parameters, no exceptions and no checks. The exception is Pointer<T>: Value and ThrowIfNull throw InvalidOperationException for a null pointer. No runtime has to stop a null dereference in unsafe code, and on wasm address 0 is readable memory.
using XdlSharp;
using static XdlSharp.Xdl;
SDL_Init(SDL_InitFlags.SDL_INIT_VIDEO);
Pointer<SDL_Window> window = SDL_CreateWindow("Title"u8, 640, 480, SDL_WindowFlags.SDL_WINDOW_RESIZABLE);XdlSharpholds the bindings. It is the same for every platform and holds no native library.XdlSharp.Native.<rid>holds XDL's runtime for one platform, from the XDL release:linux-x64,linux-arm64,osx-x64,osx-arm64,win-x64,win-arm64orbrowser-wasm. An app references the packages of the platforms it runs on.XdlSharp.Native.browser-wasmlinks its archives intodotnet.native.wasm, with emdawnwebgpu, the WebGPU binding that XDL calls, which XDL ships prebuilt. The link needs thewasm-toolsworkload and no writable Emscripten cache. To create a GPU device from the page, as the .NET runtime requires, also exportWebGPUwithEmccExportedRuntimeMethodand_wgpuCreateInstancewithEmccExportedFunction.
The version is the SDL version and a release number, such as 3.4.20.1. The package description names the XDL release, such as 3.4.20-4.
- Functions, constants, hints and property names of all four libraries are static members of
XdlSharp.Xdl; types are inXdlSharp. Each function loads from its own library:IMG_functions fromXDL3_image, and so on. T*isPointer<T>,void*isnint, and a function pointer isdelegate* unmanaged[Cdecl]<...>. The signature of a function pointer keepsT*, andboolin it isbyte.const char*parameters areReadOnlySpan<byte>, pinned for the call; au8literal is terminated.defaultpassesNULL.- Returned strings are
Pointer<byte>, neither decoded nor freed. SDL_GetStringPropertyreturnsReadOnlySpan<byte>: SDL returnsdefault_valueitself for a missing property, so the binding returns the caller's span instead of a pointer that would dangle after the pin.SDL_SetScancodeNamekeepsnameafter it returns, so that parameter isPointer<byte>: its lifetime is the caller's.- Integer typedefs are enums of the same name (
SDL_WindowID,SDL_PropertiesID); flag macros are their members (SDL_WindowFlags.SDL_WINDOW_HIDDEN). boolcrosses the native boundary asbyte, because aboolpassed by value arrives with stale upper bytes on Mono in the browser.- Function pointers cross the native boundary as
nint, because Mono's wasm interpreter cannot call a P/Invoke that has a function pointer type in its signature. - Struct fields keep
bool,Pointer<T>anddelegate*types; fixed arrays arefixedbuffers or[InlineArray]structs.
src/XdlSharp/Generated/skipped.txt lists what has no binding: variadic and va_list functions, inline functions, function-like macros, and macros that are not constants. SDL_stdinc.h is left out except SDL_malloc, SDL_calloc, SDL_realloc and SDL_free. src/XdlSharp/Xdl.Macros.cs ports the pixel format macros and SDL_INIT_INTERFACE by hand.
XDL_VERSION pins the XDL release. Building the generator or a test project runs tools/XdlRelease.proj, which downloads that release's headers, the runtime for this machine and the browser archives into artifacts/xdl/<version>. Packing a native package downloads the runtime it packs.
The bindings are generated from the release headers with clang, and the output is checked in.
dotnet run --project tools/XdlSharp.Generator
dotnet run --project tools/XdlSharp.Generator -- --check--include <dir> names other headers and --clang <path> another clang. --check writes no checked-in file and fails when a generated file is out of date. tools/XdlSharp.Generator/generator.json holds the choices the headers do not state, and the generator fails when an entry no longer matches the headers.
The checks in tests/XdlSharp.Checks compare every struct size, field offset and enum size with what the C compiler measures from the same headers, and call into XDL for booleans, strings, structs by value, callbacks and the hand-ported macros, and into each satellite library.
On the desktop they run under NUnit, against the release runtime and a library CMake builds from tests/native:
dotnet test tests/XdlSharp.TestsIn the browser runtime they run on Mono under Node. The release archives and tests/native, compiled by the Emscripten of the wasm-tools workload, are linked into the runtime:
dotnet publish tests/XdlSharp.Browser.Tests
cd tests/XdlSharp.Browser.Tests/bin/Release/net11.0/publish/wwwroot && node main.mjsThe Build workflow runs on every pull request. It checks the generated code with clang 22, runs the tests on the desktop and in the browser, and packs the packages. Then tests/XdlSharp.Package.Tests restores the packages and loads each library, as an app does, on linux-x64, linux-arm64, win-x64 and osx-arm64. tests/XdlSharp.Package.Browser.Tests does the same in the browser.
The Release workflow publishes every push to main. For SDL 3.4.20, the first release is 3.4.20.1, and each later one adds 1 to the highest v3.4.20.<N> tag. The workflow pushes the packages to nuget.org through Trusted Publishing, and then tags the commit v<version>. It stops when nuget.org already has that version from another commit.
To pack locally, run tools/pack.sh <output> [version]. Without a version, the version is marked local with the time, such as 3.4.20-local.20261109103000, so a consumer never restores an older local pack from its package cache.
- The headers are parsed for Linux, so declarations that only other platforms see, such as
SDL_SetWindowsMessageHook, are missing. The generator runs only on Linux. SDL_main.his not bound:SDL.hdoes not include it.SDL_vulkan.his not bound:SDL.hdoes not include it, andVkSurfaceKHRis a pointer on 64-bit targets but auint64_ton wasm32.wchar_t*isnint, becausewchar_tis 2 bytes on Windows and 4 elsewhere.
XdlSharp uses the zlib license, as XDL does. See LICENSE. Each XdlSharp.Native.<rid> package carries the release's THIRD-PARTY-NOTICES.txt as its license: XDL's license and the licenses of the third-party code in its libraries.