NativeLibrary example is missing mention of F#
Issue description
The AOT Native Library example does not include any F# code or documentation. Attempting to adapt the example to F# as in the below example causes a compiler error This attribute is currently unsupported by the F# compiler. Applying it will not achieve its intended effect. but I cannot find any mention of this limitation, workarounds, future support date, etc.
This is particularly confusing/frustrating because there is this lovely 2+ year old example of doing an F# native library in .NET SDK version 3, but there seems to be little else on the web discussing F# native libraries in more modern .NET.
It would be superb if someone would be willing to add some or all of the following information to either to this example repo or the official docs or both!
Information which would be super helpful to me and others like me
- Whether there is an official mainline way to compile a Native Library from F# in the same way as C# in .NET 7
- The backstory to the compiler error and the current outlook for adding
UnmanagedCallersOnlysupport to the F# compiler - Best practices for working around the limitations in the current release of .NET 7 (is it best to use CoreRT despite it being superseded by mainline .NET?)
Thank you for reading!
// 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
Target framework
Check the .NET target framework(s) being used, and include the version number(s).
- [ ] .NET Core
- [ ] .NET Framework
- [X] .NET Standard 7.0.102
If using the .NET Core SDK, include dotnet --info output. If using .NET Framework without the .NET Core SDK, include info from Visual Studio's Help > About Microsoft Visual Studio dialog.
dotnet --info output or About VS info
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
Just want to mention for any readers that I did get this working with F# on Mac OS M3.
// native_fsharp.fsproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PublishAot>true</PublishAot>
</PropertyGroup>
<ItemGroup>
<Compile Include="Library.fs" />
</ItemGroup>
</Project>
// Library.fs
module NativeFSharp
open System.Runtime.InteropServices
open System.Runtime.CompilerServices
open System
[<UnmanagedCallersOnly(EntryPoint = "Add", CallConvs = [| typeof<CallConvCdecl> |])>]
let Add (a: int) (b: int) : int =
// printfn uses reflection, won't work properly with AOT
Console.WriteLine "Printing from F#!"
a + b
// main.c
#include <stdio.h>
#include <dlfcn.h>
typedef int (*AddFunc)(int, int);
int main()
{
void *handle = dlopen("./bin/Release/net9.0/osx-arm64/publish/native_fsharp.dylib", RTLD_LAZY);
if (!handle)
{
fprintf(stderr, "Error loading library: %s\n", dlerror());
return 1;
}
// Get the Add function
AddFunc add = (AddFunc)dlsym(handle, "Add");
if (!add)
{
fprintf(stderr, "Error finding Add function: %s\n", dlerror());
dlclose(handle);
return 1;
}
// Call the function
int result = add(40, 2);
printf("Result of 40 + 2 = %d\n", result);
dlclose(handle);
return 0;
}
Then run the following:
dotnet publish -r osx-arm64 -c Release # Create the native lib
gcc main.c -o test_add -ldl # Build the C program
./test_add # Run it
You should see something like the this:
➜ native_fsharp ./test_add
Printing from F#!
Result of 40 + 2 = 42
I do get this warning from the F# compiler though:
This attribute is currently unsupported by the F# compiler. Applying it will not achieve its intended effect. F# Compiler[202](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/compiler-messages/fs0202)
But, it seems like it does achieve its intended effect 😄
Seconded: could someone from the f# team please clarify if the compiler warning is a false positive? Are there any hidden gotchas here? I've had no problem exposing f# code as a native library with a c# wrapper around it, but I'd ideally not add a c# project just to expose my f# code without compiler warnings.
@serefarikan I think there's just a set of attributes in the F# compiler that this warning appears for. I think it was probably done because no work has gone into supporting UnmanagedCallersOnly for F# per se, and I'm sure there are some scenarios where it "should" work but doesn't. If it works, it's because it's all IL under the hood and that's how UnmanagedCallersOnly works.
https://github.com/dotnet/fsharp/blob/3259edf831b702e3c10bd169143e805b03895c2b/src/Compiler/TypedTree/TcGlobals.fs#L1096
https://github.com/dotnet/fsharp/blob/3259edf831b702e3c10bd169143e805b03895c2b/src/Compiler/FSComp.txt#L42
You can probably use #nowarn to suppress this warning only in the areas you are using UnmanagedCallersOnly in F# code.
Indeed. The F# compiler currently does not implement any checks needed for this to work correctly, so it would allow you to author unsupported code. If you can do those checks manually without compiler guidance, the functions can be called from native code.