Last active
July 8, 2024 06:39
-
-
Save vadimkantorov/c9ab97aff995ea01ea7f6248ddc733bc to your computer and use it in GitHub Desktop.
Run a C# function from Python using Python.NET from https://pythonnet.github.io/pythonnet/python.html Call csharpfrompython.py which first calls the compiler and then calls the functions, feature request of better compiler bindings at https://github.com/pythonnet/pythonnet/issues/2196 ; includes two examples of NumPy -> C# array marshalling
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace CSharpFromPythonNamespace | |
{ | |
public class CSharpFromPythonClass | |
{ | |
public string Hi(string x) | |
{ | |
return "Hi " + x; | |
} | |
public static string Hello(string x) | |
{ | |
return "Hello " + x; | |
} | |
public static string World(System.ReadOnlyMemory<float> memory) | |
{ | |
return memory.ToString() + ':' + string.Join(", ", memory.ToArray()); | |
} | |
} | |
// https://stackoverflow.com/a/52191681/445810 | |
public sealed unsafe class UnmanagedMemoryManager<T> : System.Buffers.MemoryManager<T> where T : unmanaged | |
{ | |
public UnmanagedMemoryManager(System.Int64 pointer, System.Int32 length) | |
{ | |
this._pointer = (T*)pointer; | |
this._length = length; | |
} | |
public System.ReadOnlyMemory<T> ReadOnlyMemory => this.Memory; | |
public override System.Span<T> GetSpan() => new System.Span<T>(this._pointer, this._length); | |
public override System.Buffers.MemoryHandle Pin(System.Int32 elementIndex = 0) => new System.Buffers.MemoryHandle(this._pointer + elementIndex); | |
public override void Unpin() { } | |
protected override void Dispose(bool disposing) { } | |
private readonly T* _pointer; | |
private readonly System.Int32 _length; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# https://pythonnet.github.io/pythonnet/python.html | |
# wget https://dot.net/v1/dotnet-install.sh && bash dotnet-install.sh --channel 7.0 | |
# python -m pip install pythonnet --user | |
# DOTNET_ROOT="$HOME/.dotnet" PYTHONNET_RUNTIME=coreclr python -m clr | |
import os | |
import tempfile | |
import subprocess | |
import ctypes | |
os.environ['DOTNET_ROOT'] = os.path.expanduser('~/.dotnet') | |
os.environ['PYTHONNET_RUNTIME'] = 'coreclr' | |
import clr | |
def asNetArray(npArray): | |
# https://github.com/pythonnet/pythonnet/issues/514#issuecomment-350375105 | |
#Given a `numpy.ndarray` returns a CLR `System.Array`. See _MAP_NP_NET for | |
#the mapping of Numpy dtypes to CLR types. | |
#Note: `complex64` and `complex128` arrays are converted to `float32` | |
import numpy as np | |
import System | |
#and `float64` arrays respectively with shape [m,n,...] -> [m,n,...,2] | |
_MAP_NP_NET = { | |
np.dtype('float32'): System.Single, | |
np.dtype('float64'): System.Double, | |
np.dtype('int8') : System.SByte, | |
np.dtype('int16') : System.Int16, | |
np.dtype('int32') : System.Int32, | |
np.dtype('int64') : System.Int64, | |
np.dtype('uint8') : System.Byte, | |
np.dtype('uint16') : System.UInt16, | |
np.dtype('uint32') : System.UInt32, | |
np.dtype('uint64') : System.UInt64, | |
np.dtype('bool') : System.Boolean, | |
} | |
dims = npArray.shape | |
dtype = npArray.dtype | |
# For complex arrays, we must make a view of the array as its corresponding | |
# float type. | |
if dtype == np.complex64: | |
dtype = np.dtype('float32') | |
dims.append(2) | |
npArray = npArray.view(np.float32).reshape(dims) | |
elif dtype == np.complex128: | |
dtype = np.dtype('float64') | |
dims.append(2) | |
npArray = npArray.view(np.float64).reshape(dims) | |
netDims = System.Array.CreateInstance(System.Int32, npArray.ndim) | |
for I in range(npArray.ndim): | |
netDims[I] = System.Int32(dims[I]) | |
if not npArray.flags.c_contiguous: | |
npArray = npArray.copy(order='C') | |
assert npArray.flags.c_contiguous | |
try: | |
netArray = System.Array.CreateInstance(_MAP_NP_NET[dtype], netDims) | |
except KeyError: | |
raise NotImplementedError("asNetArray does not yet support dtype {}".format(dtype)) | |
try: # Memmove | |
destHandle = System.Runtime.InteropServices.GCHandle.Alloc(netArray, System.Runtime.InteropServices.GCHandleType.Pinned) | |
sourcePtr = npArray.__array_interface__['data'][0] | |
destPtr = destHandle.AddrOfPinnedObject().ToInt64() | |
ctypes.memmove(destPtr, sourcePtr, npArray.nbytes) | |
finally: | |
if destHandle.IsAllocated: destHandle.Free() | |
return netArray | |
cs_paths = [os.path.abspath('csharpfrompython.cs')] | |
dll_path = tempfile.mkstemp(suffix = '.dll')[-1] # os.path.abspath('csharpfrompython.dll') | |
compile_first_and_overlink_all_available_reference_assemblies = True | |
if compile_first_and_overlink_all_available_reference_assemblies: | |
#DOTNET_ROOT="$HOME/.dotnet" | |
#DOTNETSDKVER=$("$DOTNET_ROOT/dotnet" --version) | |
#DOTNETFWKVER=$("$DOTNET_ROOT/dotnet" --list-runtimes | grep Microsoft.NETCore.App | tail -n 1 | cut -d' ' -f2) | |
#DOTNETLIBDIR="$DOTNET_ROOT/shared/Microsoft.NETCore.App/$DOTNETFWKVER" | |
#"$DOTNET_ROOT/dotnet" "$DOTNET_ROOT/sdk/$DOTNETSDKVER/Roslyn/bincore/csc.dll" $(find "$DOTNETLIBDIR" -name "*.dll" -printf '-r:"%p" ') -target:library -out:csharpfrompython.dll csharpfrompython.cs | |
DOTNETSDKVER = subprocess.check_output([os.path.join(os.environ['DOTNET_ROOT'], 'dotnet'), '--version']).decode().strip() | |
DOTNETFWKVER = [line.split()[1] for line in subprocess.check_output([os.path.join(os.environ['DOTNET_ROOT'], 'dotnet'), '--list-runtimes']).decode().splitlines() if 'Microsoft.NETCore.App' in line][-1] | |
DOTNETLIBDIR = os.path.join(os.environ['DOTNET_ROOT'], 'shared', 'Microsoft.NETCore.App', DOTNETFWKVER) | |
csc_args_reference_assembly_paths = ['-r:' + os.path.join(dirname, basename) for dirname, dirs, basenames in os.walk(DOTNETLIBDIR) for basename in basenames if basename.endswith('.dll')] | |
csc_args_main = ['/unsafe', '-target:library', '-out:' + dll_path] + cs_paths # /unsafe needed for UnmanagedMemoryManager which is needed for zero-copy NumPy->C# array marhsalling | |
csc_args = csc_args_reference_assembly_paths + csc_args_main | |
clr.AddReference(os.path.join(os.environ['DOTNET_ROOT'], 'sdk', DOTNETSDKVER, 'Roslyn', 'bincore', 'csc.dll')) | |
import Microsoft.CodeAnalysis.CSharp.CommandLine | |
# print(csc_args) | |
assert 0 == Microsoft.CodeAnalysis.CSharp.CommandLine.Program.Main(csc_args) | |
clr.AddReference(dll_path) | |
os.remove(dll_path) # seems we can delete the assembly even before usage | |
import CSharpFromPythonNamespace, System | |
print(CSharpFromPythonNamespace.CSharpFromPythonClass().Hi('world')) # does print "Hi world" | |
print(CSharpFromPythonNamespace.CSharpFromPythonClass.Hello('you')) # does print "Hello you" | |
import numpy as np | |
arr = np.array([1,2,3], dtype = np.float32) | |
print('reference python output:', arr) | |
print('the copying way: NumPy array => Python array => C# array => ReadOnlyMemory<float>', CSharpFromPythonNamespace.CSharpFromPythonClass.World(System.ReadOnlyMemory[System.Single](System.Array[System.Single](arr.tolist())))) | |
print('yet another way: NumPy array => C# array => ReadOnlyMemory<float>', CSharpFromPythonNamespace.CSharpFromPythonClass.World(System.ReadOnlyMemory[System.Single](asNetArray(arr)))) | |
ptr = arr.__array_interface__['data'][0] | |
cnt = arr.__array_interface__['shape'][0] | |
mgr = CSharpFromPythonNamespace.UnmanagedMemoryManager[System.Single](ptr, cnt) | |
print('the zero-copy way: NumPy array => raw pointer => C# pointer => custom UnmanagedMemoryManager<float> => Memory<float> => ReadOnlyMemory<float>', CSharpFromPythonNamespace.CSharpFromPythonClass.World(mgr.ReadOnlyMemory)) | |
print('reference python output one more time, to ensure that array was not garbage-colected:', arr) | |
# prints: | |
# reference python output: [1. 2. 3.] | |
# the copying way: NumPy array => Python array => C# array => ReadOnlyMemory<float> System.ReadOnlyMemory<Single>[3]:1, 2, 3 | |
# yet another way: NumPy array => C# array => ReadOnlyMemory<float> System.ReadOnlyMemory<Single>[3]:1, 2, 3 | |
# the zero-copy way: NumPy array => raw pointer => C# pointer => custom UnmanagedMemoryManager<float> => Memory<float> => ReadOnlyMemory<float> System.ReadOnlyMemory<Single>[3]:1, 2, 3 | |
# reference python output one more time, to ensure that array was not garbage-colected: [1. 2. 3.] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment