├── C# ├── NativeFromC#.cs └── README.md ├── C++ └── NativeFromC++.cpp ├── F# └── NativeFromF#.fs ├── Go └── NativeFromGo.go ├── Java └── NativeFromJava.java ├── JavaScript └── NativeFromJavaScript.js ├── Kotlin └── NativeFromKotlin.kt ├── Python └── NativeFromPython.py ├── README.md ├── Ruby └── NativeFromRuby.rb ├── Rust └── NativeFromRust.rs ├── Visual Basic └── NativeFromVisualBasic.vb └── _Native-C# ├── Native-C#.csproj └── NativeFunctions.cs /C#/NativeFromC#.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | [DllImport("/native-demo/native-c#.dll")] 4 | static extern void print_message(); 5 | 6 | [DllImport("/native-demo/native-c#.dll")] 7 | static extern int add_numbers(int x, int y); 8 | 9 | [DllImport("/native-demo/native-c#.dll")] 10 | static extern int subtract_numbers(int x, int y); 11 | 12 | [DllImport("/native-demo/native-c#.dll")] 13 | static extern double multiply_numbers(double x, double y); 14 | 15 | [DllImport("/native-demo/native-c#.dll")] 16 | static extern double divide_numbers(double x, double y); 17 | 18 | [DllImport("/native-demo/native-c#.dll")] 19 | static extern void populate_array(double[] arrayPointer, int arraySize); 20 | 21 | var added = add_numbers(7, 2); 22 | var subtracted = subtract_numbers(7, 2); 23 | var multiplied = multiply_numbers(7, 2); 24 | var divided = divide_numbers(7, 2); 25 | 26 | var array = new double[5]; 27 | populate_array(array, array.Length); 28 | 29 | print_message(); 30 | Console.WriteLine(added); 31 | Console.WriteLine(subtracted); 32 | Console.WriteLine(multiplied); 33 | Console.WriteLine(divided); 34 | Console.WriteLine(string.Join(", ", array)); -------------------------------------------------------------------------------- /C#/README.md: -------------------------------------------------------------------------------- 1 | In the C# example of `populate_array()`: 2 | * `native-c#.dll` expects `double*` 3 | * `NativeFromC#.cs` passes `double[]` 4 | 5 | It is very convenient that the native function consumes the `double[]` as a `double*`, 6 | as it means we don't need to directly interact with unsafe code to obtain the pointer. 7 | 8 | For completeness, here is an alternative approach to populating the array that 9 | explicitly uses the pointer. 10 | 11 | ```c# 12 | [DllImport("/native-demo/native-c#.dll")] 13 | static extern unsafe void populate_array(double* arrayPointer, double arraySize); 14 | 15 | var array = new double[5]; 16 | unsafe 17 | { 18 | fixed (double* pointer = array) 19 | { 20 | populate_array(pointer, array.Length); 21 | } 22 | } 23 | ``` -------------------------------------------------------------------------------- /C++/NativeFromC++.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() 6 | { 7 | auto nativeLibrary = LoadLibrary(TEXT("/native-demo/native-c#.dll")); 8 | auto print_message = GetProcAddress(nativeLibrary, "print_message"); 9 | auto add_numbers = (int (*)(int, int))GetProcAddress(nativeLibrary, "add_numbers"); 10 | auto subtract_numbers = (int (*)(int, int))GetProcAddress(nativeLibrary, "subtract_numbers"); 11 | auto multiply_numbers = (double (*)(double, double))GetProcAddress(nativeLibrary, "multiply_numbers"); 12 | auto divide_numbers = (double (*)(double, double))GetProcAddress(nativeLibrary, "divide_numbers"); 13 | auto populate_array = (void (*)(double[], int))GetProcAddress(nativeLibrary, "populate_array"); 14 | 15 | auto added = add_numbers(7, 2); 16 | auto subtracted = subtract_numbers(7, 2); 17 | auto multiplied = multiply_numbers(7, 2); 18 | auto divided = divide_numbers(7, 2); 19 | 20 | auto array = new double[5]; 21 | populate_array(array, sizeof(array)); 22 | 23 | print_message(); 24 | std::cout << added << std::endl; 25 | std::cout << subtracted << std::endl; 26 | std::cout << multiplied << std::endl; 27 | std::cout << divided << std::endl; 28 | for (int i = 0; i < 5; i++) 29 | { 30 | std::cout << array[i] << " "; 31 | } 32 | 33 | FreeLibrary(nativeLibrary); 34 | return 0; 35 | } -------------------------------------------------------------------------------- /F#/NativeFromF#.fs: -------------------------------------------------------------------------------- 1 | open System.Runtime.InteropServices 2 | 3 | module NativeLibrary = 4 | [] 5 | extern void print_message() 6 | 7 | [] 8 | extern int add_numbers(int x, int y) 9 | 10 | [] 11 | extern int subtract_numbers(int x, int y) 12 | 13 | [] 14 | extern double multiply_numbers(double x, double y) 15 | 16 | [] 17 | extern double divide_numbers(double x, double y) 18 | 19 | [] 20 | extern void populate_array(double[] array, int arraySize) 21 | 22 | let added = NativeLibrary.add_numbers(7, 2) 23 | let subtracted = NativeLibrary.subtract_numbers(7, 2) 24 | let multiplied = NativeLibrary.multiply_numbers(7, 2) 25 | let divided = NativeLibrary.divide_numbers(7, 2) 26 | 27 | let array = Array.zeroCreate 5 28 | NativeLibrary.populate_array(array, array.Length) 29 | 30 | NativeLibrary.print_message() 31 | printfn "%i" added 32 | printfn "%i" subtracted 33 | printfn "%f" multiplied 34 | printfn "%f" divided 35 | printfn "%A" array -------------------------------------------------------------------------------- /Go/NativeFromGo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "syscall" 7 | "unsafe" 8 | ) 9 | 10 | var ( 11 | nativeLibrary, _ = syscall.LoadLibrary("/native-demo/native-c#.dll") 12 | print_message, _ = syscall.GetProcAddress(nativeLibrary, "print_message") 13 | add_numbers, _ = syscall.GetProcAddress(nativeLibrary, "add_numbers") 14 | subtract_numbers, _ = syscall.GetProcAddress(nativeLibrary, "subtract_numbers") 15 | multiply_numbers, _ = syscall.GetProcAddress(nativeLibrary, "multiply_numbers") 16 | divide_numbers, _ = syscall.GetProcAddress(nativeLibrary, "divide_numbers") 17 | populate_array, _ = syscall.GetProcAddress(nativeLibrary, "populate_array") 18 | 19 | // floating point returned in r2 (https://tip.golang.org/src/syscall/dll_windows.go line 158) 20 | added, _, _ = syscall.SyscallN(add_numbers, 7, 2) 21 | subtracted, _, _ = syscall.SyscallN(subtract_numbers, 7, 2) 22 | _, multiplied, _ = syscall.SyscallN(multiply_numbers, uintptr(math.Float64bits(7)), uintptr(math.Float64bits(2))) 23 | _, divided, _ = syscall.SyscallN(divide_numbers, uintptr(math.Float64bits(7)), uintptr(math.Float64bits(2))) 24 | 25 | array [5]float64 26 | pointer = unsafe.Pointer(&array[0]) 27 | ) 28 | 29 | func main() { 30 | defer syscall.FreeLibrary(nativeLibrary) 31 | 32 | syscall.SyscallN(print_message) 33 | fmt.Println(added) 34 | fmt.Println(subtracted) 35 | fmt.Println(math.Float64frombits(uint64(multiplied))) 36 | fmt.Println(math.Float64frombits(uint64(divided))) 37 | 38 | syscall.SyscallN(populate_array, uintptr(pointer), uintptr(len(array))) 39 | fmt.Println(array) 40 | } 41 | -------------------------------------------------------------------------------- /Java/NativeFromJava.java: -------------------------------------------------------------------------------- 1 | import com.sun.jna.Library; 2 | import com.sun.jna.Native; 3 | 4 | import java.util.Arrays; 5 | 6 | public class NativeFromJava { 7 | 8 | public interface NativeLibrary extends Library { 9 | NativeLibrary INSTANCE = Native.load("/native-demo/native-c#.dll", NativeLibrary.class); 10 | void print_message(); 11 | int add_numbers(int x, int y); 12 | int subtract_numbers(int x, int y); 13 | double multiply_numbers(double x, double y); 14 | double divide_numbers(double x, double y); 15 | void populate_array(double[] array, int arraySize); 16 | } 17 | 18 | public static void main(String[] args) { 19 | int added = NativeLibrary.INSTANCE.add_numbers(7, 2); 20 | int subtracted = NativeLibrary.INSTANCE.subtract_numbers(7, 2); 21 | double multiplied = NativeLibrary.INSTANCE.multiply_numbers(7, 2); 22 | double divided = NativeLibrary.INSTANCE.divide_numbers(7, 2); 23 | 24 | double[] array = new double[5]; 25 | NativeLibrary.INSTANCE.populate_array(array, array.length); 26 | 27 | NativeLibrary.INSTANCE.print_message(); 28 | System.out.println(added); 29 | System.out.println(subtracted); 30 | System.out.println(multiplied); 31 | System.out.println(divided); 32 | System.out.println(Arrays.toString(array)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /JavaScript/NativeFromJavaScript.js: -------------------------------------------------------------------------------- 1 | const ffi = require("ffi-napi"); 2 | const ArrayType = require("ref-array-napi"); 3 | 4 | const DoubleArray = ArrayType("double"); 5 | 6 | const nativeLibrary = ffi.Library("/native-demo/native-c#.dll", { 7 | print_message: ["void", []], 8 | add_numbers: ["int", ["int", "int"]], 9 | subtract_numbers: ["int", ["int", "int"]], 10 | multiply_numbers: ["double", ["double", "double"]], 11 | divide_numbers: ["double", ["double", "double"]], 12 | populate_array: ["void", [DoubleArray, "int"]], 13 | }); 14 | 15 | const added = nativeLibrary.add_numbers(7, 2); 16 | const subtracted = nativeLibrary.subtract_numbers(7, 2); 17 | const multiplied = nativeLibrary.multiply_numbers(7, 2); 18 | const divided = nativeLibrary.divide_numbers(7, 2); 19 | 20 | const array = new DoubleArray(5); 21 | nativeLibrary.populate_array(array, array.length); 22 | 23 | nativeLibrary.print_message(); 24 | console.log(added); 25 | console.log(subtracted); 26 | console.log(multiplied); 27 | console.log(divided); 28 | console.log(JSON.stringify(array)); -------------------------------------------------------------------------------- /Kotlin/NativeFromKotlin.kt: -------------------------------------------------------------------------------- 1 | import com.sun.jna.Library 2 | import com.sun.jna.Native 3 | 4 | interface NativeLibrary : Library { 5 | companion object { 6 | val INSTANCE = Native.load("/native-demo/native-c#.dll", NativeLibrary::class.java) as NativeLibrary 7 | } 8 | 9 | fun print_message() 10 | fun add_numbers(x: Int, y: Int): Int 11 | fun subtract_numbers(x: Int, y: Int): Int 12 | fun multiply_numbers(x: Double, y: Double): Double 13 | fun divide_numbers(x: Double, y: Double): Double 14 | fun populate_array(array: DoubleArray, arraySize: Int) 15 | } 16 | 17 | fun main() { 18 | val added = NativeLibrary.INSTANCE.add_numbers(7, 2) 19 | val subtracted = NativeLibrary.INSTANCE.subtract_numbers(7, 2) 20 | val multiplied = NativeLibrary.INSTANCE.multiply_numbers(7.0, 2.0) 21 | val divided = NativeLibrary.INSTANCE.divide_numbers(7.0, 2.0) 22 | 23 | val array = DoubleArray(5) 24 | NativeLibrary.INSTANCE.populate_array(array, array.size) 25 | 26 | NativeLibrary.INSTANCE.print_message() 27 | println(added) 28 | println(subtracted) 29 | println(multiplied) 30 | println(divided) 31 | println(array.contentToString()) 32 | } -------------------------------------------------------------------------------- /Python/NativeFromPython.py: -------------------------------------------------------------------------------- 1 | from ctypes import * 2 | 3 | nativeLibrary = cdll.LoadLibrary("/native-demo/native-c#.dll") 4 | # argtypes & restypes assumed to be int 5 | nativeLibrary.multiply_numbers.argtypes = [c_double, c_double] 6 | nativeLibrary.multiply_numbers.restype = c_double 7 | nativeLibrary.divide_numbers.argtypes = [c_double, c_double] 8 | nativeLibrary.divide_numbers.restype = c_double 9 | nativeLibrary.populate_array.argtypes = [POINTER(c_double), c_int] 10 | 11 | added = nativeLibrary.add_numbers(7, 2) 12 | subtracted = nativeLibrary.subtract_numbers(7, 2) 13 | multiplied = nativeLibrary.multiply_numbers(7, 2) 14 | divided = nativeLibrary.divide_numbers(7, 2) 15 | 16 | array = (c_double * 5)(*[]) 17 | nativeLibrary.populate_array(array, len(array)) 18 | 19 | nativeLibrary.print_message() 20 | print(added) 21 | print(subtracted) 22 | print(multiplied) 23 | print(divided) 24 | print(list(array)) 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Use C# library from any[*](#--) language 2 | This repo demonstrates how to use [.NET 7](https://dotnet.microsoft.com/en-us/download/dotnet/7.0)'s [Native AOT](https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/) feature to build a native DLL that can be called from other languages, such as Java and Python. 3 | 4 | ### 1. Enable native AOT publishing 5 | In the `.csproj` file add `true` ([see example](/_Native-C%23/Native-C%23.csproj)) 6 | 7 | ### 2. Expose native functions 8 | Associate the C# native functions with the `[UnmanagedCallersOnly]` attribute ([see example](/_Native-C%23/NativeFunctions.cs)) 9 | 10 | ### 3. Publish to a native DLL 11 | For these examples the native DLL is published to `/native-demo/native-c#.dll`: 12 | ```shell 13 | dotnet publish ./_native-c#/ -r win-x64 -o /native-demo 14 | ``` 15 | 16 | ### 4. Use the native DLL in any[*](#--) language 17 | Load the library at runtime and call the native functions. See examples in: 18 | - [C#](/C%23/NativeFromC%23.cs) 19 | - [C++](/C%2B%2B/NativeFromC%2B%2B.cpp) 20 | - [F#](/F%23/NativeFromF%23.fs) 21 | - [Go](/Go/NativeFromGo.go) 22 | - [Java](/Java/NativeFromJava.java) 23 | - [JavaScript](/JavaScript/NativeFromJavaScript.js) 24 | - [Kotlin](/Kotlin/NativeFromKotlin.kt) 25 | - [Python](/Python/NativeFromPython.py) 26 | - [Ruby](/Ruby/NativeFromRuby.rb) 27 | - [Rust](/Rust/NativeFromRust.rs) 28 | - [Visual Basic](/Visual%20Basic/NativeFromVisualBasic.vb) 29 | 30 | All examples showcase using native functions that: 31 | - prints a message to the console, 32 | - returns a 32-bit integer (`7 + 2`, `7 - 2`), 33 | - returns a 64-bit double (`7 * 2`, `7 / 2`), 34 | - modifies an array of 64-bit doubles (`array[i] = (i * 2) + 0.5`) 35 | 36 | The output should look something like: 37 | ``` 38 | Hello from native code written in C#! 39 | 9 40 | 5 41 | 14.0 42 | 3.5 43 | [0.5, 2.5, 4.5, 6.5, 8.5] 44 | ``` 45 | 46 | ### * * * 47 | 48 | > [!NOTE] 49 | > I spent a long time trying to get this to work in MATLAB and R but ultimately failed. 50 | > Both languages seem to require some level of C wrapper to work. 51 | > I would be very interested to see a solution to this! 52 | -------------------------------------------------------------------------------- /Ruby/NativeFromRuby.rb: -------------------------------------------------------------------------------- 1 | require 'fiddle' 2 | 3 | nativeLibrary = Fiddle.dlopen('/native-demo/native-c#.dll') 4 | print_message = Fiddle::Function.new(nativeLibrary['print_message'], [], Fiddle::TYPE_VOID) 5 | add_numbers = Fiddle::Function.new(nativeLibrary['add_numbers'], [Fiddle::TYPE_INT, Fiddle::TYPE_INT], Fiddle::TYPE_INT) 6 | subtract_numbers = Fiddle::Function.new(nativeLibrary['subtract_numbers'], [Fiddle::TYPE_INT, Fiddle::TYPE_INT], Fiddle::TYPE_INT) 7 | multiply_numbers = Fiddle::Function.new(nativeLibrary['multiply_numbers'], [Fiddle::TYPE_DOUBLE, Fiddle::TYPE_DOUBLE], Fiddle::TYPE_DOUBLE) 8 | divide_numbers = Fiddle::Function.new(nativeLibrary['divide_numbers'], [Fiddle::TYPE_DOUBLE, Fiddle::TYPE_DOUBLE], Fiddle::TYPE_DOUBLE) 9 | populate_array = Fiddle::Function.new(nativeLibrary['populate_array'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT], Fiddle::TYPE_VOID) 10 | 11 | added = add_numbers.call(7, 2) 12 | subtracted = subtract_numbers.call(7, 2) 13 | multiplied = multiply_numbers.call(7, 2) 14 | divided = divide_numbers.call(7, 2) 15 | 16 | array = Array.new(5, 0.0) 17 | pointer = Fiddle::Pointer[array.pack('d*')] 18 | populate_array.call(pointer, array.size) 19 | array = pointer.to_s(array.size * Fiddle::SIZEOF_DOUBLE).unpack('d*') 20 | 21 | print_message.call() 22 | p added 23 | p subtracted 24 | p multiplied 25 | p divided 26 | p array -------------------------------------------------------------------------------- /Rust/NativeFromRust.rs: -------------------------------------------------------------------------------- 1 | use libloading::{Library, Symbol}; 2 | 3 | fn main() { 4 | unsafe { 5 | let native_library = Library::new("/native-demo/native-c#.dll").unwrap(); 6 | let print_message: Symbol = native_library.get(b"print_message").unwrap(); 7 | let add_numbers: Symbol i32> = native_library.get(b"add_numbers").unwrap(); 8 | let subtract_numbers: Symbol i32> = native_library.get(b"subtract_numbers").unwrap(); 9 | let multiply_numbers: Symbol f64> = native_library.get(b"multiply_numbers").unwrap(); 10 | let divide_numbers: Symbol f64> = native_library.get(b"divide_numbers").unwrap(); 11 | let populate_array: Symbol = native_library.get(b"populate_array").unwrap(); 12 | 13 | let added = add_numbers(7, 2); 14 | let subtracted = subtract_numbers(7, 2); 15 | let multiplied = multiply_numbers(7.0, 2.0); 16 | let divided = divide_numbers(7.0, 2.0); 17 | 18 | let mut array: [f64; 5] = [0.0; 5]; 19 | populate_array(array.as_mut_ptr(), array.len()); 20 | 21 | print_message(); 22 | println!("{}", added); 23 | println!("{}", subtracted); 24 | println!("{}", multiplied); 25 | println!("{}", divided); 26 | println!("{:?}", array); 27 | } 28 | } -------------------------------------------------------------------------------- /Visual Basic/NativeFromVisualBasic.vb: -------------------------------------------------------------------------------- 1 | Imports System.Runtime.InteropServices 2 | 3 | Module NativeFromVisualBasic 4 | _ 5 | Sub print_message() 6 | End Sub 7 | 8 | _ 9 | Function add_numbers(x As Integer, y As Integer) As Integer 10 | End Function 11 | 12 | _ 13 | Function subtract_numbers(x As Integer, y As Integer) As Integer 14 | End Function 15 | 16 | _ 17 | Function multiply_numbers(x As Double, y As Double) As Double 18 | End Function 19 | 20 | _ 21 | Function divide_numbers(x As Double, y As Double) As Double 22 | End Function 23 | 24 | _ 25 | Sub populate_array(array As Double(), arraySize As Integer) 26 | End Sub 27 | 28 | Sub Main() 29 | Dim added = add_numbers(7, 2) 30 | Dim subtracted = subtract_numbers(7, 2) 31 | Dim multiplied = multiply_numbers(7, 2) 32 | Dim divided = divide_numbers(7, 2) 33 | 34 | Dim array(5) As Double 35 | populate_array(array, array.Length) 36 | 37 | print_message() 38 | Console.WriteLine(added) 39 | Console.WriteLine(subtracted) 40 | Console.WriteLine(multiplied) 41 | Console.WriteLine(divided) 42 | Console.WriteLine(String.Join(", ", array)) 43 | End Sub 44 | End Module 45 | -------------------------------------------------------------------------------- /_Native-C#/Native-C#.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Library 5 | net7.0 6 | Native_CSharp 7 | enable 8 | enable 9 | true 10 | true 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /_Native-C#/NativeFunctions.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using System.Runtime.InteropServices; 3 | 4 | public class Native 5 | { 6 | [UnmanagedCallersOnly(EntryPoint = "print_message")] 7 | public static void PrintMessage() => Console.WriteLine("Hello from native code written in C#!"); 8 | 9 | [UnmanagedCallersOnly(EntryPoint = "add_numbers")] 10 | public static int Add(int x, int y) => x + y; 11 | 12 | [UnmanagedCallersOnly(EntryPoint = "subtract_numbers")] 13 | public static int Subtract(int x, int y) => x - y; 14 | 15 | [UnmanagedCallersOnly(EntryPoint = "multiply_numbers")] 16 | public static double Multiply(double x, double y) => x * y; 17 | 18 | [UnmanagedCallersOnly(EntryPoint = "divide_numbers")] 19 | public static double Divide(double x, double y) => x / y; 20 | 21 | [UnmanagedCallersOnly(EntryPoint = "populate_array")] 22 | public static unsafe void PopulateArray(double* arrayPointer, int arraySize) 23 | { 24 | for (int i = 0; i < arraySize; i++) 25 | { 26 | arrayPointer[i] = (i * 2) + 0.5; 27 | } 28 | } 29 | } --------------------------------------------------------------------------------