Home About Eric Topics SourceGear

2023-01-23 10:00:00

Static libraries

This is part of a series on Native AOT.
Previous -- Top -- Next


Buckle up folks, this part of the ride gets a little bumpy. As of .NET 7, using Native AOT with static libraries is implemented, but is not yet considered a supported and documented feature.

As mentioned previously, building a static library with Native AOT is straightforward. Just set the NativeLib property to static:

$ dotnet publish --property NativeLib=Static -r win-x64

And the code to call our multiply() function is no more complicated than it would normally be for whatever language is in play. In this sample, we're using C++:

#include <cstdint>
#include <stdio.h>

extern "C" int32_t multiply(int32_t, int32_t);

int main()
{
    int32_t c = multiply(7, 6);
    printf("%d\n", c);
    return 0;
}

Where things get tricky is when we try to link.

To be fair, the various command line options for any C++ compiler are usually arcane and complicated, so we'll try to blame Native AOT only for the complexity that it added to an already messy situation.

Using the Microsoft C++ compiler, our first attempt to build the program shown above might look something like the following:

cl.exe 
    (compiler options)
    mul_main.cpp
    ../mul_cs/bin/Debug/net7.0/win-x64/publish/mul.lib 
    (windows libraris)

I've omitted the usual compiler options and the long list of Windows libraries so we can focus on the two things we really care about. What we want is to compile the C++ snippet from above, and link it with the static library containing our multiply() function, built by Native AOT.

Unfortunately, this command will result in the linker complaining about a bunch of unresolved symbols. These symbols correspond to the various things that Native AOT code needs, a stripped-down form of the .NET runtime.

In principle, our trivial multiply() function should not need that stuff, but it is a degenerate case. There are settings we could have used to omit certain dependencies, but we didn't use those settings, so Native AOT has assumed that this library will need all the usual stuff.

So we need to add a bunch of static libraries to our link. When Native AOT building a dynamic library, this chore is performed automatically, because the .dll (or .so or .dylib) is built by the linker, but with a static library, we're deferring the actual link step until later.

But what libraries do we need? And where are they?

A few chapters ago, when we set things up for building with Native AOT, we had to add the PublishAOT property to our csproj.

When the .NET SDK sees this property, it does a lot of work behind the scenes, including adding references to several nuget packages which contain the tools and libraries that Native AOT needs. In the case of Windows, those libraries are in the nuget package runtime.win-x64.microsoft.dotnet.ilcompiler.

I mentioned above that none of this is considered to be a supported feature for .NET 7. In fact, the currently suggested way to figure this stuff out is to run a Native AOT build with detailed logging and examine the output to see the actual command line options for the C++ compiler. That gives us the list of libraries, plus the fact that we need to require the NativeAOT_StaticInitialization symbol.

The final command line is just too ugly to show in a blog entry, but all the gory detail is in a BAT file in the sample code.

We end up with one self-contained executable file (which is still ridiculously large, grumble).

C:\Users\eric\dev\native-aot-samples\mul_cpp_win_static> dir

 Directory of C:\Users\eric\dev\native-aot-samples\mul_cpp_win_static

...
01/23/2023  09:53 AM         4,763,136 mul_main.exe
...

C:\Users\eric\dev\native-aot-samples\mul_cpp_win_static> .\mul_main.exe
42

The code for this blog entry is available at:

https://github.com/ericsink/native-aot-samples/tree/main/mul_cpp_win_static