netcorehost

repository·master·Indexed 41 days ago

https://github.com/openbytedev/netcorehost

A Rust library for hosting the .NET Core runtime within a native process. It enables developers to execute .NET applications or call managed functions from Rust using .NET Core hosting APIs, supporting features like UnmanagedCallersOnly, custom delegate types, and assembly loading from paths or bytes for .NET 8.0+.

Tokens
9K
Snippets
26
Records
34
Agent score
99%

What's inside netcorehost

  1. Call a managed function with a custom delegate type

    master

    For custom signatures, define a delegate in C# and provide its assembly-qualified name to AssemblyDelegateLoader::get_function. The third argument to get_function should be the assembly-qualified name of the delegate type (e.g., "Namespace.ClassName+DelegateName, AssemblyName").

    // C# Implementation
    using System;
    
    namespace Test {
        public static class Program {
            public delegate void CustomHelloFunc();
        
            public static void CustomHello() {
                Console.WriteLine("Hello from C!");
            }
        }
    }
    // Rust Implementation
    let hostfxr = nethost::load_hostfxr().unwrap();
    let context =
        hostfxr.initialize_for_runtime_config(pdcstr!("Test.runtimeconfig.json")).unwrap();
    let fn_loader =
        context.get_delegate_loader_for_assembly(pdcstr!("Test.dll")).unwrap();
    let hello = fn_loader.get_function::<fn()>(
        pdcstr!("Test.Program, Test"),
        pdcstr!("CustomHello"),
        pdcstr!("Test.Program+CustomHelloFunc, Test")
    ).unwrap();
    hello(); // prints "Hello from C!"
  2. Call a managed function using UnmanagedCallersOnly

    master

    Methods annotated with [UnmanagedCallersOnly] in C# can be loaded without specifying a signature because they cannot be overloaded. Use AssemblyDelegateLoader::get_function_with_unmanaged_callers_only::<T> where T is the function pointer type (e.g., fn()).

    // C# Implementation
    using System;
    using System.Runtime.InteropServices;
    
    namespace Test {
        public static class Program {
            [UnmanagedCallersOnly]
            public static void UnmanagedHello() {
                Console.WriteLine("Hello from C!");
            }
        }
    }
    // Rust Implementation
    let hostfxr = nethost::load_hostfxr().unwrap();
    let context =
        hostfxr.initialize_for_runtime_config(pdcstr!("Test.runtimeconfig.json")).unwrap();
    let fn_loader =
        context.get_delegate_loader_for_assembly(pdcstr!("Test.dll")).unwrap();
    let hello = fn_loader.get_function_with_unmanaged_callers_only::<fn()>(
        pdcstr!("Test.Program, Test"),
        pdcstr!("UnmanagedHello"),
    ).unwrap();
    hello(); // prints "Hello from C!"
  3. Call a managed function with the default signature

    master

    To call a managed method using the default signature, the C# method must match the following delegate shape:

    public delegate int ComponentEntryPoint(IntPtr args, int sizeBytes);

    In Rust, use Hostfxr::initialize_for_runtime_config to set up the context, obtain an AssemblyDelegateLoader via get_delegate_loader_for_assembly, and then use get_function_with_default_signature to retrieve the function pointer.

    // C# Implementation
    using System;
    
    namespace Test {
        public static class Program {
            public static int Hello(IntPtr args, int sizeBytes) {
                Console.WriteLine("Hello from C!");
                return 42;
            }
        }
    }
    // Rust Implementation
    let hostfxr = nethost::load_hostfxr().unwrap();
    let context =
        hostfxr.initialize_for_runtime_config(pdcstr!("Test.runtimeconfig.json")).unwrap();
    let fn_loader =
        context.get_delegate_loader_for_assembly(pdcstr!("Test.dll")).unwrap();
    let hello = fn_loader.get_function_with_default_signature(
        pdcstr!("Test.Program, Test"),
        pdcstr!("Hello"),
    ).unwrap();
    let result = unsafe { hello(std::ptr::null(), 0) };
    assert_eq!(result, 42);
  4. Configure .NET library projects for hosting

    master

    When hosting a .NET library (instead of an executable), you must ensure a runtimeconfig.json is generated. Add the following property to your .csproj file:

    <GenerateRuntimeConfigurationFiles>True</GenerateRuntimeConfigurationFiles>
  5. Run a .NET Core application from Rust

    master

    You can host and run a .NET Core executable (an application with a Main method) by initializing the runtime for a command line entry point. This requires loading hostfxr via nethost and then using initialize_for_dotnet_command_line with the path to your assembly.

    let hostfxr = nethost::load_hostfxr().unwrap();
    let context = hostfxr.initialize_for_dotnet_command_line(pdcstr!("Test.dll")).unwrap();
    let result = context.run_app().value();
  6. Understand HostfxrContext initialization types

    master

    The HostfxrContext<I> is a generic type where the type parameter I determines the initialization mode and available capabilities. There are two primary initialization modes:

    1. InitializedForRuntimeConfig: The context was initialized using a runtime configuration. In this mode, all delegate types are supported, but you cannot run a managed application directly from this context.
    2. InitializedForCommandLine: The context was initialized for the dotnet command line. In this mode, you can run a managed application using run_app, but only specific delegate types are supported: hdt_load_assembly_and_get_function_pointer and hdt_get_function_pointer.
  7. Convert between PdCString and PdCStr

    master

    The library provides several ways to move between owned (PdCString) and borrowed (PdCStr) types:

    • PdCString to PdCStr:
      • Use .as_ref() to get a &PdCStr.
      • PdCString implements Deref<Target = PdCStr>, so you can call PdCStr methods directly on a PdCString instance.
      • PdCString implements Borrow<PdCStr>.
    • PdCStr to PdCString:
      • Use .to_owned() to create an owned PdCString from a borrowed PdCStr.
    let owned = PdCString::from_str("example")?;
    
    // Borrowing via Deref
    let borrowed: &PdCStr = &owned;
    
    // Converting back to owned
    let owned_again = borrowed.to_owned();
  8. How AssemblyDelegateLoader works

    master

    The AssemblyDelegateLoader is a high-level utility designed to simplify acquiring function pointers to managed .NET methods. It wraps a DelegateLoader and an assembly path, ensuring that the specified assembly is loaded into its own AssemblyLoadContext (with dependency resolution via AssemblyDependencyResolver) upon the first request for a function. Subsequent calls to the same loader will find the method within the already-loaded assembly without re-loading it.

    // Conceptual usage pattern
    let loader = AssemblyDelegateLoader::new(delegate_loader, "/path/to/assembly.dll");
    
    // The first call loads the assembly
    let func = loader.get_function::<MyFnPtr>(&type_name, &method_name, &delegate_type_name)?;
    
    // Subsequent calls use the already loaded assembly
    let func2 = loader.get_function::<MyFnPtr>(&type_name, &method_name, &delegate_type_name)?;
  9. Call a managed function using the default signature

    master

    You can call managed methods that follow the default signature using AssemblyDelegateLoader::get_function_with_default_signature.

    Default Signature (C#):

    public delegate int ComponentEntryPoint(IntPtr args, int sizeBytes);

    Requirements:

    • The HostfxrContext must be initialized using Hostfxr::initialize_for_runtime_config (not initialize_for_dotnet_command_line).
    • For libraries, ensure <GenerateRuntimeConfigurationFiles>True</GenerateRuntimeConfigurationFiles> is set in your .csproj file so the runtimeconfig.json is generated.
    use netcorehost::{nethost, pdcstr};
    
    let hostfxr = nethost::load_hostfxr().unwrap();
    let context = hostfxr.initialize_for_runtime_config("path/to/runtimeconfig.json").unwrap();
    let fn_loader = context.get_delegate_loader_for_assembly("path/to/Test.dll").unwrap();
    
    // Load function with signature: (IntPtr, int)
    let hello = fn_loader.get_function_with_default_signature(
        pdcstr!("Test.Program, Test"),
        pdcstr!("Hello"),
    ).unwrap();
    
    // Call the function
    let result = unsafe { hello(std::ptr::null(), 0) };
  10. netcorehost features

    master

    The library provides the following features via Cargo features:

    • nethost: Links against nethost and allows for automatic detection of the hostfxr library.
    • download-nethost: Automatically downloads the latest nethost binary from NuGet (Microsoft.NETCore.DotNetHost).
  11. Run a managed application with InitializedForCommandLine

    master

    If your HostfxrContext was initialized for the command line (InitializedForCommandLine), you can execute the managed application associated with that context using run_app().

    // Only available if context is HostfxrContext<InitializedForCommandLine>
    let result = context.run_app();
    // result is an AppOrHostingResult containing the exit code or a hosting error
  12. Load the hostfxr library

    master

    To initialize the .NET hosting environment, you can use the load_hostfxr family of functions. These functions combine locating the hostfxr library and loading it into the process, returning a Hostfxr instance.

    Available loading strategies:

    • load_hostfxr(): Uses default search logic.
    • load_hostfxr_with_assembly_path(assembly_path): Locates hostfxr relative to the provided assembly.
    • load_hostfxr_with_dotnet_root(dotnet_root): Locates hostfxr within the specified .NET installation directory.

    Errors returned are of type LoadHostfxrError, which wraps either HostingError (errors from the .NET hosting components) or DlOpen errors (errors occurring during the dynamic library loading process).

    use netcorehost::{load_hostfxr, load_hostfxr_with_assembly_path, load_hostfxr_with_dotnet_root};
    
    // Load using default search
    let hostfxr = load_hostfxr().expect("Failed to load hostfxr");
    
    // Load relative to an assembly
    let hostfxr = load_hostfxr_with_assembly_path("path/to/app.dll").expect("Failed to load hostfxr");
    
    // Load using a specific .NET root
    let hostfxr = load_hostfxr_with_dotnet_root("/usr/share/dotnet").expect("Failed to load hostfxr");