|
| 1 | +# lnks |
| 2 | + |
| 3 | +[](https://github.com/OpenByteDev/lnks/actions/workflows/ci.yml) [](https://crates.io/crates/lnks) [](https://docs.rs/lnks) [](https://deps.rs/repo/github/openbytedev/lnks) [](https://github.com/OpenByteDev/lnks/blob/master/LICENSE) |
| 4 | + |
| 5 | +<!-- cargo-sync-readme start --> |
| 6 | + |
| 7 | +A Rust library for hosting the .NET Core runtime. |
| 8 | + |
| 9 | +It utilizes the .NET Core hosting API to load and execute managed code from within the current process. |
| 10 | + |
| 11 | +## Usage |
| 12 | +### Running an application |
| 13 | +The example below will setup the runtime, load `Test.dll` and run its `Main` method: |
| 14 | +```rust |
| 15 | +let hostfxr = nethost::load_hostfxr().unwrap(); |
| 16 | +let context = hostfxr.initialize_for_dotnet_command_line(pdcstr!("Test.dll")).unwrap(); |
| 17 | +let result = context.run_app().value(); |
| 18 | +``` |
| 19 | +The full example can be found in [examples/run-app](https://github.com/OpenByteDev/lnks/tree/master/examples/run-app). |
| 20 | + |
| 21 | +### Calling a managed function |
| 22 | +A function pointer to a managed method can be aquired using an [`AssemblyDelegateLoader`](https://docs.rs/lnks/*/lnks/hostfxr/struct.AssemblyDelegateLoader.html). |
| 23 | +This is only supported for [`HostfxrContext`'s](https://docs.rs/lnks/*/lnks/hostfxr/struct.HostfxrContext.html) that are initialized using [`Hostfxr::initialize_for_runtime_config`](https://docs.rs/lnks/*/lnks/hostfxr/struct.Hostfxr.html#method.initialize_for_runtime_config). The [`runtimeconfig.json`](https://docs.microsoft.com/en-us/dotnet/core/run-time-config/) is automatically generated for executables, for libraries it is neccessary to add `<GenerateRuntimeConfigurationFiles>True</GenerateRuntimeConfigurationFiles>` to the projects `.csproj` file. |
| 24 | + |
| 25 | +#### Using the default signature |
| 26 | +The default method signature is defined as follows: |
| 27 | +```csharp |
| 28 | +public delegate int ComponentEntryPoint(IntPtr args, int sizeBytes); |
| 29 | +``` |
| 30 | + |
| 31 | +A method with the default signature (see code below) can be loaded using [`AssemblyDelegateLoader::get_function_with_default_signature`](https://docs.rs/lnks/*/lnks/hostfxr/struct.AssemblyDelegateLoader.html#method.get_function_with_default_signature). |
| 32 | + |
| 33 | +**C#** |
| 34 | +```cs |
| 35 | +using System; |
| 36 | + |
| 37 | +namespace Test { |
| 38 | + public static class Program { |
| 39 | + public static int Hello(IntPtr args, int sizeBytes) { |
| 40 | + Console.WriteLine("Hello from C#!"); |
| 41 | + return 42; |
| 42 | + } |
| 43 | + } |
| 44 | +} |
| 45 | +``` |
| 46 | + |
| 47 | +**Rust** |
| 48 | +```rust |
| 49 | +let hostfxr = nethost::load_hostfxr().unwrap(); |
| 50 | +let context = |
| 51 | + hostfxr.initialize_for_runtime_config(pdcstr!("Test.runtimeconfig.json")).unwrap(); |
| 52 | +let fn_loader = |
| 53 | + context.get_delegate_loader_for_assembly(pdcstr!("Test.dll")).unwrap(); |
| 54 | +let hello = fn_loader.get_function_with_default_signature( |
| 55 | + pdcstr!("Test.Program, Test"), |
| 56 | + pdcstr!("Hello"), |
| 57 | +).unwrap(); |
| 58 | +let result = unsafe { hello(std::ptr::null(), 0) }; |
| 59 | +assert_eq!(result, 42); |
| 60 | +``` |
| 61 | + |
| 62 | +#### Using UnmanagedCallersOnly |
| 63 | +A function pointer to a method annotated with [`UnmanagedCallersOnly`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute) can be loaded without specifying its signature (as these methods cannot be overloaded). |
| 64 | + |
| 65 | +**C#** |
| 66 | +```cs |
| 67 | +using System; |
| 68 | +using System.Runtime.InteropServices; |
| 69 | + |
| 70 | +namespace Test { |
| 71 | + public static class Program { |
| 72 | + [UnmanagedCallersOnly] |
| 73 | + public static void UnmanagedHello() { |
| 74 | + Console.WriteLine("Hello from C#!"); |
| 75 | + } |
| 76 | + } |
| 77 | +} |
| 78 | +``` |
| 79 | + |
| 80 | +**Rust** |
| 81 | +```rust |
| 82 | +let hostfxr = nethost::load_hostfxr().unwrap(); |
| 83 | +let context = |
| 84 | + hostfxr.initialize_for_runtime_config(pdcstr!("Test.runtimeconfig.json")).unwrap(); |
| 85 | +let fn_loader = |
| 86 | + context.get_delegate_loader_for_assembly(pdcstr!("Test.dll")).unwrap(); |
| 87 | +let hello = fn_loader.get_function_with_unmanaged_callers_only::<fn()>( |
| 88 | + pdcstr!("Test.Program, Test"), |
| 89 | + pdcstr!("UnmanagedHello"), |
| 90 | +).unwrap(); |
| 91 | +hello(); // prints "Hello from C#!" |
| 92 | +``` |
| 93 | + |
| 94 | + |
| 95 | +#### Specifying the delegate type |
| 96 | +Another option is to define a custom delegate type and passing its assembly qualified name to [`AssemblyDelegateLoader::get_function`](https://docs.rs/lnks/*/lnks/hostfxr/struct.AssemblyDelegateLoader.html#method.get_function). |
| 97 | + |
| 98 | +**C#** |
| 99 | +```cs |
| 100 | +using System; |
| 101 | + |
| 102 | +namespace Test { |
| 103 | + public static class Program { |
| 104 | + public delegate void CustomHelloFunc(); |
| 105 | + |
| 106 | + public static void CustomHello() { |
| 107 | + Console.WriteLine("Hello from C#!"); |
| 108 | + } |
| 109 | + } |
| 110 | +} |
| 111 | +``` |
| 112 | + |
| 113 | +**Rust** |
| 114 | +```rust |
| 115 | +let hostfxr = nethost::load_hostfxr().unwrap(); |
| 116 | +let context = |
| 117 | + hostfxr.initialize_for_runtime_config(pdcstr!("Test.runtimeconfig.json")).unwrap(); |
| 118 | +let fn_loader = |
| 119 | + context.get_delegate_loader_for_assembly(pdcstr!("Test.dll")).unwrap(); |
| 120 | +let hello = fn_loader.get_function::<fn()>( |
| 121 | + pdcstr!("Test.Program, Test"), |
| 122 | + pdcstr!("CustomHello"), |
| 123 | + pdcstr!("Test.Program+CustomHelloFunc, Test") |
| 124 | +).unwrap(); |
| 125 | +hello(); // prints "Hello from C#!" |
| 126 | +``` |
| 127 | + |
| 128 | +The full examples can be found in [examples/call-managed-function](https://github.com/OpenByteDev/lnks/tree/master/examples/call-managed-function). |
| 129 | + |
| 130 | +### Passing complex parameters |
| 131 | +Examples for passing non-primitive parameters can be found in [examples/passing-parameters](https://github.com/OpenByteDev/lnks/tree/master/examples/passing-parameters). |
| 132 | + |
| 133 | +## Features |
| 134 | +- `nethost` - Links against nethost and allows for automatic detection of the hostfxr library. |
| 135 | +- `download-nethost` - Automatically downloads the latest nethost binary from [NuGet](https://www.nuget.org/packages/Microsoft.NETCore.DotNetHost/). |
| 136 | + |
| 137 | +<!-- cargo-sync-readme end --> |
| 138 | + |
| 139 | + |
| 140 | +## Related crates |
| 141 | +- [nethost-sys](https://crates.io/crates/nethost-sys) - bindings for the nethost library. |
| 142 | +- [hostfxr-sys](https://crates.io/crates/hostfxr-sys) - bindings for the hostfxr library. |
| 143 | +- [coreclr-hosting-shared](https://crates.io/crates/coreclr-hosting-shared) - shared bindings between [hostfxr-sys](https://crates.io/crates/hostfxr-sys) and [nethost-sys](https://crates.io/crates/nethost-sys). |
| 144 | + |
| 145 | +## Additional Information |
| 146 | +- [Hosting layer APIs](https://github.com/dotnet/core-setup/blob/master/Documentation/design-docs/hosting-layer-apis.md) |
| 147 | +- [Native hosting](https://github.com/dotnet/core-setup/blob/master/Documentation/design-docs/native-hosting.md#runtime-properties) |
| 148 | +- [Write a custom .NET Core host to control the .NET runtime from your native code](https://docs.microsoft.com/en-us/dotnet/core/tutorials/netcore-hosting) |
| 149 | + |
| 150 | +## License |
| 151 | +Licensed under the MIT license ([LICENSE](https://github.com/OpenByteDev/lnks/blob/master/LICENSE) or http://opensource.org/licenses/MIT) |
0 commit comments