#dotnet #managed #bindings #c-sharp #ffi

nightly rustishka

Better Interop between C# and Rust

1 unstable release

0.1.0 May 20, 2024

#2 in #dotnet

Download history 157/week @ 2024-05-20 8/week @ 2024-05-27

165 downloads per month

MIT license

205KB
2.5K SLoC

Rustishka

This project offers cool ways to interact between these super safe & supe fast languages!

Rust <===> C# interop at the highest level!

This effect is created by using special libraries on the C# side and on the Rust side.

Overview

C# Side

The library provides a mini interface for the Rust library to interact with the DotNet environment.

private struct BridgeConvention
{
    public delegate* <object, Type> FGetType;
    public delegate* <string, bool, Type> FSearchType;
    public delegate* <Type, int, void*> FGetMethodAtSlot;
    public delegate* <Type, object> FAlloc;
    public delegate* <byte*, int, string> FAllocString;
    public delegate* <Type, int, Array> FAllocArray;
    public delegate* <delegate*<void>, Exception> FTryCatch;
    public delegate* <Exception, void> FThrow;
}

The developer must connect the library to the interface using a function call:

RustishkaBridge.Bridge.ConnectRustModule(string libPath, out IntPtr moduleHandle) // to load lib and connect
// or
RustishkaBridge.Bridge.ConnectRustModule(IntPtr moduleHandle) // connect without loading. Use if the module has already been loaded before.

Rust side

The developer provides an export function with this signature. It will be called in ConnectRustModule

initialize_rustishka!();

Important thing

Structures with class content for Rust have to be created manually.

A tool for obtaining offsets

You can use Sharplab as well.

Features

  • Rust lib can search for a type in the DotNet runtime:
let integer_type = search_type_cached(&String::from("System.Int32"), false);
  • Rust lib can allocate objects & arrays:
let someType = search_type_cached(&someTypeAQN, false);
let obj: *mut NetObject<SomeType> = allocate(someType); // alloc without constructor invoke !!!
  • Rust lib can call virtual functions:
let someType: *mut NetObject<SystemType> = ...;
let baseType = someType.get_base_type();

This is presented in the form of pseudo wrappers:

define_virtual!(pub, get_base_type, 11, 4, *mut NetObject<SystemType>);

where: 11 - row eq slotId / 8, 4 - index eq slotId % 8

  • Rust lib can call non-virtual instance and static functions.
someObject.some_instance_method(args);
NetObject::<SomeObject>::some_static_method(args)
let val: *mut NetObject<SomeObject> = SomeObject::new(args);

where

impl NetObject<SomeObject> {
   define_function!(pub some_instance_method, 7, self: *mut Self, arg_name: ArgType); // where 7 - slotId
   define_function!(pub some_static_method, 8, arg_name: ArgType);
}
impl SomeObject {
   define_constructor!(pub new, arg_name: ArgType);
}
  • Rust lib can access to static fields
SomeObject::get_cool_static_field();
// where
define_typeof!(SomeObject, "blahblah");
impl SomeObject {
   define_static_field!(pub get_cool_static_field, "CoolStaticField", SomeFieldType);
}
  • Rust lib can use typeof sugar
pub struct SomeObject { }
define_typeof!(SomeObject, "SomeObject AssemblyQualifiedName");
// in method
let ty : *mut NetObject<SystemType> = SomeObject::type_of();
  • Rust lib can easily alloc managed arrays
managed_array!(ElementType, elements num, elements);
// ex:
managed_array!(i32, 5, 0, 1, 2, 3, 4);
managed_array!(SystemType, 2, i32::type_of(), f32::type_of());
  • Rust lib can use almost all of .NET Reflection's features! (Even use DynamicMethod!)

As you can see: it`s very human design, very easy to use.

Examples

C# side

Rust side

TODO

  • Add support for field access
  • .Net type inheritance in Rust by creating a custom type via .Net TypeBuilder & overriding methodtable entries.
  • Source generators (atm it's scary Rustishka.Tools)

Dependencies

~110KB