Comment by jcranmer

2 years ago

That's not the direction being talked about here. Try calling the C# method from C or C++ or Rust.

(I somewhat recently did try setting up mono to be able to do this... it wasn't fun.)

It is very easy to call a C# method from C++, since .NET has a COM interop layer. From C++ this will just look as a class with no fields but a bunch of virtual methods. Alternatively, you can easily convert a static method to a native function pointer and then invoke that - this way it's also easy to do from C, Rust, and just about anything else that speaks the C ABI.

If your C# method doesn't take any arguments like managed strings or arrays that require marshaling, it's also very cheap (and there's unsafe pointers, structs, and fixed arrays that can be used at interop boundary to avoid marshaling even for fairly complicated data structures).

.NET was very much designed around these kinds of things. It's not a coincidence that its full type system covers everything that you can find in C.

What you may have been looking for is these:

- https://learn.microsoft.com/en-us/dotnet/core/deploying/nati...

- https://github.com/dotnet/samples/blob/main/core/nativeaot/N...

With that said, Mono has been a staple choice for embedding in game-script style scenarios, in particular, because of the ability to directly call its methods inside (provided the caller honors the calling convention correctly), but it has been slowly becoming more of a liability as you are missing out on a lot of performance by not hosting CoreCLR instead.

For .dll/.so/.dylib's, it is easier and often better to just build a native library with naot instead (the links above, you can also produce statically linkable binaries but it might have issues on e.g. macOS which has...not the most reliable linker that likes to take breaking changes).

This type of library works in almost every scenario a library implemented in C/C++/Rust with C exports does. For example, here someone implemented a hello-world demonstration of using C# to write an OBS plugin: https://sharovarskyi.com/blog/posts/dotnet-obs-plugin-with-n...

Using the exports boils down to just this https://github.com/kostya9/DotnetObsPluginWithNativeAOT/blob... and specifying correct build flags.

  • I haven't been looking for those because I don't work with .NET. Regardless, what you're linking still needs callers and callees to agree on calling convention and special binding annotations across FFI boundaries which isn't particularly interesting from the perspective of language implementation like the promises of Graal or WASM + GC + component model.

    • There is no free lunch. WASM just means another lowest common denominator abstraction for FFI. I'm also looking forward to WASM getting actually good so .NET could properly target it (because shipping WASM-compiled GC is really, really painful, it works acceptably today, but could be better). Current WasmGC spec is pretty much unusable by any language that has non-primitive GC implementation.

      Just please don't run WASM on the server, we're already getting diminishing generational performance gains in hardware, no need to reduce them further.

      The exports in the examples follow C ABI with respective OS/ISA-specific calling convention.