Can a static library be linked to a .NET AOT compiled executable?

697 Views Asked by At

As seen e.g. here[1], we can export a function from .NET to be statically linked to another executable later.

using System;
using System.Runtime.InteropServices;

public static class NativeExports
{
    [UnmanagedCallersOnly(EntryPoint = "multiply")]
    public static int Multiply(int a, int b)
    {
        return a * b;
    }
}

Is the other direction also possible?

Something like this (pseudo code), is what I'm looking for:

using System;
using System.Runtime.InteropServices;

public static class NativeImports
{
    [UnmanagedImpl(EntryPoint = "multiply")]
    public static extern int Multiply(int a, int b);
}

Later on linked with this statically:

// multiply.c
int multiply(int a, int b)
{
    return a*b;
}

Overall goal is to have a single, statically linked, dependency free executable mainly written in C#.

I know about P/Invoke et. al. that's my current workaround.

[1] https://ericsink.com/native_aot/mul_cs.html

2

There are 2 best solutions below

0
sneusse On BEST ANSWER

Answering my own question after getting some help (https://github.com/dotnet/runtime/issues/89044):

Minimal example for direct P/Invoke calls with static linking

This is the function we would like to statically link against our executable written in C#

#include <stdio.h>
#include <string.h>

double UnmanagedTest (const char* name, double num)
{
    printf("Hello, %s\n", name);
    return num * strlen(name);
}

This is our C# application. External functions to be linked statically are declared like normal P/Invoke functions.

using System.Runtime.InteropServices;

internal static class App
{
    [DllImport("nativelib", EntryPoint = "UnmanagedTest", CallingConvention = CallingConvention.Cdecl)]
    public static extern double UnmanagedTest ([MarshalAs(UnmanagedType.LPStr)] string name, double num);

    public static void Main()
    {
        Console.WriteLine("Hello, World!");
        var val = UnmanagedTest("World!", 7);
        Console.WriteLine($"I got the number '{val}' back");
    }
}

This part of the project file is responsible for generating the direct calls and linking statically against the library written in C:

app.csproj


<ItemGroup>
    <!-- Generate direct PInvokes for Dependency -->
    <DirectPInvoke Include="nativelib" />
    <!-- Specify library to link against -->
    <NativeLibrary Include="nativelib.lib" Condition="$(RuntimeIdentifier.StartsWith('win'))" />
    <!-- Specify the path to search for libraries -->
    <LinkerArg Include="/LIBPATH:..\\clib" Condition="$(RuntimeIdentifier.StartsWith('win'))" />
</ItemGroup>

References

1
YendisFish On

I actually figured out a way to do this... but it utilizes a very niche method of interop. You can essentially pass a delegate into your C# code! Unfortunately this might come with some limitations but I'm not sure. I've also only tried to dynamically link the compiled C#, but I'm sure you can statically link it too. Anyways here's an example:

main.c

typedef int(*FunctionPointer)(int, int);

extern void InitializeLib(FunctionPointer ptr);
extern void CSMain();

int multiply(int a , int b)
{
     return a * b;
}

int main()
{
     InitializeLib(&multiply);
     CSMain();
}

Then your C# file will look like this:

Program.cs

using System.Runtime.InteropServices;

namespace MyProgram;

unsafe class Program
{
     public static delegate*<int, int, int>multiply { get; set; }

     [UnmanagedCallersOnly(EntryPoint = "InitializeLib")]
     public static void InitializeLib(delegate*<int, int, int> ptr)
     {
           multiply = ptr;
     }

     [UnmanagedCallersOnly(EntryPoint = "CSMain")]
     public static void CSMain()
     {
           int val = multiply(5, 5);
     }
}

I did not test this code after writing it. But I believe this would work... anyways the idea here is that you compile the C and statically link your C# to the C binary. From there you can call your C# main function, and also pass delegates into your C# code.

I have written out a simple proof of concept and instructions on how to build this on my github:

https://github.com/YendisFish/CS2C/