dotnet / fsharp

The F# compiler, F# core library, F# language service, and F# tooling integration for Visual Studio
https://dotnet.microsoft.com/languages/fsharp
MIT License
3.86k stars 779 forks source link

Compile a NativeLibrary with NativeAOT #14883

Open lukemerrick opened 1 year ago

lukemerrick commented 1 year ago

Is your feature request related to a problem? Please describe.

I tried to use the new .NET 7 NativeAOT NativeLibrary feature in F#, adapting the C# official example into the following code:

// My attempt to expose a simple function in a Native Library build.
namespace HelloLib

open System.Runtime.InteropServices

module Say =
    [<UnmanagedCallersOnly(EntryPoint= "hello")>]
    let hello name =
        printfn "Hello %s" name

Unfortunately, the F# compiler just returned a generic This attribute is currently unsupported by the F# compiler. Applying it will not achieve its intended effect.

This is frustrating because there is this lovely 3 year old example of doing an F# native library in .NET SDK version 3, which makes it seem like the current lack of support is a bit of a regression compared to the state of Native AOT in 2020.

Describe the solution you'd like

I would like F# to support building a NativeLibrary. Short of this, adding some newbie-friendly information to the F# documentation explaining the current state of Native AOT and the expectation for future support would also be helpful.

Describe alternatives you've considered

I have considered trying to wrap an F# library in a C# native library, but that feels like too much of a hack. I also am a .NET and compilers novice, so I'm not sure that this approach would even work. If this would likely work and is actually a good workaround at the moment, any pointers as to how to accomplish this would also be appreciated!

dotnet --info output ```console dotnet --info .NET SDK: Version: 7.0.102 Commit: 4bbdd14480 Runtime Environment: OS Name: manjaro OS Version: OS Platform: Linux RID: manjaro-x64 Base Path: /usr/share/dotnet/sdk/7.0.102/ Host: Version: 7.0.2 Architecture: x64 Commit: d037e070eb .NET SDKs installed: 7.0.102 [/usr/share/dotnet/sdk] .NET runtimes installed: Microsoft.NETCore.App 7.0.2 [/usr/share/dotnet/shared/Microsoft.NETCore.App] Other architectures found: None Environment variables: Not set global.json file: Not found Learn more: https://aka.ms/dotnet/info Download .NET: https://aka.ms/dotnet/download ```
jameshulse commented 9 months ago

Hi @lukemerrick, I was trying to follow the same article that you linked and ran into the exact same problem. I asked on the F# discord and they said that a C# wrapper will work. It is a bit of a hack, but it isn't particular difficult to get working.

Here's what I did:

  1. Created a solution (sln) to house both my F# and C# projects (dotnet new sln)
  2. Moved my F# project into a sub folder e.g /Fsharp
  3. Created a new C# project (dotnet new classlib -o Exports --aot)
  4. Added the projects to my solution (dotnet sln add Exports, dotnet sln add FSharp)
  5. Ensure both projects have the <PublishAot>true</PublishAot> setting in their project file (see AOT docs)
  6. In my C# code I imported the F# function and wrapped it in a function with the same signature, and added the UnmanagedCallersOnly attribute (see below). I'm not sure if this is the best way to do it, but it seems to work.
  7. Finally, publish the native DLL. For me (on linux) this was: dotnet publish /p:NativeLib=Shared -r linux-x64 -c Release
  8. Under Exports/bin/Release/net8.0/linux-x64/native will be your native library. In my case Exports.so or .dylib on mac, or .dll on windows.

C# example wrapper:

public class External
{
    [UnmanagedCallersOnly(EntryPoint = "add")]
    public static int Add(int a, int b)
    {
        return Fsharp.Add(a, b);
    }
}

Hope this helps you or someone else who comes across this in the future.

lukemerrick commented 8 months ago

@jameshulse you're my hero! Thank you for the clear explainer. I'm going to have to make a second attempt at my side project idea now.

jameshulse commented 8 months ago

@jameshulse you're my hero! Thank you for the clear explainer. I'm going to have to make a second attempt at my side project idea now.

No worries. Hopefully you can get something going with that. I'd still be keen to see support re-instated in F# directly, but this works for now.

I didn't personally get very far with my side project idea. I loaded the library in PHP, and it executed the Add function correctly, but if the process ended immediately after (like in a console app) then I got a segmentation fault. If I put a delay, then I didn't see the error. So I am wondering if there was some .NET garbage collection step or similar that was happening that I didn't have visibility of.

Anyway, good luck!