.NET 7 came out recently and has a newer featured called NativeAOT. Native Ahead Of Time compilation, means compiling c# directly to native code, rather than the IL (intermediate language) that it is normally compiled to. This has some really nice properties that interest us.
One example: we can write c# that can get loaded directly as a shared library / DLL! We won't cover installing the .NET SDK, but we can do a quick example of using this in practice. We'll also skim over some details about shared libraries and all that.
The project setup
On the simplest level, let's say we were making a native shared library/dll with a single function that gets called. Let's start a blank empty project, in a blank folder named example
, we run this on the command line:
dotnet new classlib
This creates a .csproj
file in your folder, and a Class1.cs
file. We'll add a single function to this class before we compile but this should be what you see.
Now we want a function that we can load + call from this native library. Let's call it `hello_world`.
This
is the magic. It lets C# know to make this function available/visible as native code.[UnmanagedCallersOnly(EntryPoint="hello_world")]
One more step:
Your csproj
file needs an extra flag, <PublishAot>true</PublishAot>
.
Compiling c# to a native library
We're almost done! We're gonna run dotnet publish
, with the -r
flag (for runtime), and the -c
flag to make sure it's a release instead of debug build. Note that you can cross compile, details at the end).
dotnet publish -r win-x64 -c Release
Take note that it mentions Generating native code
. This means it did the right type of build and our nativeAOT flag is working.
Now let's look in the build output folder. Note that it mentions native/ - this is important.
Inside here we can find the native dll, and can see it's much bigger than normal C# library. This is a nearly 3mb native DLL, ready to load in another language. This also includes the .lib file on windows, and the debug database (pdb) like normal native code for debugging✨
Loading and running the function
This can happen in many languages, any language that can load a dynamic library! A full example is out of scope, here's a small C++ snippet. This assumes the library is in the same folder, doesn't check for errors etc.
C# plugins for luxe
This makes writing a C# plugin for luxe a lot easier, nicer, and more performant. There's no Mono or other embed host needed, since the engine can load and call the c# directly as native code. There's a lot of work involved in that happening, but the engine provides that for you 🙌.
When using Wren, the language used by luxe (which we have customized a lot) you have foreign functions which have their implementation come from somewhere else... like c#! Most often they come from the engine itself, but plugins can also provide implementations (like in rust, c/c++, and more - mentioned long ago).
In this example, doStuff
can call a normal C# delegate we define inside the plugin. This is the Wren side:
And this is the C# side of our plugin. Note the class name + function signature match our foreign function:
This is great for accessing libraries you have in C# already, existing game code maybe, or writing a system that could use the performance boost directly in c#, compiled to native code, and you can opt in or out as needed ✨.
This works in luxe already 👀
While some details aren't final, we've done the ground work. More details on the exact nature of this for luxe will be in upcoming dev logs but we already have pure C# native plugins working, and it's really nice!
C# elsewhere
NativeAOT makes it possible to use c# on console platforms, like the FNA devs have been doing which is awesome.
Learn more
C# nativeAOT is new and has some limitations. It can also cross compile!
Read all about it, the platforms it supports and more on the docs. There's more info here too.
- Sign up for the luxe engine newsletter.
- View all blog posts
You can post comments in the community below.