OpenByteDev / netcorehost

A .NET Core hosting library written in Rust with included bindings for nethost and hostfxr.
MIT License
106 stars 7 forks source link

Easier method of executing Rust code from C# #32

Open SeanOMik opened 11 months ago

SeanOMik commented 11 months ago

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!

D3lta-2-1 commented 9 months ago

You have 2 options :

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

ignatz commented 6 months ago

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 "" is available and exported in the programs symbols, which is the case:

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" :/