Posted on • Edited on • Originally published atmazeez.dev
Writing Native Libraries in C# and using them in other languages
Note: Stefan Hausotte has portedthis little experiment to F#.
Recently I stumbled uponthis article fromMichal Strehovsky. It was a great introduction to CoreRT, it made me curious, can you write native libraries with CoreRT? and the answer was Yes!
Michal Strehovský@mstrehovsky@mhmd_azeez@xoofx CoreRT can generate .dll/.so/.dylib and .lib/.a files that export C# functions in a way that makes them directly callable through standard FFI. A sample walkthrough is here:github.com/dotnet/corert/…07:58 AM - 02 May 2019
I have a little library that I want to be available for multiple languages, so I was quite interested in it. So I tried out theofficial sample and was delighted with the results.
I compiled the library usingdotnet publish /p:NativeLib=Shared -r win-x64 -c Release
and it produced a 4.52 MB dll. With the help of Michal and by following the steps ofthis article, I was able to get the size down to 1.67 MB, which is good enough for me.
The official sample hasa class that contains two methods:Add
andWriteLine
which demonstrate how to take primitives and strings as parameters. By default, CoreRT only allows primitives as parameter types, you'll have to marshal anything else that's more complex. However,System.Runtime.InteropServices.Marshal
does some have helpful methods.
publicclassClass1{[NativeCallable(EntryPoint="add",CallingConvention=CallingConvention.StdCall)]publicstaticintAdd(inta,intb){returna+b;}[NativeCallable(EntryPoint="write_line",CallingConvention=CallingConvention.StdCall)]publicstaticintWriteLine(IntPtrpString){// The marshalling code is typically auto-generated by a custom tool in larger projects.try{// NativeCallable methods only accept primitive arguments. The primitive arguments// have to be marshalled manually if necessary.stringstr=Marshal.PtrToStringAnsi(pString);Console.WriteLine(str);}catch{// Exceptions escaping out of NativeCallable methods are treated as unhandled exceptions.// The errors have to be marshalled manually if necessary.return-1;}return0;}}
The methods that are decorated withNativeCallable
cannot be called through normal C# methods, but they can be called through P/Invoke 😈.
To do that, you have to:
- Build the native library by running
dotnet publish /p:NativeLib=Shared -r win-x64 -c Release
in its folder. - Create a new console app (I created a dotnet core console app, but it doesn't matter).
- Right click on the project and click
Add => Exisiting Item
. - Browse to
bin\Release\netcoreapp2.2\win-x64\native
folder of the native library project and then select the dll and click onAdd As Link
. - Right Click on the dll in Solution Explorer and click on properties.
- Change
Copy to Output Directory
toCopy if newer
. - Change Solutions Platform to
x64
- And then change the code in
Program.cs
as follows:
classProgram{[DllImport("NativeLibrary.dll",EntryPoint="add",CallingConvention=CallingConvention.StdCall)]publicstaticexternintAdd(inta,intb);[DllImport("NativeLibrary.dll",EntryPoint="write_line",CallingConvention=CallingConvention.StdCall)]publicstaticexternvoidWriteLine(stringtext);staticvoidMain(string[]args){varresult=Add(1,2);WriteLine(result.ToString());WriteLine("Hello World!");}}
Now run the console app and you'll get an output like this:
3Hello World!
Now p/invoking the library might not be very useful, but compiling a class library as a native library opens doors for other languages to call the library.
It was a long and painful process, but I was eventually able to reference the library from the C++ app. Thisvideo and thisarticle were super helpful.
Here are the steps:
1 . Add an empty C++ project to the solution.
2 . Add a source file and paste in this code snippet:
#include <iostream>#include <NativeLibrary.h>usingnamespacestd;voidmain(){intresult=add(1,2);cout<<result<<endl;write_line("Hello World!");}
3 . Create a new header file calledNativeLibrary.h
(That's the name of the library) and paste in this code snippet:
#pragma onceextern"C"int__stdcalladd(inta,intb);extern"C"void__stdcallwrite_line(constchar*pString);
As you can see have written the signatures of the functions that are exported fromNativeLibrary
.
4 . Right Click on the C++ project and Click on Properties.
5 . ChooseAll Configurations
fromConfiguration:
. This will make sure that the changes apply to bothRelease
andDebug
configurations (And any other configuration you might have).
6 . Go to General and changeOutput Directory
to$(ProjectDir)bin\$(Platform)\$(Configuration)\
. This is not necessary, but I felt more at home like this.
7 . Go toC\C++
>Linker
>General
and add$(SolutionDir)NativeLibrary\bin\Release\netcoreapp2.2\win-x64\native
toAdditional Library Directories
. This allows the linker to discoverNativeLibrary.lib
.
8 . Go toC\C++
>Linker
>Input
and addNativeLibrary.lib
to the list ofAdditional Dependencies
.
9 . Go toBuild Events
>Post-Build Event
and paste in this code snippet toCommand Line
:
xcopy/y /d"$(SolutionDir)NativeLibrary\bin\Release\netcoreapp2.2\win-x64\native\NativeLibrary.dll""$(OutDir)"
This will copyNativeLibrary.dll
to the output dir whenever you build the C++ project.
10 . Build and run the application and you should see this output:
3Hello World!
If this is not cool, I don't know what is.
The source code isavailable on GitHub.
Top comments(1)
For further actions, you may consider blocking this person and/orreporting abuse