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