netcorehost
                                
                                 netcorehost copied to clipboard
                                
                                    netcorehost copied to clipboard
                            
                            
                            
                        Easier method of executing Rust code from C#
Hello, I was wondering of an easier method to execute Rust code (unmanaged) from C# (managed). I already know of the return-string-from-managed example and was able to easily get it working, but it required a field and two methods to be created for a single unmanaged method:
// Required since only unsafe code can use function pointers.
private static unsafe void DoRustyThings() {
    DoRustyThingsPtr();
}
private static unsafe delegate*<void> DoRustyThingsPtr;
[UnmanagedCallersOnly]
public static unsafe void SetDoRustyThingsPtr(delegate*<void> doRustyThingsPtr) => DoRustyThingsPtr = doRustyThingsPtr;
Mono has a really simple way to call managed code from unmanaged code by adding an attribute:
[MethodImplAttribute(MethodImplOptions.InternalCall)]
public static void DoRustyThings();
Is there anyway that running managed code from unmanaged code could become easier and lead to less boilerplate? If its not implemented just yet, I may be able to mess around with implementing it and make a PR, or just hack something up to get it working for me. Thanks!
You have 2 options :
- converting rust's pointer into delegate (which can be quite slow) using https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.getdelegateforfunctionpointer?view=net-8.0 - using PInvoke : https://learn.microsoft.com/fr-fr/dotnet/standard/native-interop/pinvoke executable and DLL are almost the same thing for operating systeme (a least Window and Linux). To use your executable as a DLL, you need to enable the export table on your rust binary with -Zexport-executable-symbolswhich is only available on nightly. On the C# side you can simply use the[DllImportAttribute]with a custom name resolver (https://learn.microsoft.com/fr-fr/dotnet/api/system.runtime.interopservices.nativelibrary.setdllimportresolver?view=net-8.0). Every method marked as extern with the#[no_mangle]attribute can be used in C#.
Calling rust from C# allow you to use unmanaged structs as function paramter, and all kind of references will be marshall as pointer (or rust references).
[MethodImplAttribute(MethodImplOptions.InternalCall)] is only available when you make a fork of CoreClr (not loading it from another executable), or when you use Mono.
hope this helps
Hi @D3lta-2-1 , I was very happy to find your instructions because your second option was exactly what I was trying to do, i.e.: call rust code from my managed code.
I did recompile my host application binary with -Zexport-executable-symbols and then used a cutsom DllImportResolver, however when NativeLibrary.Loading the binary itself, I'm just getting: "target/debug/test_wgpu_sdl: cannot dynamically load position-independent executable".
Now, looking at the binary itself, I seem to be missing an .edata section despite the-Zexport-executable-symbols despite what https://doc.rust-lang.org/beta/unstable-book/compiler-flags/export-executable-symbols.html is claiming. Is that the culprit or merely a platform difference (I'm on x86 linux)? Am I holding it wrong? Do you have an example you could point me to?
EDIT: FTR, I also tried  NativeLibrary.GetExport(NativeLibrary.GetMainProgramHandle(), <functionname>) w/o success, I assume MainProgram her refers to the dotnet program rather than the rust host binary. I also checked that "
0000000000117b40 g     F .text  0000000000000032       functionname
EDIT2: I wrote a small C program that dlopens itself to  see if a .edata section would need to be present and the answer is: it doesn't need to be there. My program can happily dlopen itself. If I NativeLibrary.Load the same binary from dotnet, I still get: "Unhandled exception. System.DllNotFoundException: Unable to load shared library './main' or one of its dependencies. In order to help diagnose loading problems, consider using a tool like strace. If you're using glibc, consider setting the LD_DEBUG environment variable:
./main: cannot dynamically load position-independent executable" :/