Recently I was asked if it’s possible to call a library written in C# from a native C++ library on Windows. I remembered that I’ve read something about native libraries in C# and CoreRT. I’ve used CoreRT a lot and already blogged about it: Compile a .NET Core app to a single native binary

If an executable binary is possible in C#, a library should be possible too, right? As it turns out it is! I’ve followed the tutorial Writing Native Libraries in C# and using them in other languages and it worked on the first try.

As I am a huge fan of the clean and simple syntax and structure of the F# programming language, the logical next step was to port the C# library to F#, which can be called by C++ (or any other language that can call standard C ABI).

Create a native F# library

First, create a new F# library project.

mkdir src dotnet new classlib -lang=F# -o .\src\NativeLib

Now add a nuget.config file to the project, as we need an additional NuGet source for the native compiler. Add the following content to the nuget.config file.

<?xml version="1.0" encoding="utf-8"?> <configuration> <packageSources> <!--To inherit the global NuGet package sources remove the <clear/> line below --> <clear /> <add key="dotnet-core" value="https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json" /> <add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" /> </packageSources> </configuration>

To be able to export a function with an interface callable from C++, we need to annotate the function with an attribute. Create the file NativeCallable.fs with the following content.

namespace System.Runtime.InteropServices open System open System.Runtime.InteropServices [<AttributeUsage(AttributeTargets.Method)>] type NativeCallableAttribute() = inherit System.Attribute() [<DefaultValue>] val mutable public EntryPoint : string [<DefaultValue>] val mutable public CallingConvention : CallingConvention

The next step is the actual library with the functions we want to export. Create a file NativeLibrary.fs with the functions we want to export, annotated with the custom attribute we just created.

module NativeLibrary open System.Runtime.InteropServices open System [<NativeCallable(EntryPoint = "add", CallingConvention = CallingConvention.StdCall)>] let Add (a: int) (b: int) : int = a + b [<NativeCallable(EntryPoint = "write_line", CallingConvention = CallingConvention.StdCall)>] let WriteLine (pString: IntPtr) : int = try let str = Marshal.PtrToStringAnsi(pString) Console.WriteLine(str) 0 with | _ -> -1

This exports two functions. The first add will add two integers and the second one write_line writes a line to stdout.

To be able to compile to a native library we need to modify the NativeLib.fsproj file to contain a reference to the CoreRT compiler and we add a few optimizations to reduce the size of the binary. To learn more about optimizations see: Optimizing CoreRT

<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> <Platforms>AnyCPU;x64</Platforms> </PropertyGroup> <ItemGroup> <Compile Include="NativeCallable.fs" /> <Compile Include="NativeLibrary.fs" /> </ItemGroup> <ItemGroup> <PackageReference Include="Microsoft.DotNet.ILCompiler" Version="1.0.0-alpha-*" /> </ItemGroup> <PropertyGroup> <IlcInvariantGlobalization>true</IlcInvariantGlobalization> <RootAllApplicationAssemblies>false</RootAllApplicationAssemblies> <IlcGenerateCompleteTypeMetadata>false</IlcGenerateCompleteTypeMetadata> <IlcGenerateStackTraceData>false</IlcGenerateStackTraceData> <IlcOptimizationPreference>Size</IlcOptimizationPreference> <IlcFoldIdenticalMethodBodies>true</IlcFoldIdenticalMethodBodies> </PropertyGroup> </Project>

Our library is now complete, so its time to compile it to a native Windows DLL.

dotnet publish /p:NativeLib=Shared -r win-x64 -c Release

This creates a DLL and a Lib in the folder $(SolutionDir)\src\NativeLib\bin\Release

etstandard2.0\win-x64

ative.

You can check if everything worked by having a look at the exports of the DLL on penet.io. Just upload the created DLL and click on Exports.

We can clearly see our two functions are in the export table of the DLL.

Create a native C++ app and use our F# DLL

The F# part was the easy one. As usually C++ is a big pain, as we need to configure the linker to use our library.

First, we create a new empty C++ console application. Then we create a header file called NativeLib.h with the declarations for our functions.

#pragma once extern "C" int __stdcall add(int a, int b); extern "C" void __stdcall write_line(const char* pString);

After that, we create our Main.cpp file with the following content:

#include <iostream> #include "NativeLib.h" using namespace std; int main() { int result = add(1, 2); cout << result << endl; write_line("Hello World!"); return 0; }

As we built our F# library for x64, switch the configuration for the build to x64 in the Configuration Manager.

Next, we need to edit the properties of the project. Open the settings and change the following values. Make sure to select All Configurations to change the values for Debug and Release.

General -> Output Directory = $(ProjectDir)bin\$(Platform)\$(Configuration)\

Linker -> General -> Additional Library Directories = $(SolutionDir)src\NativeLib\bin\Release

etstandard2.0\win-x64

ative;%(AdditionalLibraryDirectories)

Linker -> Input -> Additional Dependencies = NativeLib.lib;%(AdditionalDependencies)

Build Events -> Post-Build Events = xcopy /y /d “$(SolutionDir)src\NativeLib\bin\Release

etstandard2.0\win-x64

ative\NativeLib.dll” “$(OutDir)”

The build event guarantees that our previously built F# library will be copied to the output folder of the C++ console application.

Now build and run the C++ console application and the following output appears:

3 Hello World!

You can find the complete code on Github: Native F# Library.