├── tests ├── Test │ ├── .gitignore │ ├── Test-net10.0.csproj │ ├── Test-net8.0.csproj │ ├── Test-net9.0.csproj │ └── Program.cs ├── ClassLibrary │ ├── .gitignore │ ├── ClassLibrary-net8.0.csproj │ ├── ClassLibrary-net9.0.csproj │ ├── ClassLibrary-net10.0.csproj │ └── Library.cs ├── macro-build-tests │ ├── .gitignore │ ├── pdcstr-compile-fail.rs │ ├── pdcstr-pass.rs │ ├── pdcstr-compile-fail.other.stderr │ └── pdcstr-compile-fail.windows.stderr ├── macro-test-crate │ ├── .gitignore │ ├── src │ │ └── main.rs │ └── Cargo.toml ├── manual_close_frees_lib.rs ├── custom_delegate_type.rs ├── run_app.rs ├── unmanaged_callers_only.rs ├── runtime_properties.rs ├── pdcstr.rs ├── primary_and_secondary.rs ├── error_writer.rs ├── hello_world.rs ├── sdk_resolve.rs ├── load_assembly_manually.rs ├── environment_info.rs ├── common.rs ├── errors.rs └── unhandled_managed_exeptions.rs ├── .gitignore ├── examples ├── run-app │ ├── ExampleProject │ │ ├── .gitignore │ │ ├── Program.cs │ │ ├── ExampleProject.csproj │ │ └── ExampleProject.sln │ └── main.rs ├── run-app-with-args │ ├── ExampleProject │ │ ├── .gitignore │ │ ├── ExampleProject.csproj │ │ ├── Program.cs │ │ └── ExampleProject.sln │ └── main.rs ├── call-managed-function │ ├── ExampleProject │ │ ├── .gitignore │ │ ├── ExampleProject.csproj │ │ ├── Program.cs │ │ └── ExampleProject.sln │ └── main.rs ├── passing-parameters │ ├── ExampleProject │ │ ├── .gitignore │ │ ├── ExampleProject.csproj │ │ ├── ExampleProject.sln │ │ └── Program.cs │ └── main.rs └── return-string-from-managed │ ├── ExampleProject │ ├── .gitignore │ ├── ExampleProject.csproj │ ├── ExampleProject.sln │ └── Program.cs │ └── main.rs ├── src ├── error │ ├── mod.rs │ └── univ.rs ├── pdcstring │ ├── impl │ │ ├── mod.rs │ │ ├── other │ │ │ ├── error.rs │ │ │ ├── mod.rs │ │ │ ├── ext.rs │ │ │ ├── pdcstring.rs │ │ │ └── pdcstr.rs │ │ ├── windows │ │ │ ├── error.rs │ │ │ ├── mod.rs │ │ │ ├── ext.rs │ │ │ ├── pdcstring.rs │ │ │ └── pdcstr.rs │ │ └── traits.rs │ ├── mod.rs │ ├── error.rs │ └── shared.rs ├── bindings │ └── mod.rs ├── hostfxr │ ├── managed_function.rs │ ├── mod.rs │ ├── library1_0.rs │ ├── library.rs │ ├── runtime_property.rs │ ├── library6_0.rs │ ├── library2_1.rs │ ├── context.rs │ └── delegate_loader.rs ├── utils.rs ├── nethost.rs └── lib.rs ├── .github ├── scripts │ ├── uninstall-dotnet-ubuntu │ ├── install-dotnet-macos │ ├── install-dotnet-ubuntu │ ├── install-dotnet-windows.ps1 │ ├── uninstall-dotnet-macos │ └── uninstall-dotnet-windows.ps1 └── workflows │ └── ci.yml ├── LICENSE ├── Cargo.toml └── README.md /tests/Test/.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | obj/ 3 | bin/ 4 | -------------------------------------------------------------------------------- /tests/ClassLibrary/.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | obj/ 3 | bin/ 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .metadata 4 | .vscode 5 | -------------------------------------------------------------------------------- /examples/run-app/ExampleProject/.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | obj/ 3 | bin/ 4 | -------------------------------------------------------------------------------- /tests/macro-build-tests/.gitignore: -------------------------------------------------------------------------------- 1 | pdcstr-compile-fail.stderr 2 | -------------------------------------------------------------------------------- /examples/run-app-with-args/ExampleProject/.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | obj/ 3 | bin/ 4 | -------------------------------------------------------------------------------- /examples/call-managed-function/ExampleProject/.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | obj/ 3 | bin/ 4 | -------------------------------------------------------------------------------- /examples/passing-parameters/ExampleProject/.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | obj/ 3 | bin/ 4 | -------------------------------------------------------------------------------- /examples/return-string-from-managed/ExampleProject/.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | obj/ 3 | bin/ 4 | -------------------------------------------------------------------------------- /tests/macro-test-crate/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .metadata 4 | .vscode 5 | -------------------------------------------------------------------------------- /tests/macro-test-crate/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let _ = netcorehost::pdcstr!("test"); 3 | } 4 | -------------------------------------------------------------------------------- /src/error/mod.rs: -------------------------------------------------------------------------------- 1 | mod hosting_result; 2 | pub use hosting_result::*; 3 | 4 | mod univ; 5 | pub use univ::*; 6 | -------------------------------------------------------------------------------- /.github/scripts/uninstall-dotnet-ubuntu: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sudo apt remove 'dotnet*' 4 | sudo apt remove 'aspnetcore*' 5 | -------------------------------------------------------------------------------- /.github/scripts/install-dotnet-macos: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | chmod +x ./.github/scripts/install-dotnet-ubuntu 4 | ./.github/scripts/install-dotnet-ubuntu "$@" 5 | -------------------------------------------------------------------------------- /tests/macro-test-crate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "macro-test-crate" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | netcorehost = { path = "../.." } 8 | -------------------------------------------------------------------------------- /examples/run-app/ExampleProject/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public static class Program { 4 | public static void Main(string[] args) { 5 | Console.WriteLine("Hello from C#!"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/run-app/ExampleProject/ExampleProject.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net10.0 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/run-app-with-args/ExampleProject/ExampleProject.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net10.0 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/run-app-with-args/ExampleProject/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public static class Program { 4 | public static void Main(string[] args) { 5 | Console.WriteLine($"args[{args.Length}] = [{string.Join(", ", args)}]"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/ClassLibrary/ClassLibrary-net8.0.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ClassLibrary 5 | net8.0 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/ClassLibrary/ClassLibrary-net9.0.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ClassLibrary 5 | net9.0 6 | 7 | 8 | -------------------------------------------------------------------------------- /tests/ClassLibrary/ClassLibrary-net10.0.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ClassLibrary 5 | net10.0 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/pdcstring/impl/mod.rs: -------------------------------------------------------------------------------- 1 | mod traits; 2 | pub(crate) use traits::*; 3 | 4 | #[cfg(windows)] 5 | pub mod windows; 6 | #[cfg(windows)] 7 | pub(crate) use windows::*; 8 | 9 | #[cfg(not(windows))] 10 | pub mod other; 11 | #[cfg(not(windows))] 12 | pub(crate) use other::*; 13 | -------------------------------------------------------------------------------- /tests/macro-build-tests/pdcstr-compile-fail.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | // with internal nul 3 | let _ = netcorehost::pdcstr!("\0"); 4 | let _ = netcorehost::pdcstr!("somerandomteststring\0"); 5 | let _ = netcorehost::pdcstr!("somerandomteststring\0somerandomteststring"); 6 | } 7 | -------------------------------------------------------------------------------- /tests/Test/Test-net10.0.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | Test 6 | net10.0 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/Test/Test-net8.0.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | Test 6 | net8.0 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/Test/Test-net9.0.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | Test 6 | net9.0 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /tests/macro-build-tests/pdcstr-pass.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let _ = netcorehost::pdcstr!(""); 3 | let _ = netcorehost::pdcstr!("test"); 4 | let _ = netcorehost::pdcstr!("test with spaces"); 5 | let _ = netcorehost::pdcstr!("0"); 6 | let _ = netcorehost::pdcstr!("\\0"); 7 | let _ = netcorehost::pdcstr!("κόσμε"); 8 | } 9 | -------------------------------------------------------------------------------- /src/pdcstring/impl/other/error.rs: -------------------------------------------------------------------------------- 1 | use crate::pdcstring::{MissingNulTerminatorInner, ToStringErrorInner}; 2 | 3 | impl ToStringErrorInner for std::str::Utf8Error { 4 | fn index(&self) -> Option { 5 | self.error_len() 6 | } 7 | } 8 | 9 | impl MissingNulTerminatorInner for std::ffi::FromBytesWithNulError {} 10 | -------------------------------------------------------------------------------- /examples/call-managed-function/ExampleProject/ExampleProject.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Library 5 | net10.0 6 | true 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/pdcstring/impl/windows/error.rs: -------------------------------------------------------------------------------- 1 | use crate::pdcstring::{MissingNulTerminatorInner, ToStringErrorInner}; 2 | 3 | impl ToStringErrorInner for widestring::error::Utf16Error { 4 | fn index(&self) -> Option { 5 | Some(self.index()) 6 | } 7 | } 8 | 9 | impl MissingNulTerminatorInner for widestring::error::MissingNulTerminator {} 10 | -------------------------------------------------------------------------------- /src/bindings/mod.rs: -------------------------------------------------------------------------------- 1 | extern crate coreclr_hosting_shared; 2 | 3 | /// Module for shared bindings for all hosting components. 4 | pub use coreclr_hosting_shared::*; 5 | 6 | /// Module containing the raw bindings for hostfxr. 7 | pub use hostfxr_sys as hostfxr; 8 | 9 | /// Module containing the raw bindings for nethost. 10 | #[cfg(feature = "nethost")] 11 | pub use nethost_sys as nethost; 12 | -------------------------------------------------------------------------------- /.github/scripts/install-dotnet-ubuntu: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | VERSION=$1 4 | ARCH=$2 5 | 6 | curl -sSL https://dot.net/v1/dotnet-install.sh -o dotnet-install.sh 7 | chmod +x dotnet-install.sh 8 | ./dotnet-install.sh -Architecture "$ARCH" -Channel "$VERSION" 9 | 10 | DOTNET_ROOT="$HOME/.dotnet" 11 | echo "$DOTNET_ROOT" >> "$GITHUB_PATH" 12 | echo "DOTNET_ROOT=$DOTNET_ROOT" >> "$GITHUB_ENV" 13 | -------------------------------------------------------------------------------- /examples/passing-parameters/ExampleProject/ExampleProject.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Library 5 | net10.0 6 | true 7 | true 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/return-string-from-managed/ExampleProject/ExampleProject.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Library 5 | net10.0 6 | true 7 | true 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/run-app/main.rs: -------------------------------------------------------------------------------- 1 | use netcorehost::{nethost, pdcstr}; 2 | 3 | fn main() { 4 | let hostfxr = nethost::load_hostfxr().unwrap(); 5 | let context = hostfxr 6 | .initialize_for_dotnet_command_line(pdcstr!( 7 | "examples/run-app/ExampleProject/bin/Debug/net10.0/ExampleProject.dll" 8 | )) 9 | .unwrap(); 10 | context.run_app().as_hosting_exit_code().unwrap(); 11 | } 12 | -------------------------------------------------------------------------------- /src/pdcstring/impl/other/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) type PdCStringInnerImpl = std::ffi::CString; 2 | pub(crate) type PdCStrInnerImpl = std::ffi::CStr; 3 | pub(crate) type ToStringErrorInnerImpl = std::str::Utf8Error; 4 | pub(crate) type MissingNulTerminatorInnerImpl = std::ffi::FromBytesWithNulError; 5 | 6 | mod pdcstr; 7 | pub use pdcstr::*; 8 | 9 | mod pdcstring; 10 | #[allow(unused)] 11 | pub use pdcstring::*; 12 | 13 | mod error; 14 | #[allow(unused)] 15 | pub use error::*; 16 | 17 | mod ext; 18 | pub use ext::*; 19 | -------------------------------------------------------------------------------- /src/pdcstring/impl/windows/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) type PdCStringInnerImpl = widestring::U16CString; 2 | pub(crate) type PdCStrInnerImpl = widestring::U16CStr; 3 | pub(crate) type ToStringErrorInnerImpl = widestring::error::Utf16Error; 4 | pub(crate) type MissingNulTerminatorInnerImpl = widestring::error::MissingNulTerminator; 5 | 6 | mod pdcstr; 7 | pub use pdcstr::*; 8 | 9 | mod pdcstring; 10 | #[allow(unused)] 11 | pub use pdcstring::*; 12 | 13 | mod error; 14 | #[allow(unused)] 15 | pub use error::*; 16 | 17 | mod ext; 18 | pub use ext::*; 19 | -------------------------------------------------------------------------------- /.github/scripts/install-dotnet-windows.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [string]$Version, 3 | [string]$Arch 4 | ) 5 | 6 | Invoke-WebRequest -Uri https://dot.net/v1/dotnet-install.ps1 -OutFile dotnet-install.ps1 7 | ./dotnet-install.ps1 -Architecture $Arch -Channel $Version 8 | 9 | if ($Env:DOTNET_INSTALL_DIR) { 10 | $dotnetRoot = $Env:DOTNET_INSTALL_DIR 11 | } else { 12 | $dotnetRoot = Join-Path $Env:LOCALAPPDATA "Microsoft\dotnet" 13 | } 14 | 15 | Add-Content -Path $Env:GITHUB_PATH -Value $dotnetRoot 16 | Add-Content -Path $Env:GITHUB_ENV -Value "DOTNET_ROOT=$dotnetRoot" 17 | -------------------------------------------------------------------------------- /src/pdcstring/mod.rs: -------------------------------------------------------------------------------- 1 | mod error; 2 | pub use error::*; 3 | 4 | /// The platform-dependent character type used by the hosting components. 5 | pub type PdChar = crate::bindings::char_t; 6 | /// The unsigned version of the platform-dependent character type used by the hosting components. 7 | #[cfg(windows)] 8 | pub type PdUChar = u16; 9 | /// The unsigned version of the platform-dependent character type used by the hosting components. 10 | #[cfg(not(windows))] 11 | pub type PdUChar = u8; 12 | 13 | mod r#impl; 14 | pub use r#impl::*; 15 | 16 | mod shared; 17 | pub use shared::*; 18 | -------------------------------------------------------------------------------- /tests/ClassLibrary/Library.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace ClassLibrary { 6 | public class Library { 7 | [ModuleInitializer] 8 | internal static void Init() { 9 | Console.WriteLine("Library DLL Loaded"); 10 | Console.WriteLine($"Running under .NET {Environment.Version}"); 11 | } 12 | 13 | [UnmanagedCallersOnly] 14 | public static int Hello() { 15 | Console.WriteLine("Hello from Library!"); 16 | return 42; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/call-managed-function/ExampleProject/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace ExampleProject { 5 | public static class Program { 6 | public delegate void HelloWorld1Delegate(); 7 | public static void HelloWorld1() { 8 | Console.WriteLine("Hello from C#!"); 9 | } 10 | 11 | [UnmanagedCallersOnly] 12 | public static void HelloWorld2() { 13 | Console.WriteLine("Hello from C#!"); 14 | } 15 | 16 | public static int HelloWorld3(IntPtr arg, int argLength) { 17 | Console.WriteLine("Hello from C#!"); 18 | return 0; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/manual_close_frees_lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "netcore3_0")] 2 | 3 | use std::sync::Arc; 4 | 5 | use netcorehost::nethost; 6 | use rusty_fork::rusty_fork_test; 7 | 8 | #[path = "common.rs"] 9 | mod common; 10 | 11 | rusty_fork_test! { 12 | #[test] 13 | fn manual_close_frees_lib() { 14 | common::setup(); 15 | 16 | let hostfxr = nethost::load_hostfxr().unwrap(); 17 | let context = hostfxr 18 | .initialize_for_runtime_config(common::test_runtime_config_path()) 19 | .unwrap(); 20 | 21 | let weak = Arc::downgrade(&hostfxr.lib); 22 | drop(hostfxr); 23 | context.close().unwrap(); 24 | 25 | assert_eq!(weak.strong_count(), 0); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/macro-build-tests/pdcstr-compile-fail.other.stderr: -------------------------------------------------------------------------------- 1 | error: nul byte found in the literal 2 | --> $DIR/pdcstr-compile-fail.rs:3:34 3 | | 4 | 3 | let _ = netcorehost::pdcstr!("\0"); 5 | | ^^^^ 6 | 7 | error: nul byte found in the literal 8 | --> $DIR/pdcstr-compile-fail.rs:4:34 9 | | 10 | 4 | let _ = netcorehost::pdcstr!("somerandomteststring\0"); 11 | | ^^^^^^^^^^^^^^^^^^^^^^^^ 12 | 13 | error: nul byte found in the literal 14 | --> $DIR/pdcstr-compile-fail.rs:5:34 15 | | 16 | 5 | let _ = netcorehost::pdcstr!("somerandomteststring\0somerandomteststring"); 17 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 18 | -------------------------------------------------------------------------------- /examples/run-app-with-args/main.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use netcorehost::{nethost, pdcstr, pdcstring::PdCString}; 4 | 5 | fn main() { 6 | let hostfxr = nethost::load_hostfxr().unwrap(); 7 | 8 | let args = env::args() 9 | .skip(1) // skip rust host program name 10 | .map(|arg| PdCString::from_os_str(arg).unwrap()); 11 | 12 | let context = hostfxr 13 | .initialize_for_dotnet_command_line_with_args( 14 | pdcstr!( 15 | "examples/run-app-with-args/ExampleProject/bin/Debug/net10.0/ExampleProject.dll" 16 | ), 17 | args, 18 | ) 19 | .unwrap(); 20 | 21 | let result = context.run_app().value(); 22 | println!("Exit code: {}", result); 23 | } 24 | -------------------------------------------------------------------------------- /.github/scripts/uninstall-dotnet-macos: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Download uninstall tool 4 | latest_release_json=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" https://api.github.com/repos/dotnet/cli-lab/releases/latest) 5 | download_url=$(echo "$latest_release_json" | grep "browser_download_url" | grep "osx-x64.tar.gz" | cut -d '"' -f 4) 6 | filename=$(basename "$download_url") 7 | curl -L -o "$filename" "$download_url" 8 | 9 | # Prepare uninstall tool 10 | tar -xzf "$filename" 11 | uninstall_tool_path=$(find . -name dotnet-core-uninstall) 12 | chmod +x "$uninstall_tool_path" 13 | 14 | # Perform uninstall 15 | sudo "$uninstall_tool_path" remove --yes --force --all --runtime --verbosity detailed 16 | sudo "$uninstall_tool_path" remove --yes --force --all --sdk --verbosity detailed 17 | -------------------------------------------------------------------------------- /src/pdcstring/impl/other/ext.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{CStr, CString}; 2 | 3 | use crate::pdcstring::{PdCStr, PdCString}; 4 | 5 | pub trait PdCStringExt 6 | where 7 | Self: Sized, 8 | { 9 | fn from_c_string(s: CString) -> Self; 10 | fn into_c_string(self) -> CString; 11 | } 12 | 13 | impl PdCStringExt for PdCString { 14 | fn from_c_string(s: CString) -> Self { 15 | Self::from_inner(s) 16 | } 17 | 18 | fn into_c_string(self) -> CString { 19 | self.into_inner() 20 | } 21 | } 22 | 23 | pub trait PdCStrExt { 24 | fn from_c_str(s: &CStr) -> &Self; 25 | fn as_c_str(&self) -> &CStr; 26 | } 27 | 28 | impl PdCStrExt for PdCStr { 29 | fn from_c_str(s: &CStr) -> &Self { 30 | Self::from_inner(s) 31 | } 32 | 33 | fn as_c_str(&self) -> &CStr { 34 | self.as_inner() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/pdcstring/impl/windows/ext.rs: -------------------------------------------------------------------------------- 1 | use widestring::{U16CStr, U16CString}; 2 | 3 | use crate::pdcstring::{PdCStr, PdCString}; 4 | 5 | pub trait PdCStringExt 6 | where 7 | Self: Sized, 8 | { 9 | fn from_u16_c_string(s: U16CString) -> Self; 10 | fn into_u16_c_string(self) -> U16CString; 11 | } 12 | 13 | impl PdCStringExt for PdCString { 14 | fn from_u16_c_string(s: U16CString) -> Self { 15 | Self::from_inner(s) 16 | } 17 | 18 | fn into_u16_c_string(self) -> U16CString { 19 | self.into_inner() 20 | } 21 | } 22 | 23 | pub trait PdCStrExt { 24 | fn from_u16_c_str(s: &U16CStr) -> &Self; 25 | fn as_u16_c_str(&self) -> &U16CStr; 26 | } 27 | 28 | impl PdCStrExt for PdCStr { 29 | fn from_u16_c_str(s: &U16CStr) -> &Self { 30 | Self::from_inner(s) 31 | } 32 | 33 | fn as_u16_c_str(&self) -> &U16CStr { 34 | self.as_inner() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/pdcstring/impl/windows/pdcstring.rs: -------------------------------------------------------------------------------- 1 | use widestring::U16CString; 2 | 3 | use crate::pdcstring::{ContainsNul, PdCStringInner, PdChar}; 4 | 5 | impl PdCStringInner for U16CString { 6 | fn from_str(s: impl AsRef) -> Result { 7 | Ok(U16CString::from_str(s)?) 8 | } 9 | 10 | fn from_os_str(s: impl AsRef) -> Result { 11 | U16CString::from_os_str(s).map_err(|e| e.into()) 12 | } 13 | 14 | unsafe fn from_str_ptr(ptr: *const PdChar) -> Self { 15 | unsafe { U16CString::from_ptr_str(ptr) } 16 | } 17 | 18 | fn from_vec(vec: impl Into>) -> Result { 19 | U16CString::from_vec(vec).map_err(|e| e.into()) 20 | } 21 | 22 | fn into_vec(self) -> Vec { 23 | U16CString::into_vec(self) 24 | } 25 | 26 | fn into_vec_with_nul(self) -> Vec { 27 | U16CString::into_vec_with_nul(self) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/custom_delegate_type.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "netcore3_0")] 2 | 3 | use netcorehost::{nethost, pdcstr}; 4 | use rusty_fork::rusty_fork_test; 5 | 6 | #[path = "common.rs"] 7 | mod common; 8 | 9 | rusty_fork_test! { 10 | fn unmanaged_caller_hello_world() { 11 | common::setup(); 12 | 13 | let hostfxr = nethost::load_hostfxr().unwrap(); 14 | 15 | let context = hostfxr 16 | .initialize_for_runtime_config(common::test_runtime_config_path()) 17 | .unwrap(); 18 | let fn_loader = context 19 | .get_delegate_loader_for_assembly(common::test_dll_path()) 20 | .unwrap(); 21 | let hello = fn_loader 22 | .get_function::( 23 | pdcstr!("Test.Program, Test"), 24 | pdcstr!("CustomHello"), 25 | pdcstr!("Test.Program+CustomHelloFunc, Test"), 26 | ) 27 | .unwrap(); 28 | 29 | hello(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/run_app.rs: -------------------------------------------------------------------------------- 1 | #![allow(deprecated)] 2 | 3 | use netcorehost::nethost; 4 | use rusty_fork::rusty_fork_test; 5 | 6 | #[path = "common.rs"] 7 | mod common; 8 | 9 | rusty_fork_test! { 10 | #[test] 11 | #[cfg(feature = "netcore3_0")] 12 | fn run_app_with_context() { 13 | common::setup(); 14 | 15 | let hostfxr = nethost::load_hostfxr().unwrap(); 16 | let context = hostfxr 17 | .initialize_for_dotnet_command_line(common::test_dll_path()) 18 | .unwrap(); 19 | let result = context.run_app().value(); 20 | assert_eq!(result, 42); 21 | } 22 | 23 | #[test] 24 | #[cfg(feature = "netcore1_0")] 25 | fn run_app_direct() { 26 | common::setup(); 27 | 28 | let hostfxr = nethost::load_hostfxr().unwrap(); 29 | let result = hostfxr.run_app(&common::test_dll_path()); 30 | result.as_hosting_exit_code().unwrap(); 31 | assert_eq!(result.value(), 42); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/unmanaged_callers_only.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "netcore3_0")] 2 | 3 | use netcorehost::{nethost, pdcstr}; 4 | use rusty_fork::rusty_fork_test; 5 | 6 | #[path = "common.rs"] 7 | mod common; 8 | 9 | rusty_fork_test! { 10 | fn unmanaged_caller_hello_world() { 11 | common::setup(); 12 | 13 | let hostfxr = nethost::load_hostfxr().unwrap(); 14 | 15 | let context = hostfxr 16 | .initialize_for_runtime_config(common::test_runtime_config_path()) 17 | .unwrap(); 18 | let fn_loader = context 19 | .get_delegate_loader_for_assembly(common::test_dll_path()) 20 | .unwrap(); 21 | let hello = fn_loader 22 | .get_function_with_unmanaged_callers_only:: i32>( 23 | pdcstr!("Test.Program, Test"), 24 | pdcstr!("UnmanagedHello"), 25 | ) 26 | .unwrap(); 27 | 28 | let result = hello(); 29 | assert_eq!(result, 42); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/pdcstring/impl/other/pdcstring.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{CStr, CString}, 3 | os::unix::prelude::OsStrExt, 4 | }; 5 | 6 | use crate::pdcstring::{ContainsNul, PdCStringInner, PdChar, PdUChar}; 7 | 8 | impl PdCStringInner for CString { 9 | fn from_str(s: impl AsRef) -> Result { 10 | Self::from_vec(s.as_ref().as_bytes().to_vec()) 11 | } 12 | 13 | fn from_os_str(s: impl AsRef) -> Result { 14 | Self::from_vec(s.as_ref().as_bytes().to_vec()) 15 | } 16 | 17 | unsafe fn from_str_ptr(ptr: *const PdChar) -> Self { 18 | unsafe { CStr::from_ptr(ptr) }.to_owned() 19 | } 20 | 21 | fn from_vec(vec: impl Into>) -> Result { 22 | CString::new(vec).map_err(|e| e.into()) 23 | } 24 | 25 | fn into_vec(self) -> Vec { 26 | CString::into_bytes(self) 27 | } 28 | 29 | fn into_vec_with_nul(self) -> Vec { 30 | CString::into_bytes_with_nul(self) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) OpenByte 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /src/error/univ.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | /// A universal error type encompassing all possible errors from the [`netcorehost`](crate) crate. 4 | #[derive(Debug, Error)] 5 | pub enum Error { 6 | /// An error from the native hosting components. 7 | #[error(transparent)] 8 | Hosting(#[from] crate::error::HostingError), 9 | /// An error while loading a function pointer to a managed method. 10 | #[error(transparent)] 11 | #[cfg(feature = "netcore3_0")] 12 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))] 13 | GetFunctionPointer(#[from] crate::hostfxr::GetManagedFunctionError), 14 | /// An error while loading the hostfxr library. 15 | #[error(transparent)] 16 | #[cfg(feature = "nethost")] 17 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "nethost")))] 18 | LoadHostfxr(#[from] crate::nethost::LoadHostfxrError), 19 | } 20 | 21 | #[cfg(feature = "nethost")] 22 | impl From for Error { 23 | fn from(err: crate::dlopen2::Error) -> Self { 24 | Self::LoadHostfxr(crate::nethost::LoadHostfxrError::DlOpen(err)) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/runtime_properties.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "netcore3_0")] 2 | 3 | use netcorehost::{nethost, pdcstr}; 4 | use rusty_fork::rusty_fork_test; 5 | 6 | #[path = "common.rs"] 7 | mod common; 8 | 9 | rusty_fork_test! { 10 | #[test] 11 | fn runtime_properties() { 12 | common::setup(); 13 | 14 | let hostfxr = nethost::load_hostfxr().unwrap(); 15 | let mut context = hostfxr 16 | .initialize_for_runtime_config(common::test_runtime_config_path()) 17 | .unwrap(); 18 | 19 | let test_property_name = pdcstr!("TEST_PROPERTY"); 20 | let test_property_value = pdcstr!("TEST_VALUE"); 21 | context 22 | .set_runtime_property_value(test_property_name, test_property_value) 23 | .unwrap(); 24 | let property_value = context 25 | .get_runtime_property_value(test_property_name) 26 | .unwrap(); 27 | assert_eq!(test_property_value, property_value); 28 | 29 | let properties = context.runtime_properties().unwrap(); 30 | let property_value = properties.get(test_property_name).copied().unwrap(); 31 | assert_eq!(test_property_value, property_value); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/pdcstr.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, process::Command}; 2 | 3 | #[test] 4 | fn try_build() { 5 | // as different macros are used depending on the os -> copy the correct contents to the .stderr fil. 6 | let family = if cfg!(windows) { "windows" } else { "other" }; 7 | fs::copy( 8 | format!( 9 | "tests/macro-build-tests/pdcstr-compile-fail.{}.stderr", 10 | family 11 | ), 12 | "tests/macro-build-tests/pdcstr-compile-fail.stderr", 13 | ) 14 | .unwrap(); 15 | 16 | let t = trybuild::TestCases::new(); 17 | t.pass("tests/macro-build-tests/pdcstr-pass.rs"); 18 | t.compile_fail("tests/macro-build-tests/pdcstr-compile-fail.rs"); 19 | } 20 | 21 | #[test] 22 | fn correct_reexports() { 23 | // check that macro dependencies are correctly exported and do not need to be manually referenced by the consuming crate. 24 | let exit_status = Command::new("cargo") 25 | .arg("build") 26 | .arg("--target") 27 | .arg(current_platform::CURRENT_PLATFORM) 28 | .current_dir("tests/macro-test-crate") 29 | .spawn() 30 | .unwrap() 31 | .wait() 32 | .unwrap(); 33 | assert!(exit_status.success()); 34 | } 35 | -------------------------------------------------------------------------------- /src/hostfxr/managed_function.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Debug, ops::Deref}; 2 | 3 | pub use fn_ptr::{FnPtr, UntypedFnPtr as RawFnPtr}; 4 | 5 | /// A wrapper around a managed function pointer. 6 | pub struct ManagedFunction(pub(crate) F); 7 | 8 | impl Debug for ManagedFunction { 9 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 10 | f.debug_struct("ManagedFunction") 11 | .field("ptr", &self.0.as_ptr()) 12 | .field("sig", &std::any::type_name::()) 13 | .finish() 14 | } 15 | } 16 | 17 | impl Deref for ManagedFunction { 18 | type Target = F; 19 | 20 | fn deref(&self) -> &Self::Target { 21 | &self.0 22 | } 23 | } 24 | 25 | /// Trait representing a managed function pointer. 26 | pub trait ManagedFnPtr: fn_ptr::FnPtr + fn_ptr::HasAbi<{ fn_ptr::abi!("system") }> {} 27 | 28 | impl> ManagedFnPtr for T {} 29 | 30 | #[macro_export] 31 | #[doc(hidden)] 32 | macro_rules! __as_managed { 33 | ($t:ty) => { 34 | ::fn_ptr::with_abi!(::fn_ptr::Abi::System, $t) 35 | }; 36 | } 37 | pub(crate) use __as_managed as as_managed; 38 | -------------------------------------------------------------------------------- /examples/run-app/ExampleProject/ExampleProject.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31521.260 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExampleProject", "ExampleProject.csproj", "{09D36E6F-D8A9-4062-9235-EE545D2E4260}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {09D36E6F-D8A9-4062-9235-EE545D2E4260}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {09D36E6F-D8A9-4062-9235-EE545D2E4260}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {09D36E6F-D8A9-4062-9235-EE545D2E4260}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {09D36E6F-D8A9-4062-9235-EE545D2E4260}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {F810D689-6FBD-4628-B3D2-35291A9A4D5E} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /tests/primary_and_secondary.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "netcore3_0")] 2 | 3 | use netcorehost::nethost; 4 | use rusty_fork::rusty_fork_test; 5 | 6 | #[path = "common.rs"] 7 | mod common; 8 | 9 | rusty_fork_test! { 10 | #[test] 11 | fn primary_is_primary() { 12 | common::setup(); 13 | 14 | let hostfxr = nethost::load_hostfxr().unwrap(); 15 | let context = hostfxr 16 | .initialize_for_runtime_config(common::test_runtime_config_path()) 17 | .unwrap(); 18 | assert!(context.is_primary()); 19 | context.close().unwrap(); 20 | } 21 | 22 | #[test] 23 | fn secondary_is_secondary() { 24 | common::setup(); 25 | 26 | let hostfxr = nethost::load_hostfxr().unwrap(); 27 | let context = hostfxr 28 | .initialize_for_dotnet_command_line(common::test_dll_path()) 29 | .unwrap(); 30 | assert!(context.is_primary()); 31 | context.run_app().as_hosting_exit_code().unwrap(); 32 | 33 | let context2 = hostfxr 34 | .initialize_for_runtime_config(common::test_runtime_config_path()) 35 | .unwrap(); 36 | assert!(!context2.is_primary()); 37 | 38 | context2.close().unwrap(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/passing-parameters/ExampleProject/ExampleProject.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31521.260 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{5ECF7343-49C0-4FC9-83F5-DA7DE70F4C26}") = "ExampleProject", "ExampleProject.csproj", "{6448301B-92D0-4004-A013-2B9E83FBFA71}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {6448301B-92D0-4004-A013-2B9E83FBFA71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {6448301B-92D0-4004-A013-2B9E83FBFA71}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {6448301B-92D0-4004-A013-2B9E83FBFA71}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {6448301B-92D0-4004-A013-2B9E83FBFA71}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {D1F4FB1E-07AF-4D2B-800A-DF2B019A330B} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /examples/run-app-with-args/ExampleProject/ExampleProject.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31521.260 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExampleProject", "ExampleProject.csproj", "{09D36E6F-D8A9-4062-9235-EE545D2E4260}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {09D36E6F-D8A9-4062-9235-EE545D2E4260}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {09D36E6F-D8A9-4062-9235-EE545D2E4260}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {09D36E6F-D8A9-4062-9235-EE545D2E4260}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {09D36E6F-D8A9-4062-9235-EE545D2E4260}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {F810D689-6FBD-4628-B3D2-35291A9A4D5E} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /examples/call-managed-function/ExampleProject/ExampleProject.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31521.260 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{20FFF50B-3B22-484C-BB76-E34082C70A8C}") = "ExampleProject", "ExampleProject.csproj", "{FF3FD3DA-8A6E-4BB1-805E-9207F740F9A0}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {FF3FD3DA-8A6E-4BB1-805E-9207F740F9A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {FF3FD3DA-8A6E-4BB1-805E-9207F740F9A0}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {FF3FD3DA-8A6E-4BB1-805E-9207F740F9A0}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {FF3FD3DA-8A6E-4BB1-805E-9207F740F9A0}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {BDCBB934-B852-4DE8-BE74-1B677720FF0A} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /examples/return-string-from-managed/ExampleProject/ExampleProject.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31521.260 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{5ECF7343-49C0-4FC9-83F5-DA7DE70F4C26}") = "ExampleProject", "ExampleProject.csproj", "{6448301B-92D0-4004-A013-2B9E83FBFA71}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {6448301B-92D0-4004-A013-2B9E83FBFA71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {6448301B-92D0-4004-A013-2B9E83FBFA71}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {6448301B-92D0-4004-A013-2B9E83FBFA71}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {6448301B-92D0-4004-A013-2B9E83FBFA71}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {D1F4FB1E-07AF-4D2B-800A-DF2B019A330B} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /tests/Test/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Runtime.CompilerServices; 4 | 5 | namespace Test { 6 | public static class Program { 7 | [ModuleInitializer] 8 | internal static void Init() { 9 | Console.WriteLine("Test DLL Loaded"); 10 | Console.WriteLine($"Running under .NET {Environment.Version}"); 11 | } 12 | 13 | public static int Hello(IntPtr arg, int argLength) { 14 | Console.WriteLine("Hello from C#!"); 15 | return 42; 16 | } 17 | public static int Hello2(IntPtr arg, int argLength) { 18 | Console.WriteLine("Hello again?"); 19 | return 0; 20 | } 21 | 22 | public delegate void CustomHelloFunc(); 23 | public static void CustomHello() { 24 | Console.WriteLine("Hello from C#!"); 25 | } 26 | 27 | [UnmanagedCallersOnly] 28 | public static int UnmanagedHello() { 29 | return Hello(default, default); 30 | } 31 | 32 | public static int Main() => Hello(default, default); 33 | 34 | class Foo { public int bar; } 35 | [UnmanagedCallersOnly] 36 | public static void Throw() { 37 | Foo foo = null!; 38 | foo.bar = 42; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.github/scripts/uninstall-dotnet-windows.ps1: -------------------------------------------------------------------------------- 1 | # Download uninstall tool 2 | $headers = @{ 3 | Authorization = "Bearer $($env:GITHUB_TOKEN)" 4 | } 5 | $releases = Invoke-RestMethod -Uri "https://api.github.com/repos/dotnet/cli-lab/releases/latest" -Headers $headers 6 | $asset = $releases.assets | Where-Object { $_.name -eq "dotnet-core-uninstall.msi" } | Select-Object -First 1 7 | $url = $asset.browser_download_url 8 | Invoke-WebRequest -Uri $url -OutFile $(Split-Path $url -Leaf) 9 | 10 | # Prepare uninstall tool 11 | $extractPath = Join-Path $pwd "dotnet-core-uninstall" # needs to be a new path 12 | msiexec.exe /A dotnet-core-uninstall.msi TARGETDIR=$extractPath /QN /L*V log.txt 13 | $uninstallToolPath = Join-Path $extractPath "dotnet-core-uninstall" "dotnet-core-uninstall.exe" 14 | # wait for the tool to be ready 15 | $maxRetries = 30 16 | $retry = 0 17 | while (-not (Test-Path $uninstallToolPath) -and ($retry -lt $maxRetries)) { 18 | Start-Sleep -Seconds 1 19 | $retry++ 20 | } 21 | if ($retry -eq $maxRetries) { 22 | Write-Error "Uninstall tool was not found after $maxRetries seconds." 23 | Get-Content -Path "log.txt" | Write-Host 24 | exit 1 25 | } 26 | 27 | # Perform uninstall 28 | & $uninstallToolPath remove --yes --force --all --runtime --verbosity detailed 29 | & $uninstallToolPath remove --yes --force --all --sdk --verbosity detailed 30 | -------------------------------------------------------------------------------- /examples/call-managed-function/main.rs: -------------------------------------------------------------------------------- 1 | use netcorehost::{nethost, pdcstr}; 2 | 3 | fn main() { 4 | let hostfxr = nethost::load_hostfxr().unwrap(); 5 | let context = hostfxr.initialize_for_runtime_config(pdcstr!("examples/call-managed-function/ExampleProject/bin/Debug/net10.0/ExampleProject.runtimeconfig.json")).unwrap(); 6 | let delegate_loader = context 7 | .get_delegate_loader_for_assembly(pdcstr!( 8 | "examples/call-managed-function/ExampleProject/bin/Debug/net10.0/ExampleProject.dll" 9 | )) 10 | .unwrap(); 11 | 12 | let hello_world1 = delegate_loader 13 | .get_function::( 14 | pdcstr!("ExampleProject.Program, ExampleProject"), 15 | pdcstr!("HelloWorld1"), 16 | pdcstr!("ExampleProject.Program+HelloWorld1Delegate, ExampleProject"), 17 | ) 18 | .unwrap(); 19 | hello_world1(); 20 | 21 | let hello_world2 = delegate_loader 22 | .get_function_with_unmanaged_callers_only::( 23 | pdcstr!("ExampleProject.Program, ExampleProject"), 24 | pdcstr!("HelloWorld2"), 25 | ) 26 | .unwrap(); 27 | hello_world2(); 28 | 29 | let hello_world3 = delegate_loader 30 | .get_function_with_default_signature( 31 | pdcstr!("ExampleProject.Program, ExampleProject"), 32 | pdcstr!("HelloWorld3"), 33 | ) 34 | .unwrap(); 35 | unsafe { hello_world3(std::ptr::null(), 0) }; 36 | } 37 | -------------------------------------------------------------------------------- /src/pdcstring/impl/traits.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | error::Error, 3 | ffi::{OsStr, OsString}, 4 | fmt::{Debug, Display}, 5 | }; 6 | 7 | use crate::pdcstring::{ContainsNul, MissingNulTerminator, PdChar, PdUChar, ToStringError}; 8 | 9 | pub(crate) trait PdCStringInner 10 | where 11 | Self: Sized, 12 | { 13 | fn from_str(s: impl AsRef) -> Result; 14 | fn from_os_str(s: impl AsRef) -> Result; 15 | unsafe fn from_str_ptr(ptr: *const PdChar) -> Self; 16 | fn from_vec(vec: impl Into>) -> Result; 17 | fn into_vec(self) -> Vec; 18 | fn into_vec_with_nul(self) -> Vec; 19 | } 20 | 21 | pub(crate) trait PdCStrInner { 22 | fn as_ptr(&self) -> *const PdChar; 23 | unsafe fn from_str_ptr<'a>(ptr: *const PdChar) -> &'a Self; 24 | unsafe fn from_slice_with_nul_unchecked(slice: &[PdUChar]) -> &Self; 25 | fn to_os_string(&self) -> OsString; 26 | fn from_slice_with_nul(slice: &[PdUChar]) -> Result<&Self, MissingNulTerminator>; 27 | fn as_slice(&self) -> &[PdUChar]; 28 | fn as_slice_with_nul(&self) -> &[PdUChar]; 29 | fn is_empty(&self) -> bool; 30 | fn len(&self) -> usize; 31 | fn to_string(&self) -> Result; 32 | fn to_string_lossy(&self) -> String; 33 | } 34 | 35 | pub(crate) trait ToStringErrorInner: Debug + Display + Error + Clone { 36 | fn index(&self) -> Option; 37 | } 38 | 39 | #[allow(dead_code)] 40 | pub(crate) trait MissingNulTerminatorInner: Debug + Display + Error + Clone {} 41 | -------------------------------------------------------------------------------- /examples/passing-parameters/ExampleProject/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace ExampleProject { 5 | public static class Program { 6 | [UnmanagedCallersOnly] 7 | public static void PrintUtf8(/* byte* */ IntPtr textPtr, int textLength) { 8 | var text = Marshal.PtrToStringUTF8(textPtr, textLength); 9 | Console.WriteLine(text); 10 | } 11 | [UnmanagedCallersOnly] 12 | public static void PrintUtf16(/* char* */ IntPtr textPtr, int textLength) { 13 | var text = Marshal.PtrToStringUni(textPtr, textLength); 14 | Console.WriteLine(text); 15 | } 16 | 17 | [UnmanagedCallersOnly] 18 | public static unsafe int IsPalindrom(char* textPtr, int textLength) { 19 | var text = new ReadOnlySpan(textPtr, textLength); // this does not copy the string like the methods on Marshal do. 20 | 21 | for (var i=0; i < text.Length / 2; i++) { 22 | if (char.ToLower(text[i]) != char.ToLower(text[text.Length - i - 1])) { 23 | return 0; 24 | } 25 | } 26 | 27 | return 1; 28 | } 29 | 30 | [UnmanagedCallersOnly] 31 | public static unsafe float GetLength(Vector2f* vector) { 32 | return (float) Math.Sqrt(vector->x*vector->x + vector->y*vector->y); 33 | } 34 | 35 | [StructLayout(LayoutKind.Sequential)] 36 | public struct Vector2f { 37 | public float x; 38 | public float y; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/hostfxr/mod.rs: -------------------------------------------------------------------------------- 1 | mod library; 2 | pub use library::*; 3 | 4 | #[cfg(feature = "netcore1_0")] 5 | mod library1_0; 6 | #[cfg(feature = "netcore1_0")] 7 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore1_0")))] 8 | #[allow(unused)] 9 | pub use library1_0::*; 10 | 11 | #[cfg(feature = "netcore2_1")] 12 | mod library2_1; 13 | #[cfg(feature = "netcore2_1")] 14 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore2_1")))] 15 | pub use library2_1::*; 16 | 17 | #[cfg(feature = "netcore3_0")] 18 | mod library3_0; 19 | #[cfg(feature = "netcore3_0")] 20 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))] 21 | #[allow(unused)] 22 | pub use library3_0::*; 23 | 24 | #[cfg(feature = "net6_0")] 25 | mod library6_0; 26 | #[cfg(feature = "net6_0")] 27 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "net6_0")))] 28 | pub use library6_0::*; 29 | 30 | #[cfg(feature = "netcore3_0")] 31 | mod context; 32 | #[cfg(feature = "netcore3_0")] 33 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))] 34 | pub use context::*; 35 | 36 | #[cfg(feature = "netcore3_0")] 37 | mod delegate_loader; 38 | #[cfg(feature = "netcore3_0")] 39 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))] 40 | pub use delegate_loader::*; 41 | 42 | #[cfg(feature = "netcore3_0")] 43 | mod runtime_property; 44 | #[cfg(feature = "netcore3_0")] 45 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))] 46 | #[allow(unused)] 47 | pub use runtime_property::*; 48 | 49 | #[cfg(feature = "netcore3_0")] 50 | mod managed_function; 51 | #[cfg(feature = "netcore3_0")] 52 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))] 53 | pub use managed_function::*; 54 | -------------------------------------------------------------------------------- /tests/error_writer.rs: -------------------------------------------------------------------------------- 1 | #![allow(deprecated)] 2 | 3 | use netcorehost::{hostfxr::Hostfxr, nethost, pdcstr}; 4 | use rusty_fork::rusty_fork_test; 5 | use std::cell::Cell; 6 | 7 | #[path = "common.rs"] 8 | mod common; 9 | 10 | fn cause_error(hostfxr: &Hostfxr) { 11 | let bad_path = pdcstr!("bad.runtimeconfig.json"); 12 | let _ = hostfxr.initialize_for_runtime_config(bad_path); 13 | } 14 | 15 | rusty_fork_test! { 16 | #[test] 17 | #[cfg(feature = "netcore3_0")] 18 | fn gets_called() { 19 | common::setup(); 20 | 21 | let hostfxr = nethost::load_hostfxr().unwrap(); 22 | let was_called = Box::leak(Box::new(Cell::new(false))); 23 | hostfxr.set_error_writer(Some(Box::new( 24 | |_| { was_called.set(true); } 25 | ))); 26 | cause_error(&hostfxr); 27 | 28 | assert!(was_called.get()); 29 | } 30 | 31 | #[test] 32 | #[cfg(feature = "netcore3_0")] 33 | fn can_be_replaced() { 34 | common::setup(); 35 | 36 | let hostfxr = nethost::load_hostfxr().unwrap(); 37 | 38 | let counter = Box::leak(Box::new(Cell::new(0))); 39 | hostfxr.set_error_writer(Some(Box::new( 40 | |_| { counter.set(counter.get() + 1); } 41 | ))); 42 | cause_error(&hostfxr); 43 | hostfxr.set_error_writer(Some(Box::new( 44 | |_| { } 45 | ))); 46 | cause_error(&hostfxr); 47 | hostfxr.set_error_writer(Some(Box::new( 48 | |_| { counter.set(counter.get() + 1); } 49 | ))); 50 | cause_error(&hostfxr); 51 | hostfxr.set_error_writer(None); 52 | cause_error(&hostfxr); 53 | 54 | assert_eq!(counter.get(), 2); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/hello_world.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "netcore3_0")] 2 | 3 | use netcorehost::{nethost, pdcstr}; 4 | use rusty_fork::rusty_fork_test; 5 | use std::ptr; 6 | 7 | #[path = "common.rs"] 8 | mod common; 9 | 10 | rusty_fork_test! { 11 | #[test] 12 | fn hello_world() { 13 | common::setup(); 14 | 15 | let hostfxr = nethost::load_hostfxr().unwrap(); 16 | 17 | let context = hostfxr 18 | .initialize_for_runtime_config(common::test_runtime_config_path()) 19 | .unwrap(); 20 | let fn_loader = context 21 | .get_delegate_loader_for_assembly(common::test_dll_path()) 22 | .unwrap(); 23 | let hello = fn_loader 24 | .get_function_with_default_signature(pdcstr!("Test.Program, Test"), pdcstr!("Hello")) 25 | .unwrap(); 26 | let result = unsafe { hello(ptr::null(), 0) }; 27 | assert_eq!(result, 42); 28 | } 29 | 30 | #[test] 31 | fn hello_world_twice() { 32 | common::setup(); 33 | 34 | let hostfxr = nethost::load_hostfxr().unwrap(); 35 | 36 | let context = hostfxr 37 | .initialize_for_runtime_config(common::test_runtime_config_path()) 38 | .unwrap(); 39 | let fn_loader = context 40 | .get_delegate_loader_for_assembly(common::test_dll_path()) 41 | .unwrap(); 42 | 43 | let hello_one = fn_loader 44 | .get_function_with_default_signature(pdcstr!("Test.Program, Test"), pdcstr!("Hello")) 45 | .unwrap(); 46 | let result = unsafe { hello_one(ptr::null(), 0) }; 47 | assert_eq!(result, 42); 48 | 49 | let hello_two = fn_loader 50 | .get_function_with_default_signature(pdcstr!("Test.Program, Test"), pdcstr!("Hello2")) 51 | .unwrap(); 52 | let result = unsafe { hello_two(ptr::null(), 0) }; 53 | assert_eq!(result, 0); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/pdcstring/impl/windows/pdcstr.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::OsString; 2 | 3 | use widestring::U16CStr; 4 | 5 | use crate::pdcstring::{MissingNulTerminator, PdCStrInner, PdChar, ToStringError}; 6 | 7 | #[doc(hidden)] 8 | pub extern crate widestring; 9 | 10 | #[macro_export] 11 | /// A macro for creating a [`PdCStr`](crate::pdcstring::PdCStr) at compile time. 12 | macro_rules! pdcstr { 13 | ($expression:expr) => { 14 | <$crate::pdcstring::PdCStr as $crate::pdcstring::windows::PdCStrExt>::from_u16_c_str( 15 | $crate::pdcstring::windows::widestring::u16cstr!($expression), 16 | ) 17 | }; 18 | } 19 | 20 | impl PdCStrInner for U16CStr { 21 | fn as_ptr(&self) -> *const PdChar { 22 | U16CStr::as_ptr(self) 23 | } 24 | unsafe fn from_str_ptr<'a>(ptr: *const PdChar) -> &'a Self { 25 | unsafe { U16CStr::from_ptr_str(ptr) } 26 | } 27 | unsafe fn from_slice_with_nul_unchecked(slice: &[PdChar]) -> &Self { 28 | unsafe { U16CStr::from_slice_unchecked(slice) } 29 | } 30 | fn to_os_string(&self) -> OsString { 31 | U16CStr::to_os_string(self) 32 | } 33 | 34 | fn from_slice_with_nul(slice: &[PdChar]) -> Result<&Self, MissingNulTerminator> { 35 | U16CStr::from_slice_truncate(slice).map_err(MissingNulTerminator) 36 | } 37 | 38 | fn as_slice(&self) -> &[PdChar] { 39 | U16CStr::as_slice(self) 40 | } 41 | 42 | fn as_slice_with_nul(&self) -> &[PdChar] { 43 | U16CStr::as_slice_with_nul(self) 44 | } 45 | 46 | fn is_empty(&self) -> bool { 47 | U16CStr::is_empty(self) 48 | } 49 | 50 | fn len(&self) -> usize { 51 | U16CStr::len(self) 52 | } 53 | 54 | fn to_string(&self) -> Result { 55 | U16CStr::to_string(self).map_err(ToStringError) 56 | } 57 | 58 | fn to_string_lossy(&self) -> String { 59 | U16CStr::to_string_lossy(self) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/pdcstring/impl/other/pdcstr.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{CStr, OsStr, OsString}, 3 | os::unix::prelude::OsStrExt, 4 | }; 5 | 6 | use crate::pdcstring::{MissingNulTerminator, PdCStrInner, PdChar, PdUChar, ToStringError}; 7 | 8 | #[doc(hidden)] 9 | pub extern crate cstr; 10 | 11 | #[macro_export] 12 | /// A macro for creating a [`PdCStr`](crate::pdcstring::PdCStr) at compile time. 13 | macro_rules! pdcstr { 14 | ($expression:expr) => { 15 | <$crate::pdcstring::PdCStr as $crate::pdcstring::other::PdCStrExt>::from_c_str( 16 | $crate::pdcstring::other::cstr::cstr!($expression), 17 | ) 18 | }; 19 | } 20 | 21 | impl PdCStrInner for CStr { 22 | fn as_ptr(&self) -> *const PdChar { 23 | CStr::as_ptr(self) 24 | } 25 | 26 | unsafe fn from_str_ptr<'a>(ptr: *const PdChar) -> &'a Self { 27 | unsafe { CStr::from_ptr(ptr) } 28 | } 29 | 30 | unsafe fn from_slice_with_nul_unchecked(slice: &[PdUChar]) -> &Self { 31 | unsafe { CStr::from_bytes_with_nul_unchecked(slice) } 32 | } 33 | 34 | fn to_os_string(&self) -> OsString { 35 | OsStr::from_bytes(CStr::to_bytes(self)).to_owned() 36 | } 37 | 38 | fn from_slice_with_nul(slice: &[PdUChar]) -> Result<&Self, MissingNulTerminator> { 39 | CStr::from_bytes_with_nul(slice).map_err(MissingNulTerminator) 40 | } 41 | 42 | fn as_slice(&self) -> &[PdUChar] { 43 | CStr::to_bytes(self) 44 | } 45 | 46 | fn as_slice_with_nul(&self) -> &[PdUChar] { 47 | CStr::to_bytes_with_nul(self) 48 | } 49 | 50 | fn is_empty(&self) -> bool { 51 | CStr::to_bytes(self).is_empty() 52 | } 53 | 54 | fn len(&self) -> usize { 55 | CStr::to_bytes(self).len() 56 | } 57 | 58 | fn to_string(&self) -> Result { 59 | CStr::to_str(self) 60 | .map(str::to_string) 61 | .map_err(ToStringError) 62 | } 63 | 64 | fn to_string_lossy(&self) -> String { 65 | CStr::to_string_lossy(self).to_string() 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/sdk_resolve.rs: -------------------------------------------------------------------------------- 1 | use netcorehost::{nethost, pdcstr, pdcstring::PdCString}; 2 | use std::{ 3 | path::{Path, PathBuf}, 4 | process::Command, 5 | }; 6 | 7 | #[path = "common.rs"] 8 | mod common; 9 | 10 | #[test] 11 | #[cfg(feature = "netcore3_0")] 12 | fn resolve_sdk() { 13 | let hostfxr = nethost::load_hostfxr().unwrap(); 14 | 15 | let actual_sdks = get_sdks(); 16 | let sdks_dir = actual_sdks 17 | .first() 18 | .unwrap() 19 | .parent() 20 | .unwrap() 21 | .parent() 22 | .unwrap(); 23 | 24 | let sdk = hostfxr 25 | .resolve_sdk( 26 | &PdCString::from_os_str(sdks_dir).unwrap(), 27 | pdcstr!("."), 28 | true, 29 | ) 30 | .unwrap(); 31 | 32 | assert!(actual_sdks.contains(&sdk.sdk_dir)); 33 | } 34 | 35 | #[test] 36 | #[cfg(feature = "netcore3_0")] 37 | fn list_sdks() { 38 | let hostfxr = nethost::load_hostfxr().unwrap(); 39 | 40 | let mut actual_sdks = get_sdks(); 41 | let sdks_dir = actual_sdks 42 | .first() 43 | .unwrap() 44 | .parent() 45 | .unwrap() 46 | .parent() 47 | .unwrap(); 48 | 49 | let mut sdks = 50 | hostfxr.get_available_sdks_with_dotnet_path(&PdCString::from_os_str(sdks_dir).unwrap()); 51 | sdks.sort(); 52 | actual_sdks.sort(); 53 | assert_eq!(actual_sdks, sdks); 54 | } 55 | 56 | #[test] 57 | #[cfg(feature = "netcore2_1")] 58 | fn get_native_search_directories() { 59 | common::setup(); 60 | 61 | let hostfxr = nethost::load_hostfxr().unwrap(); 62 | hostfxr 63 | .get_native_search_directories(&common::test_dll_path()) 64 | .unwrap(); 65 | } 66 | 67 | fn get_sdks() -> Vec { 68 | let sdks_output = Command::new("dotnet").arg("--list-sdks").output().unwrap(); 69 | assert!(sdks_output.status.success()); 70 | 71 | String::from_utf8_lossy(&sdks_output.stdout) 72 | .lines() 73 | .map(|line| { 74 | let (version, path) = line.split_once(' ').unwrap(); 75 | Path::new(&path[1..(path.len() - 1)]).join(version) 76 | }) 77 | .collect::>() 78 | } 79 | -------------------------------------------------------------------------------- /tests/load_assembly_manually.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "net8_0")] 2 | 3 | use netcorehost::{nethost, pdcstr}; 4 | use rusty_fork::rusty_fork_test; 5 | use std::fs; 6 | 7 | #[path = "common.rs"] 8 | mod common; 9 | 10 | rusty_fork_test! { 11 | #[test] 12 | fn load_from_path() { 13 | common::setup(); 14 | 15 | let hostfxr = nethost::load_hostfxr().unwrap(); 16 | 17 | let context = hostfxr 18 | .initialize_for_runtime_config(common::test_runtime_config_path()) 19 | .unwrap(); 20 | 21 | context 22 | .load_assembly_from_path(common::library_dll_path()) 23 | .unwrap(); 24 | 25 | let fn_loader = context 26 | .get_delegate_loader_for_assembly(common::library_dll_path()) 27 | .unwrap(); 28 | let hello = fn_loader 29 | .get_function_with_unmanaged_callers_only:: i32>( 30 | pdcstr!("ClassLibrary.Library, ClassLibrary"), 31 | pdcstr!("Hello"), 32 | ) 33 | .unwrap(); 34 | 35 | let result = hello(); 36 | assert_eq!(result, 42); 37 | } 38 | 39 | #[test] 40 | fn load_from_bytes() { 41 | common::setup(); 42 | 43 | let hostfxr = nethost::load_hostfxr().unwrap(); 44 | 45 | let context = hostfxr 46 | .initialize_for_runtime_config(common::test_runtime_config_path()) 47 | .unwrap(); 48 | 49 | let assembly_bytes = fs::read(common::library_dll_path().to_os_string()).unwrap(); 50 | let symbol_bytes = fs::read(common::library_symbols_path().to_os_string()).unwrap(); 51 | 52 | context 53 | .load_assembly_from_bytes(assembly_bytes, symbol_bytes) 54 | .unwrap(); 55 | 56 | let fn_loader = context 57 | .get_delegate_loader_for_assembly(common::library_dll_path()) 58 | .unwrap(); 59 | let hello = fn_loader 60 | .get_function_with_unmanaged_callers_only:: i32>( 61 | pdcstr!("ClassLibrary.Library, ClassLibrary"), 62 | pdcstr!("Hello"), 63 | ) 64 | .unwrap(); 65 | 66 | let result = hello(); 67 | assert_eq!(result, 42); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/hostfxr/library1_0.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | hostfxr::{AppOrHostingResult, Hostfxr}, 3 | pdcstring::PdCStr, 4 | }; 5 | 6 | use super::UNSUPPORTED_HOST_VERSION_ERROR_CODE; 7 | 8 | impl Hostfxr { 9 | /// Run an application. 10 | /// 11 | /// # Note 12 | /// This function does not return until the application completes execution. 13 | /// It will shutdown CoreCLR after the application executes. 14 | /// If the application is successfully executed, this value will return the exit code of the application. 15 | /// Otherwise, it will return an error code indicating the failure. 16 | #[cfg_attr( 17 | feature = "netcore3_0", 18 | deprecated(note = "Use `HostfxrContext::run_app` instead"), 19 | allow(deprecated) 20 | )] 21 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore1_0")))] 22 | #[must_use] 23 | pub fn run_app(&self, app_path: &PdCStr) -> AppOrHostingResult { 24 | self.run_app_with_args::<&PdCStr>(app_path, &[]) 25 | } 26 | 27 | /// Run an application with the specified arguments. 28 | /// 29 | /// # Note 30 | /// This function does not return until the application completes execution. 31 | /// It will shutdown CoreCLR after the application executes. 32 | /// If the application is successfully executed, this value will return the exit code of the application. 33 | /// Otherwise, it will return an error code indicating the failure. 34 | #[cfg_attr( 35 | feature = "netcore3_0", 36 | deprecated(note = "Use `HostfxrContext::run_app` instead") 37 | )] 38 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore1_0")))] 39 | pub fn run_app_with_args>( 40 | &self, 41 | app_path: &PdCStr, 42 | args: &[A], 43 | ) -> AppOrHostingResult { 44 | let args = [&self.dotnet_exe, app_path] 45 | .into_iter() 46 | .chain(args.iter().map(|s| s.as_ref())) 47 | .map(|s| s.as_ptr()) 48 | .collect::>(); 49 | 50 | let result = unsafe { 51 | self.lib 52 | .hostfxr_main(args.len().try_into().unwrap(), args.as_ptr()) 53 | } 54 | .unwrap_or(UNSUPPORTED_HOST_VERSION_ERROR_CODE); 55 | 56 | AppOrHostingResult::from(result) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "netcorehost" 3 | version = "0.19.0" 4 | description = "A Rust library for hosting the .NET Core runtime." 5 | readme = "README.md" 6 | repository = "https://github.com/OpenByteDev/netcorehost" 7 | documentation = "https://docs.rs/netcorehost" 8 | license = "MIT" 9 | authors = ["OpenByte "] 10 | edition = "2021" 11 | categories = ["api-bindings", "external-ffi-bindings"] 12 | keywords = ["nethost", "hostfxr", "dotnet", "bindings", "coreclr"] 13 | 14 | [dependencies] 15 | num_enum = { version = "0.7", default-features = false } 16 | thiserror = { version = "2.0", default-features = false } 17 | derive_more = { version = "2.0", features = ["deref", "from", "display"], default-features = false } 18 | hostfxr-sys = { version = "0.13", features = ["enum-map", "undocumented", "wrapper", "optional-apis"], default-features = false } 19 | coreclr-hosting-shared = { version = "0.1", default-features = false } 20 | destruct-drop = { version = "0.2", default-features = false } 21 | enum-map = { version = "2.7", default-features = false } 22 | once_cell = { version = "1.21", default-features = false } 23 | fn-ptr = { version = "0.4", default-features = false } 24 | nethost-sys = { version = "0.7", optional = true, default-features = false } 25 | 26 | [target.'cfg(windows)'.dependencies] 27 | widestring = { version = "1.2", features = ["std"], default-features = false } 28 | 29 | [target.'cfg(not(windows))'.dependencies] 30 | cstr = { version = "0.2", default-features = false } 31 | libc = { version = "0.2", default-features = false, optional = true } 32 | 33 | [dev-dependencies] 34 | trybuild = "1.0" 35 | current_platform = "0.2" 36 | glob = "0.3" 37 | widestring = "1.2" 38 | rusty-fork = "0.3" 39 | path-absolutize = "3.1" 40 | 41 | [target.'cfg(not(windows))'.dev-dependencies] 42 | libc = { version = "0.2", default-features = false } 43 | 44 | [features] 45 | default = ["nethost-download", "net10_0", "utils"] 46 | nethost-download = ["nethost", "nethost-sys/download-nuget"] 47 | nethost = ["nethost-sys"] 48 | nightly = [] 49 | utils = ["libc"] 50 | doc-cfg = [] 51 | netcore1_0 = ["hostfxr-sys/netcore1_0"] 52 | netcore2_0 = ["hostfxr-sys/netcore2_0", "netcore1_0"] 53 | netcore2_1 = ["hostfxr-sys/netcore2_1", "netcore2_0"] 54 | netcore3_0 = ["hostfxr-sys/netcore3_0", "netcore2_1"] 55 | net5_0 = ["hostfxr-sys/net5_0", "netcore3_0"] 56 | net6_0 = ["hostfxr-sys/net6_0", "net5_0"] 57 | net7_0 = ["hostfxr-sys/net7_0", "net6_0"] 58 | net8_0 = ["hostfxr-sys/net8_0", "net7_0"] 59 | net9_0 = ["hostfxr-sys/net9_0", "net8_0"] 60 | net10_0 = ["hostfxr-sys/net10_0", "net9_0"] 61 | latest = ["hostfxr-sys/latest", "net10_0"] 62 | 63 | # Prevent downloading nethost library when building on docs.rs. 64 | [package.metadata.docs.rs] 65 | features = ["nethost", "latest", "doc-cfg", "nightly", "utils"] 66 | no-default-features = true 67 | -------------------------------------------------------------------------------- /examples/passing-parameters/main.rs: -------------------------------------------------------------------------------- 1 | use netcorehost::{hostfxr::AssemblyDelegateLoader, nethost, pdcstr}; 2 | 3 | fn main() { 4 | let hostfxr = nethost::load_hostfxr().unwrap(); 5 | let context = hostfxr.initialize_for_runtime_config(pdcstr!("examples/passing-parameters/ExampleProject/bin/Debug/net10.0/ExampleProject.runtimeconfig.json")).unwrap(); 6 | let delegate_loader = context 7 | .get_delegate_loader_for_assembly(pdcstr!( 8 | "examples/passing-parameters/ExampleProject/bin/Debug/net10.0/ExampleProject.dll" 9 | )) 10 | .unwrap(); 11 | 12 | print_utf8_example(&delegate_loader); 13 | print_utf16_example(&delegate_loader); 14 | is_palindrom_example(&delegate_loader); 15 | get_length_example(&delegate_loader); 16 | } 17 | 18 | fn print_utf8_example(delegate_loader: &AssemblyDelegateLoader) { 19 | let print_utf8 = delegate_loader 20 | .get_function_with_unmanaged_callers_only::( 21 | pdcstr!("ExampleProject.Program, ExampleProject"), 22 | pdcstr!("PrintUtf8"), 23 | ) 24 | .unwrap(); 25 | let test_string = "Hello World!"; 26 | print_utf8(test_string.as_ptr(), test_string.len() as i32); 27 | } 28 | 29 | fn print_utf16_example(delegate_loader: &AssemblyDelegateLoader) { 30 | let print_utf16 = delegate_loader 31 | .get_function_with_unmanaged_callers_only::( 32 | pdcstr!("ExampleProject.Program, ExampleProject"), 33 | pdcstr!("PrintUtf16"), 34 | ) 35 | .unwrap(); 36 | let test_string = widestring::U16String::from_str("Hello World!"); 37 | print_utf16(test_string.as_ptr(), test_string.len() as i32); 38 | } 39 | 40 | fn is_palindrom_example(delegate_loader: &AssemblyDelegateLoader) { 41 | let is_palindrom = delegate_loader 42 | .get_function_with_unmanaged_callers_only:: i32>( 43 | pdcstr!("ExampleProject.Program, ExampleProject"), 44 | pdcstr!("IsPalindrom"), 45 | ) 46 | .unwrap(); 47 | for s in ["Racecar", "stats", "hello", "test"].iter() { 48 | let widestring = widestring::U16String::from_str(s); 49 | let palindrom_answer = if is_palindrom(widestring.as_ptr(), widestring.len() as i32) != 0 { 50 | "Yes" 51 | } else { 52 | "No" 53 | }; 54 | println!("Is '{}' a palindrom? {}!", s, palindrom_answer); 55 | } 56 | } 57 | 58 | fn get_length_example(delegate_loader: &AssemblyDelegateLoader) { 59 | let get_length = delegate_loader 60 | .get_function_with_unmanaged_callers_only:: f32>( 61 | pdcstr!("ExampleProject.Program, ExampleProject"), 62 | pdcstr!("GetLength"), 63 | ) 64 | .unwrap(); 65 | let vec = Vector2f { 66 | x: 3.0f32, 67 | y: 4.0f32, 68 | }; 69 | let length = get_length(&vec); 70 | println!("The length of {:?} is {:?}", vec, length); 71 | } 72 | 73 | #[derive(Debug)] 74 | #[repr(C)] 75 | struct Vector2f { 76 | x: f32, 77 | y: f32, 78 | } 79 | -------------------------------------------------------------------------------- /src/pdcstring/error.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | error::Error, 3 | fmt::{self, Display}, 4 | }; 5 | 6 | use super::{MissingNulTerminatorInnerImpl, PdUChar, ToStringErrorInner, ToStringErrorInnerImpl}; 7 | 8 | // same definition as ffi::NulError and widestring::error::ContainsNul 9 | /// An error returned to indicate that an invalid nul value was found in a string. 10 | #[must_use] 11 | #[derive(Clone, PartialEq, Eq, Debug)] 12 | pub struct ContainsNul(usize, Vec); 13 | 14 | impl ContainsNul { 15 | pub(crate) fn new(nul_position: usize, data: Vec) -> Self { 16 | Self(nul_position, data) 17 | } 18 | 19 | /// Returns the position of the nul byte in the slice. 20 | #[must_use] 21 | pub fn nul_position(&self) -> usize { 22 | self.0 23 | } 24 | 25 | /// Consumes this error, returning the underlying vector of bytes which 26 | /// generated the error in the first place. 27 | #[must_use] 28 | pub fn into_vec(self) -> Vec { 29 | self.1 30 | } 31 | } 32 | 33 | #[cfg(not(windows))] 34 | impl From for ContainsNul { 35 | fn from(err: std::ffi::NulError) -> Self { 36 | Self::new(err.nul_position(), err.into_vec()) 37 | } 38 | } 39 | 40 | #[cfg(windows)] 41 | impl From> for ContainsNul { 42 | fn from(err: widestring::error::ContainsNul) -> Self { 43 | Self::new(err.nul_position(), err.into_vec().unwrap()) 44 | } 45 | } 46 | 47 | impl Error for ContainsNul { 48 | fn description(&self) -> &'static str { 49 | "nul value found in data" 50 | } 51 | } 52 | 53 | impl fmt::Display for ContainsNul { 54 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 55 | write!(f, "nul byte found in provided data at position: {}", self.0) 56 | } 57 | } 58 | 59 | impl From for Vec { 60 | fn from(e: ContainsNul) -> Vec { 61 | e.into_vec() 62 | } 63 | } 64 | 65 | // common definition of str::Utf8Error and widestring::error::Utf16Error 66 | /// Errors which can occur when attempting to interpret a sequence of platform-dependent characters as a string. 67 | #[must_use] 68 | #[derive(Clone, Debug)] 69 | pub struct ToStringError(pub(crate) ToStringErrorInnerImpl); 70 | 71 | impl ToStringError { 72 | /// Returns [`Some`]`(index)` in the given string at which the invalid value occurred or 73 | /// [`None`] if the end of the input was reached unexpectedly. 74 | #[must_use] 75 | pub fn index(&self) -> Option { 76 | ToStringErrorInner::index(&self.0) 77 | } 78 | } 79 | 80 | impl Display for ToStringError { 81 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 82 | self.0.fmt(f) 83 | } 84 | } 85 | 86 | impl Error for ToStringError { 87 | fn source(&self) -> Option<&(dyn Error + 'static)> { 88 | Some(&self.0) 89 | } 90 | } 91 | 92 | /// An error returned from to indicate that a terminating nul value was missing. 93 | #[must_use] 94 | #[derive(Clone, Debug)] 95 | pub struct MissingNulTerminator(pub(crate) MissingNulTerminatorInnerImpl); 96 | 97 | impl Display for MissingNulTerminator { 98 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 99 | self.0.fmt(f) 100 | } 101 | } 102 | 103 | impl Error for MissingNulTerminator { 104 | fn source(&self) -> Option<&(dyn Error + 'static)> { 105 | Some(&self.0) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/hostfxr/library.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | dlopen2::wrapper::Container, 3 | error::{HostingError, HostingResult}, 4 | pdcstring::PdCString, 5 | }; 6 | use derive_more::From; 7 | use std::{ 8 | env::consts::EXE_SUFFIX, 9 | ffi::OsString, 10 | path::{Path, PathBuf}, 11 | sync::Arc, 12 | }; 13 | 14 | pub(crate) type HostfxrLibrary = Container; 15 | pub(crate) type SharedHostfxrLibrary = Arc; 16 | #[allow(unused, clippy::cast_possible_wrap)] 17 | pub(crate) const UNSUPPORTED_HOST_VERSION_ERROR_CODE: i32 = 18 | HostingError::HostApiUnsupportedVersion.value() as i32; 19 | 20 | /// A struct representing a loaded hostfxr library. 21 | #[derive(Clone, From)] 22 | pub struct Hostfxr { 23 | /// The underlying hostfxr library. 24 | pub lib: SharedHostfxrLibrary, 25 | pub(crate) dotnet_exe: PdCString, 26 | } 27 | 28 | fn find_dotnet_bin(hostfxr_path: impl AsRef) -> PathBuf { 29 | let mut p = hostfxr_path.as_ref().to_path_buf(); 30 | loop { 31 | if let Some(dir) = p.file_name() { 32 | if dir == "dotnet" || dir == ".dotnet" { 33 | break; 34 | } 35 | p.pop(); 36 | } else { 37 | p.clear(); 38 | break; 39 | } 40 | } 41 | p.push("dotnet"); 42 | let mut p = OsString::from(p); 43 | p.extend(Path::new(EXE_SUFFIX)); 44 | PathBuf::from(p) 45 | } 46 | 47 | impl Hostfxr { 48 | /// Loads the hostfxr library from the given path. 49 | pub fn load_from_path(path: impl AsRef) -> Result { 50 | let path = path.as_ref(); 51 | let lib = SharedHostfxrLibrary::new(unsafe { Container::load(path) }?); 52 | 53 | // Some APIs of hostfxr.dll require a path to the dotnet executable, so we try to locate it here based on the hostfxr path. 54 | let dotnet_exe = PdCString::from_os_str(find_dotnet_bin(path)).unwrap(); 55 | 56 | Ok(Self { lib, dotnet_exe }) 57 | } 58 | 59 | /// Locates the hostfxr library using [`nethost`](crate::nethost) and loads it. 60 | #[cfg(feature = "nethost")] 61 | pub fn load_with_nethost() -> Result { 62 | crate::nethost::load_hostfxr() 63 | } 64 | 65 | /// Returns the path to the dotnet root. 66 | #[must_use] 67 | pub fn get_dotnet_root(&self) -> PathBuf { 68 | self.get_dotnet_exe().parent().unwrap().to_owned() 69 | } 70 | 71 | /// Returns the path to the dotnet executable of the same installation as hostfxr. 72 | #[must_use] 73 | pub fn get_dotnet_exe(&self) -> PathBuf { 74 | self.dotnet_exe.to_os_string().into() 75 | } 76 | } 77 | 78 | /// Either the exit code of the app if it ran successful, otherwise the error from the hosting components. 79 | #[repr(transparent)] 80 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 81 | pub struct AppOrHostingResult(i32); 82 | 83 | impl AppOrHostingResult { 84 | /// Gets the raw value of the result. 85 | #[must_use] 86 | pub const fn value(&self) -> i32 { 87 | self.0 88 | } 89 | 90 | /// Converts the result to an hosting exit code. 91 | pub fn as_hosting_exit_code(self) -> HostingResult { 92 | HostingResult::from(self.0) 93 | } 94 | } 95 | 96 | impl From for i32 { 97 | fn from(code: AppOrHostingResult) -> Self { 98 | code.value() 99 | } 100 | } 101 | 102 | impl From for AppOrHostingResult { 103 | fn from(code: i32) -> Self { 104 | Self(code) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | #![cfg(unix)] 2 | 3 | /// Utilities for configuring the alternate signal stack on Unix platforms. 4 | /// 5 | /// Rust installs a small alternate signal stack for handling certain signals such as `SIGSEGV`. 6 | /// When embedding or hosting the .NET CoreCLR inside Rust, this small stack may be insufficient 7 | /// for the CLR exception-handling mechanisms. Increasing or disabling the alternate signal stack 8 | /// may be necessary to avoid a segfault in such cases. 9 | /// 10 | /// See for more details. 11 | pub mod altstack { 12 | use libc::{ 13 | mmap, sigaltstack, stack_t, MAP_ANON, MAP_FAILED, MAP_PRIVATE, PROT_READ, PROT_WRITE, 14 | SS_DISABLE, 15 | }; 16 | use std::{io, mem::MaybeUninit, ptr}; 17 | 18 | /// Represents the desired configuration of the alternate signal stack. 19 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] 20 | pub enum State { 21 | /// Disables Rust's alternate signal stack. 22 | Disabled, 23 | 24 | /// Enables and sets the alternate signal stack to a given size in bytes. 25 | Enabled { 26 | /// Target altstack size 27 | size: usize, 28 | }, 29 | } 30 | 31 | impl Default for State { 32 | fn default() -> Self { 33 | Self::Enabled { size: 8 * 1024 } 34 | } 35 | } 36 | 37 | /// Configures the alternate signal stack according to the provided status. 38 | pub fn set(state: State) -> io::Result<()> { 39 | match state { 40 | State::Disabled => { 41 | let ss = stack_t { 42 | ss_flags: SS_DISABLE, 43 | ss_sp: ptr::null_mut(), 44 | ss_size: 0, 45 | }; 46 | 47 | let result = unsafe { sigaltstack(&raw const ss, ptr::null_mut()) }; 48 | if result != 0 { 49 | return Err(io::Error::last_os_error()); 50 | } 51 | } 52 | 53 | State::Enabled { size } => { 54 | let ptr = unsafe { 55 | mmap( 56 | ptr::null_mut(), 57 | size, 58 | PROT_READ | PROT_WRITE, 59 | MAP_PRIVATE | MAP_ANON, 60 | -1, 61 | 0, 62 | ) 63 | }; 64 | 65 | if ptr == MAP_FAILED { 66 | return Err(io::Error::last_os_error()); 67 | } 68 | 69 | let ss = stack_t { 70 | ss_sp: ptr, 71 | ss_size: size, 72 | ss_flags: 0, 73 | }; 74 | 75 | let result = unsafe { sigaltstack(&raw const ss, ptr::null_mut()) }; 76 | if result != 0 { 77 | return Err(io::Error::last_os_error()); 78 | } 79 | } 80 | } 81 | 82 | Ok(()) 83 | } 84 | 85 | /// Returns the current altstack status. 86 | pub fn get() -> io::Result { 87 | let mut current = MaybeUninit::uninit(); 88 | let result = unsafe { sigaltstack(ptr::null(), current.as_mut_ptr()) }; 89 | if result != 0 { 90 | return Err(io::Error::last_os_error()); 91 | } 92 | 93 | let current = unsafe { current.assume_init() }; 94 | let enabled = current.ss_flags & SS_DISABLE == 0; 95 | let state = if enabled { 96 | State::Enabled { 97 | size: current.ss_size, 98 | } 99 | } else { 100 | State::Disabled 101 | }; 102 | 103 | Ok(state) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /examples/return-string-from-managed/ExampleProject/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | 6 | namespace ExampleProject { 7 | public static class Method1 { 8 | private static unsafe delegate* CopyToCString; 9 | 10 | [UnmanagedCallersOnly] 11 | public static unsafe void SetCopyToCStringFunctionPtr(delegate* copyToCString) => CopyToCString = copyToCString; 12 | 13 | [UnmanagedCallersOnly] 14 | public unsafe static byte* GetNameAsCString() { 15 | var name = "Some string we want to return to Rust."; 16 | fixed (char* ptr = name) { 17 | return CopyToCString(ptr, name.Length); 18 | } 19 | } 20 | } 21 | 22 | public static class Method2 { 23 | [UnmanagedCallersOnly] 24 | public static IntPtr GetNameAsUnmanagedMemory() { 25 | var name = "Some string we want to return to Rust."; 26 | return StringToHGlobalUTF8(name); 27 | } 28 | 29 | [UnmanagedCallersOnly] 30 | public static void FreeUnmanagedMemory(IntPtr ptr) { 31 | Marshal.FreeHGlobal(ptr); 32 | } 33 | 34 | private static unsafe IntPtr StringToHGlobalUTF8(string? s) { 35 | if (s is null) { 36 | return IntPtr.Zero; 37 | } 38 | 39 | int nb = Encoding.UTF8.GetMaxByteCount(s.Length); 40 | 41 | IntPtr ptr = Marshal.AllocHGlobal(nb + 1); 42 | 43 | int nbWritten; 44 | byte* pbMem = (byte*)ptr; 45 | 46 | fixed (char* firstChar = s) { 47 | nbWritten = Encoding.UTF8.GetBytes(firstChar, s.Length, pbMem, nb); 48 | } 49 | 50 | pbMem[nbWritten] = 0; 51 | 52 | return ptr; 53 | } 54 | } 55 | 56 | public static class Method3 { 57 | [UnmanagedCallersOnly] 58 | public static IntPtr GetNameAsGCHandle() { 59 | var name = "Some string we want to return to Rust."; 60 | return StringToGCHandle(name); 61 | } 62 | 63 | public static unsafe IntPtr StringToGCHandle(string s) { 64 | var handle = GCHandle.Alloc(s, GCHandleType.Pinned); 65 | return GCHandle.ToIntPtr(handle); 66 | } 67 | 68 | [UnmanagedCallersOnly] 69 | public static void FreeGCHandleString(IntPtr handle_ptr) { 70 | GCHandle.FromIntPtr(handle_ptr).Free(); 71 | } 72 | 73 | [UnmanagedCallersOnly] 74 | public static nuint GetStringDataOffset() => (nuint)RuntimeHelpers.OffsetToStringData; 75 | } 76 | 77 | public static class Method4 { 78 | private static unsafe delegate* RustAllocateMemory; 79 | 80 | [UnmanagedCallersOnly] 81 | public static unsafe void SetRustAllocateMemory(delegate* rustAllocateMemory) => RustAllocateMemory = rustAllocateMemory; 82 | 83 | [UnmanagedCallersOnly] 84 | public unsafe static void GetNameIntoRustVec(RawVec* vec) { 85 | var name = "Some string we want to return to Rust."; 86 | *vec = StringToRustVec(name); 87 | } 88 | 89 | private unsafe static RawVec StringToRustVec(string s) { 90 | var num_bytes = Encoding.UTF8.GetMaxByteCount(s.Length); 91 | 92 | var vec = new RawVec(); 93 | RustAllocateMemory((nuint)num_bytes, &vec); 94 | 95 | fixed (char* chars = s) { 96 | vec.Len = (nuint) Encoding.UTF8.GetBytes(chars, s.Length, vec.Data, (int)vec.Capacity); 97 | } 98 | 99 | return vec; 100 | } 101 | 102 | [StructLayout(LayoutKind.Sequential)] 103 | public unsafe struct RawVec { 104 | public byte* Data; 105 | public nuint Len; 106 | public nuint Capacity; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/hostfxr/runtime_property.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, mem::MaybeUninit, ptr}; 2 | 3 | use crate::{ 4 | error::{HostingError, HostingResult}, 5 | pdcstring::PdCStr, 6 | }; 7 | 8 | use super::HostfxrContext; 9 | 10 | impl HostfxrContext { 11 | /// Gets the runtime property value for the given key of this host context. 12 | pub fn get_runtime_property_value( 13 | &self, 14 | name: impl AsRef, 15 | ) -> Result<&'_ PdCStr, HostingError> { 16 | let mut value = MaybeUninit::uninit(); 17 | 18 | let result = unsafe { 19 | self.library().hostfxr_get_runtime_property_value( 20 | self.handle().as_raw(), 21 | name.as_ref().as_ptr(), 22 | value.as_mut_ptr(), 23 | ) 24 | } 25 | .unwrap(); 26 | HostingResult::from(result).into_result()?; 27 | 28 | Ok(unsafe { PdCStr::from_str_ptr(value.assume_init()) }) 29 | } 30 | 31 | /// Sets the value of a runtime property for this host context. 32 | pub fn set_runtime_property_value( 33 | &mut self, 34 | name: impl AsRef, 35 | value: impl AsRef, 36 | ) -> Result<(), HostingError> { 37 | let result = unsafe { 38 | self.library().hostfxr_set_runtime_property_value( 39 | self.handle().as_raw(), 40 | name.as_ref().as_ptr(), 41 | value.as_ref().as_ptr(), 42 | ) 43 | } 44 | .unwrap(); 45 | HostingResult::from(result).into_result().map(|_| ()) 46 | } 47 | 48 | /// Remove a runtime property for this host context. 49 | pub fn remove_runtime_property_value( 50 | &mut self, 51 | name: impl AsRef, 52 | ) -> Result<(), HostingError> { 53 | let result = unsafe { 54 | self.library().hostfxr_set_runtime_property_value( 55 | self.handle().as_raw(), 56 | name.as_ref().as_ptr(), 57 | ptr::null(), 58 | ) 59 | } 60 | .unwrap(); 61 | HostingResult::from(result).into_result().map(|_| ()) 62 | } 63 | 64 | /// Get all runtime properties for this host context. 65 | pub fn runtime_properties(&self) -> Result, HostingError> { 66 | // get count 67 | let mut count = MaybeUninit::uninit(); 68 | let mut result = unsafe { 69 | self.library().hostfxr_get_runtime_properties( 70 | self.handle().as_raw(), 71 | count.as_mut_ptr(), 72 | ptr::null_mut(), 73 | ptr::null_mut(), 74 | ) 75 | } 76 | .unwrap(); 77 | 78 | // ignore buffer too small error as the first call is only to get the required buffer size. 79 | match HostingResult::from(result).into_result() { 80 | Ok(_) | Err(HostingError::HostApiBufferTooSmall) => {} 81 | Err(e) => return Err(e), 82 | } 83 | 84 | // get values / fill buffer 85 | let mut count = unsafe { count.assume_init() }; 86 | let mut keys = Vec::with_capacity(count); 87 | let mut values = Vec::with_capacity(count); 88 | result = unsafe { 89 | self.library().hostfxr_get_runtime_properties( 90 | self.handle().as_raw(), 91 | &raw mut count, 92 | keys.as_mut_ptr(), 93 | values.as_mut_ptr(), 94 | ) 95 | } 96 | .unwrap(); 97 | HostingResult::from(result).into_result()?; 98 | 99 | unsafe { keys.set_len(count) }; 100 | unsafe { values.set_len(count) }; 101 | 102 | let keys = keys.into_iter().map(|e| unsafe { PdCStr::from_str_ptr(e) }); 103 | let values = values 104 | .into_iter() 105 | .map(|e| unsafe { PdCStr::from_str_ptr(e) }); 106 | 107 | let map = keys.zip(values).collect(); 108 | Ok(map) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /tests/environment_info.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "net6_0")] 2 | 3 | use netcorehost::{ 4 | hostfxr::{EnvironmentInfo, FrameworkInfo, SdkInfo}, 5 | nethost, 6 | }; 7 | use std::{ 8 | collections::HashMap, 9 | path::{Path, PathBuf}, 10 | process::Command, 11 | str::FromStr, 12 | }; 13 | 14 | #[path = "common.rs"] 15 | mod common; 16 | 17 | #[test] 18 | fn get_dotnet_environment_info() { 19 | use netcorehost::pdcstring::PdCString; 20 | 21 | let hostfxr = if let Some(dotnet_root) = option_env!("DOTNET_ROOT") { 22 | nethost::load_hostfxr_with_dotnet_root(PdCString::from_str(dotnet_root).unwrap()) 23 | } else { 24 | nethost::load_hostfxr() 25 | } 26 | .unwrap(); 27 | 28 | let actual_env = hostfxr.get_dotnet_environment_info().unwrap(); 29 | let expected_env = get_expected_environment_info(); 30 | 31 | assert_eq!(expected_env.hostfxr_version, actual_env.hostfxr_version); 32 | assert_eq!(expected_env.sdks, actual_env.sdks); 33 | assert_eq!(expected_env.frameworks, actual_env.frameworks); 34 | } 35 | 36 | fn get_expected_environment_info() -> EnvironmentInfo { 37 | let dotnet_path = option_env!("DOTNET_ROOT") 38 | .map(|root| Path::new(root).join("dotnet")) 39 | .unwrap_or_else(|| PathBuf::from_str("dotnet").unwrap()); 40 | let output = Command::new(dotnet_path).arg("--info").output().unwrap(); 41 | assert!(output.status.success()); 42 | let output = String::from_utf8_lossy(&output.stdout); 43 | 44 | let mut sections = Vec::new(); 45 | let mut current_section = None; 46 | for line in output.lines() { 47 | if line.is_empty() { 48 | if let Some(section) = current_section.take() { 49 | sections.push(section); 50 | } 51 | continue; 52 | } 53 | 54 | match &mut current_section { 55 | None => current_section = Some((line.trim().trim_end_matches(':'), Vec::new())), 56 | Some((_header, content)) => { 57 | content.push(line.trim()); 58 | } 59 | } 60 | } 61 | 62 | let host_section_content = sections 63 | .iter() 64 | .find(|(header, _content)| *header == "Host") 65 | .map(|(_header, content)| content) 66 | .unwrap(); 67 | let host_info = host_section_content 68 | .iter() 69 | .map(|line| { 70 | let (key, value) = line.split_once(':').unwrap(); 71 | (key.trim(), value.trim()) 72 | }) 73 | .collect::>(); 74 | let hostfxr_version = host_info["Version"].to_string(); 75 | let hostfxr_commit_hash = host_info["Commit"].to_string(); 76 | 77 | let sdk_section_content = sections 78 | .iter() 79 | .find(|(header, _content)| *header == ".NET SDKs installed") 80 | .map(|(_header, content)| content) 81 | .unwrap(); 82 | let sdks = sdk_section_content 83 | .iter() 84 | .map(|line| { 85 | let (version, enclosed_path) = line.split_once(' ').unwrap(); 86 | let path = enclosed_path.trim_start_matches('[').trim_end_matches(']'); 87 | let version = version.to_string(); 88 | let mut path = PathBuf::from(path); 89 | path.push(&version); 90 | SdkInfo { version, path } 91 | }) 92 | .collect::>(); 93 | 94 | let framework_section_content = sections 95 | .iter() 96 | .find(|(header, _content)| *header == ".NET runtimes installed") 97 | .map(|(_header, content)| content) 98 | .unwrap(); 99 | let frameworks = framework_section_content 100 | .iter() 101 | .map(|line| { 102 | let mut items = line.splitn(3, ' '); 103 | let name = items.next().unwrap(); 104 | let version = items.next().unwrap(); 105 | let enclosed_path = items.next().unwrap(); 106 | assert_eq!(items.next(), None); 107 | 108 | let name = name.to_string(); 109 | let path = PathBuf::from(enclosed_path.trim_start_matches('[').trim_end_matches(']')); 110 | let version = version.to_string(); 111 | FrameworkInfo { 112 | name, 113 | version, 114 | path, 115 | } 116 | }) 117 | .collect::>(); 118 | 119 | EnvironmentInfo { 120 | hostfxr_version, 121 | hostfxr_commit_hash, 122 | sdks, 123 | frameworks, 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /tests/macro-build-tests/pdcstr-compile-fail.windows.stderr: -------------------------------------------------------------------------------- 1 | error[E0080]: evaluation panicked: invalid NUL value found in string literal 2 | --> tests/macro-build-tests/pdcstr-compile-fail.rs:3:13 3 | | 4 | 3 | let _ = netcorehost::pdcstr!("\0"); 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `main::_WIDESTRING_U16_MACRO_UTF16` failed here 6 | | 7 | = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `netcorehost::pdcstr` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | 9 | error[E0080]: evaluation panicked: invalid NUL value found in string literal 10 | --> tests/macro-build-tests/pdcstr-compile-fail.rs:4:13 11 | | 12 | 4 | let _ = netcorehost::pdcstr!("somerandomteststring\0"); 13 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `main::_WIDESTRING_U16_MACRO_UTF16` failed here 14 | | 15 | = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `netcorehost::pdcstr` (in Nightly builds, run with -Z macro-backtrace for more info) 16 | 17 | error[E0080]: evaluation panicked: invalid NUL value found in string literal 18 | --> tests/macro-build-tests/pdcstr-compile-fail.rs:5:13 19 | | 20 | 5 | let _ = netcorehost::pdcstr!("somerandomteststring\0somerandomteststring"); 21 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `main::_WIDESTRING_U16_MACRO_UTF16` failed here 22 | | 23 | = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `netcorehost::pdcstr` (in Nightly builds, run with -Z macro-backtrace for more info) 24 | 25 | note: erroneous constant encountered 26 | --> tests/macro-build-tests/pdcstr-compile-fail.rs:5:13 27 | | 28 | 5 | let _ = netcorehost::pdcstr!("somerandomteststring\0somerandomteststring"); 29 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 30 | | 31 | = note: this note originates in the macro `$crate::pdcstring::windows::widestring::u16cstr` which comes from the expansion of the macro `netcorehost::pdcstr` (in Nightly builds, run with -Z macro-backtrace for more info) 32 | 33 | note: erroneous constant encountered 34 | --> tests/macro-build-tests/pdcstr-compile-fail.rs:4:13 35 | | 36 | 4 | let _ = netcorehost::pdcstr!("somerandomteststring\0"); 37 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 38 | | 39 | = note: this note originates in the macro `$crate::pdcstring::windows::widestring::u16cstr` which comes from the expansion of the macro `netcorehost::pdcstr` (in Nightly builds, run with -Z macro-backtrace for more info) 40 | 41 | note: erroneous constant encountered 42 | --> tests/macro-build-tests/pdcstr-compile-fail.rs:3:13 43 | | 44 | 3 | let _ = netcorehost::pdcstr!("\0"); 45 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 46 | | 47 | = note: this note originates in the macro `$crate::pdcstring::windows::widestring::u16cstr` which comes from the expansion of the macro `netcorehost::pdcstr` (in Nightly builds, run with -Z macro-backtrace for more info) 48 | 49 | note: erroneous constant encountered 50 | --> tests/macro-build-tests/pdcstr-compile-fail.rs:3:13 51 | | 52 | 3 | let _ = netcorehost::pdcstr!("\0"); 53 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^ 54 | | 55 | = note: this note originates in the macro `$crate::pdcstring::windows::widestring::u16cstr` which comes from the expansion of the macro `netcorehost::pdcstr` (in Nightly builds, run with -Z macro-backtrace for more info) 56 | 57 | note: erroneous constant encountered 58 | --> tests/macro-build-tests/pdcstr-compile-fail.rs:4:13 59 | | 60 | 4 | let _ = netcorehost::pdcstr!("somerandomteststring\0"); 61 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 62 | | 63 | = note: this note originates in the macro `$crate::pdcstring::windows::widestring::u16cstr` which comes from the expansion of the macro `netcorehost::pdcstr` (in Nightly builds, run with -Z macro-backtrace for more info) 64 | 65 | note: erroneous constant encountered 66 | --> tests/macro-build-tests/pdcstr-compile-fail.rs:5:13 67 | | 68 | 5 | let _ = netcorehost::pdcstr!("somerandomteststring\0somerandomteststring"); 69 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 70 | | 71 | = note: this note originates in the macro `$crate::pdcstring::windows::widestring::u16cstr` which comes from the expansion of the macro `netcorehost::pdcstr` (in Nightly builds, run with -Z macro-backtrace for more info) 72 | -------------------------------------------------------------------------------- /tests/common.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | use netcorehost::pdcstring::PdCString; 4 | use path_absolutize::Absolutize; 5 | use std::{ 6 | env, 7 | path::{Path, PathBuf}, 8 | process::Command, 9 | str::FromStr, 10 | }; 11 | 12 | pub fn test_netcore_version() -> String { 13 | env::var("NETCOREHOST_TEST_NETCORE_VERSION").unwrap_or_else(|_| "net10.0".to_string()) 14 | } 15 | 16 | pub fn test_project_file_path() -> PathBuf { 17 | PathBuf::from_str(&format!( 18 | "tests/Test/Test-{}.csproj", 19 | test_netcore_version() 20 | )) 21 | .unwrap() 22 | .absolutize() 23 | .unwrap() 24 | .to_path_buf() 25 | } 26 | 27 | pub fn test_runtime_config_path() -> PdCString { 28 | PdCString::from_os_str( 29 | PathBuf::from_str(&format!( 30 | "tests/Test/bin/Debug/{}/Test.runtimeconfig.json", 31 | test_netcore_version() 32 | )) 33 | .unwrap() 34 | .absolutize() 35 | .unwrap() 36 | .as_os_str(), 37 | ) 38 | .unwrap() 39 | } 40 | 41 | pub fn test_dll_path() -> PdCString { 42 | PdCString::from_os_str( 43 | PathBuf::from_str(&format!( 44 | "tests/Test/bin/Debug/{}/Test.dll", 45 | test_netcore_version() 46 | )) 47 | .unwrap() 48 | .absolutize() 49 | .unwrap() 50 | .as_os_str(), 51 | ) 52 | .unwrap() 53 | } 54 | 55 | pub fn library_project_file_path() -> PathBuf { 56 | PathBuf::from_str(&format!( 57 | "tests/ClassLibrary/ClassLibrary-{}.csproj", 58 | test_netcore_version() 59 | )) 60 | .unwrap() 61 | .absolutize() 62 | .unwrap() 63 | .to_path_buf() 64 | } 65 | 66 | pub fn library_symbols_path() -> PdCString { 67 | PdCString::from_os_str( 68 | PathBuf::from_str(&format!( 69 | "tests/ClassLibrary/bin/Debug/{}/ClassLibrary.pdb", 70 | test_netcore_version() 71 | )) 72 | .unwrap() 73 | .absolutize() 74 | .unwrap() 75 | .as_os_str(), 76 | ) 77 | .unwrap() 78 | } 79 | 80 | pub fn library_dll_path() -> PdCString { 81 | PdCString::from_os_str( 82 | PathBuf::from_str(&format!( 83 | "tests/ClassLibrary/bin/Debug/{}/ClassLibrary.dll", 84 | test_netcore_version() 85 | )) 86 | .unwrap() 87 | .absolutize() 88 | .unwrap() 89 | .as_os_str(), 90 | ) 91 | .unwrap() 92 | } 93 | 94 | pub fn display_framework_id(id: &str) -> String { 95 | let s = id.trim_start_matches('.'); 96 | 97 | if let Some(rest) = s.strip_prefix("netcoreapp") { 98 | // .netcoreappX.Y → .NET Core X.Y 99 | let version = rest.trim_start_matches('.'); 100 | return format!(".NET Core {}", version); 101 | } 102 | 103 | if let Some(rest) = s.strip_prefix("net") { 104 | // .netX.Y → .NET X.Y 105 | let version = rest.trim_start_matches('.'); 106 | return format!(".NET {}", version); 107 | } 108 | 109 | // Fallback 110 | id.to_string() 111 | } 112 | 113 | pub fn setup() { 114 | println!("Running Test Setup"); 115 | println!("Using {}", display_framework_id(&test_netcore_version())); 116 | 117 | println!("Building Test Project"); 118 | build_test_project(); 119 | println!("Building Library Project"); 120 | build_library_project(); 121 | } 122 | 123 | pub fn build_test_project() { 124 | if Path::new(&test_dll_path().to_os_string()).exists() { 125 | return; 126 | } 127 | 128 | let netcore_version = test_netcore_version(); 129 | let project_file_path = test_project_file_path(); 130 | let project_dir = project_file_path.parent().unwrap(); 131 | 132 | Command::new("dotnet") 133 | .arg("build") 134 | .arg(&project_file_path) 135 | .arg("--framework") 136 | .arg(netcore_version) 137 | .current_dir(project_dir) 138 | .spawn() 139 | .expect("dotnet build failed") 140 | .wait() 141 | .expect("dotnet build failed"); 142 | } 143 | 144 | pub fn build_library_project() { 145 | if Path::new(&library_dll_path().to_os_string()).exists() { 146 | return; 147 | } 148 | 149 | let netcore_version = test_netcore_version(); 150 | let project_file_path = library_project_file_path(); 151 | let project_dir = project_file_path.parent().unwrap(); 152 | 153 | Command::new("dotnet") 154 | .arg("build") 155 | .arg(&project_file_path) 156 | .arg("--framework") 157 | .arg(netcore_version) 158 | .current_dir(project_dir) 159 | .spawn() 160 | .expect("dotnet build failed") 161 | .wait() 162 | .expect("dotnet build failed"); 163 | } 164 | -------------------------------------------------------------------------------- /tests/errors.rs: -------------------------------------------------------------------------------- 1 | #![cfg(feature = "netcore3_0")] 2 | 3 | use netcorehost::{hostfxr::GetManagedFunctionError, nethost, pdcstr}; 4 | use rusty_fork::rusty_fork_test; 5 | 6 | #[path = "common.rs"] 7 | mod common; 8 | 9 | rusty_fork_test! { 10 | #[test] 11 | fn get_function_pointer() { 12 | common::setup(); 13 | 14 | let hostfxr = nethost::load_hostfxr().unwrap(); 15 | 16 | let context = hostfxr 17 | .initialize_for_runtime_config(common::test_runtime_config_path()) 18 | .unwrap(); 19 | let fn_loader = context 20 | .get_delegate_loader_for_assembly(common::test_dll_path()) 21 | .unwrap(); 22 | 23 | let invalid_method_name = fn_loader.get_function_with_default_signature( 24 | pdcstr!("Test.Program, Test"), 25 | pdcstr!("SomeMethodThatDoesNotExist"), 26 | ); 27 | assert!(invalid_method_name.is_err()); 28 | assert_eq!( 29 | invalid_method_name.unwrap_err(), 30 | GetManagedFunctionError::TypeOrMethodNotFound 31 | ); 32 | 33 | let invalid_method_signature = fn_loader 34 | .get_function_with_default_signature(pdcstr!("Test.Program, Test"), pdcstr!("Main")); 35 | assert!(invalid_method_signature.is_err()); 36 | assert_eq!( 37 | invalid_method_signature.unwrap_err(), 38 | GetManagedFunctionError::TypeOrMethodNotFound 39 | ); 40 | 41 | let invalid_type_name = fn_loader.get_function_with_default_signature( 42 | pdcstr!("Test.SomeTypeThatDoesNotExist, Test"), 43 | pdcstr!("Hello"), 44 | ); 45 | assert!(invalid_type_name.is_err()); 46 | assert_eq!( 47 | invalid_type_name.unwrap_err(), 48 | GetManagedFunctionError::TypeOrMethodNotFound 49 | ); 50 | 51 | let invalid_namespace_name = fn_loader.get_function_with_default_signature( 52 | pdcstr!("SomeNamespaceThatDoesNotExist.Program, Test"), 53 | pdcstr!("Hello"), 54 | ); 55 | assert!(invalid_namespace_name.is_err()); 56 | assert_eq!( 57 | invalid_namespace_name.unwrap_err(), 58 | GetManagedFunctionError::TypeOrMethodNotFound 59 | ); 60 | 61 | let invalid_assembly_name = fn_loader.get_function_with_default_signature( 62 | pdcstr!("Test.Program, SomeAssemblyThatDoesNotExist"), 63 | pdcstr!("Hello"), 64 | ); 65 | assert!(invalid_assembly_name.is_err()); 66 | assert_eq!( 67 | invalid_assembly_name.unwrap_err(), 68 | GetManagedFunctionError::AssemblyNotFound 69 | ); 70 | 71 | let method_not_marked = fn_loader.get_function_with_unmanaged_callers_only::( 72 | pdcstr!("Test.Program, Test"), 73 | pdcstr!("Hello"), 74 | ); 75 | assert!(method_not_marked.is_err()); 76 | assert_eq!( 77 | method_not_marked.unwrap_err(), 78 | GetManagedFunctionError::MethodNotUnmanagedCallersOnly 79 | ); 80 | 81 | let invalid_delegate_type_name = fn_loader.get_function::( 82 | pdcstr!("Test.Program, Test"), 83 | pdcstr!("Hello"), 84 | pdcstr!("Test.Program+SomeDelegateThatDoesNotExist, Test"), 85 | ); 86 | assert!(invalid_delegate_type_name.is_err()); 87 | assert_eq!( 88 | invalid_delegate_type_name.unwrap_err(), 89 | GetManagedFunctionError::TypeOrMethodNotFound 90 | ); 91 | 92 | context.close().unwrap(); 93 | } 94 | 95 | #[test] 96 | fn get_delegate_loader_for_assembly() { 97 | common::setup(); 98 | 99 | let hostfxr = nethost::load_hostfxr().unwrap(); 100 | 101 | let context = hostfxr 102 | .initialize_for_runtime_config(common::test_runtime_config_path()) 103 | .unwrap(); 104 | 105 | let fn_loader = context 106 | .get_delegate_loader_for_assembly(pdcstr!("tests/errors.rs")) 107 | .unwrap(); 108 | let invalid_assembly_path = fn_loader 109 | .get_function_with_default_signature(pdcstr!("Test.Program, Test"), pdcstr!("Hello")); 110 | assert!(invalid_assembly_path.is_err()); 111 | assert_eq!( 112 | invalid_assembly_path.unwrap_err(), 113 | GetManagedFunctionError::AssemblyNotFound 114 | ); 115 | 116 | let fn_loader = context 117 | .get_delegate_loader_for_assembly(pdcstr!("PathThatDoesNotExist.dll")) 118 | .unwrap(); 119 | let non_existant_assembly_path = fn_loader 120 | .get_function_with_default_signature(pdcstr!("Test.Program, Test"), pdcstr!("Hello")); 121 | assert!(non_existant_assembly_path.is_err()); 122 | assert_eq!( 123 | non_existant_assembly_path.unwrap_err(), 124 | GetManagedFunctionError::AssemblyNotFound 125 | ); 126 | 127 | context.close().unwrap(); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/nethost.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | bindings::{nethost::get_hostfxr_parameters, MAX_PATH}, 3 | error::{HostingError, HostingResult, HostingSuccess}, 4 | hostfxr::Hostfxr, 5 | pdcstring::{self, PdCStr, PdUChar}, 6 | }; 7 | use std::{ffi::OsString, mem::MaybeUninit, ptr}; 8 | use thiserror::Error; 9 | 10 | /// Gets the path to the hostfxr library. 11 | pub fn get_hostfxr_path() -> Result { 12 | unsafe { get_hostfxr_path_with_parameters(ptr::null()) } 13 | } 14 | 15 | /// Gets the path to the hostfxr library. 16 | /// Hostfxr is located as if the `assembly_path` is the apphost. 17 | pub fn get_hostfxr_path_with_assembly_path>( 18 | assembly_path: P, 19 | ) -> Result { 20 | let parameters = get_hostfxr_parameters::with_assembly_path(assembly_path.as_ref().as_ptr()); 21 | unsafe { get_hostfxr_path_with_parameters(&raw const parameters) } 22 | } 23 | 24 | /// Gets the path to the hostfxr library. 25 | /// Hostfxr is located as if an application is started using `dotnet app.dll`, which means it will be 26 | /// searched for under the `dotnet_root` path. 27 | pub fn get_hostfxr_path_with_dotnet_root>( 28 | dotnet_root: P, 29 | ) -> Result { 30 | let parameters = get_hostfxr_parameters::with_dotnet_root(dotnet_root.as_ref().as_ptr()); 31 | unsafe { get_hostfxr_path_with_parameters(&raw const parameters) } 32 | } 33 | 34 | unsafe fn get_hostfxr_path_with_parameters( 35 | parameters: *const get_hostfxr_parameters, 36 | ) -> Result { 37 | let mut path_buffer = [const { MaybeUninit::::uninit() }; MAX_PATH]; 38 | let mut path_length = path_buffer.len(); 39 | 40 | let result = unsafe { 41 | crate::bindings::nethost::get_hostfxr_path( 42 | path_buffer.as_mut_ptr().cast(), 43 | &raw mut path_length, 44 | parameters, 45 | ) 46 | }; 47 | 48 | match HostingResult::from(result).into_result() { 49 | Ok(_) => { 50 | let path_slice = 51 | unsafe { maybe_uninit_slice_assume_init_ref(&path_buffer[..path_length]) }; 52 | Ok(unsafe { PdCStr::from_slice_with_nul_unchecked(path_slice) }.to_os_string()) 53 | } 54 | Err(HostingError::HostApiBufferTooSmall) => { 55 | let mut path_vec = Vec::new(); 56 | path_vec.resize(path_length, MaybeUninit::::uninit()); 57 | 58 | let result = unsafe { 59 | crate::bindings::nethost::get_hostfxr_path( 60 | path_vec[0].as_mut_ptr().cast(), 61 | &raw mut path_length, 62 | parameters, 63 | ) 64 | }; 65 | assert_eq!(result as u32, HostingSuccess::Success.value()); 66 | 67 | let path_slice = 68 | unsafe { maybe_uninit_slice_assume_init_ref(&path_vec[..path_length]) }; 69 | Ok(unsafe { PdCStr::from_slice_with_nul_unchecked(path_slice) }.to_os_string()) 70 | } 71 | Err(err) => Err(err), 72 | } 73 | } 74 | 75 | /// Retrieves the path to the hostfxr library and loads it. 76 | pub fn load_hostfxr() -> Result { 77 | let hostfxr_path = get_hostfxr_path()?; 78 | let hostfxr = Hostfxr::load_from_path(hostfxr_path)?; 79 | Ok(hostfxr) 80 | } 81 | 82 | /// Retrieves the path to the hostfxr library and loads it. 83 | /// Hostfxr is located as if the `assembly_path` is the apphost. 84 | pub fn load_hostfxr_with_assembly_path>( 85 | assembly_path: P, 86 | ) -> Result { 87 | let hostfxr_path = get_hostfxr_path_with_assembly_path(assembly_path)?; 88 | let hostfxr = Hostfxr::load_from_path(hostfxr_path)?; 89 | Ok(hostfxr) 90 | } 91 | 92 | /// Retrieves the path to the hostfxr library and loads it. 93 | /// Hostfxr is located as if an application is started using `dotnet app.dll`, which means it will be 94 | /// searched for under the `dotnet_root` path. 95 | pub fn load_hostfxr_with_dotnet_root>( 96 | dotnet_root: P, 97 | ) -> Result { 98 | let hostfxr_path = get_hostfxr_path_with_dotnet_root(dotnet_root)?; 99 | let hostfxr = Hostfxr::load_from_path(hostfxr_path)?; 100 | Ok(hostfxr) 101 | } 102 | 103 | /// Enum for errors that can occur while locating and loading the hostfxr library. 104 | #[derive(Debug, Error)] 105 | pub enum LoadHostfxrError { 106 | /// An error occured inside the hosting components. 107 | #[error(transparent)] 108 | Hosting(#[from] HostingError), 109 | /// An error occured while loading the hostfxr library. 110 | #[error(transparent)] 111 | DlOpen(#[from] crate::dlopen2::Error), 112 | } 113 | 114 | const unsafe fn maybe_uninit_slice_assume_init_ref(slice: &[MaybeUninit]) -> &[T] { 115 | #[cfg(feature = "nightly")] 116 | unsafe { 117 | slice.assume_init_ref() 118 | } 119 | #[cfg(not(feature = "nightly"))] 120 | unsafe { 121 | &*(std::ptr::from_ref::<[MaybeUninit]>(slice) as *const [T]) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | RUSTDOCFLAGS: "--deny warnings" 12 | RUSTFLAGS: "--deny warnings" 13 | RUST_TEST_THREADS: 1 14 | # COREHOST_TRACE: 2 15 | 16 | jobs: 17 | test: 18 | runs-on: ${{ matrix.os }}-latest 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | toolchain: ["beta"] 23 | target: ["x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc", "i686-pc-windows-msvc", "aarch64-apple-darwin"] 24 | dotnet: ["8.0", "9.0", "10.0"] 25 | include: 26 | - target: x86_64-unknown-linux-gnu 27 | os: ubuntu 28 | arch: x64 29 | - target: x86_64-pc-windows-msvc 30 | os: windows 31 | arch: x64 32 | - target: i686-pc-windows-msvc 33 | os: windows 34 | arch: x86 35 | - target: aarch64-apple-darwin 36 | os: macos 37 | arch: arm64 38 | env: 39 | NETCOREHOST_TEST_NETCORE_VERSION: net${{ matrix.dotnet }} 40 | steps: 41 | - uses: actions/checkout@v6 42 | 43 | - name: Uninstall .NET SDKs 44 | run: ./.github/scripts/uninstall-dotnet-${{ matrix.os }} 45 | env: 46 | GITHUB_TOKEN: ${{ github.token }} 47 | 48 | - name: Install .NET SDK ${{ matrix.dotnet }} 49 | run: ./.github/scripts/install-dotnet-${{ matrix.os }} ${{ matrix.dotnet }} ${{ matrix.arch }} 50 | 51 | - name: Check .NET Installation 52 | run: dotnet --info 53 | 54 | - name: Install latest ${{ matrix.toolchain }} 55 | uses: dtolnay/rust-toolchain@master 56 | with: 57 | target: ${{ matrix.target }} 58 | toolchain: ${{ matrix.toolchain }} 59 | 60 | - name: Build 61 | run: cargo build --target ${{ matrix.target }} --no-default-features --features "nethost-download $("net" + "${{ matrix.dotnet }}".replace(".", "_"))" 62 | shell: pwsh 63 | 64 | - name: Test 65 | run: cargo test --target ${{ matrix.target }} --all-targets --no-fail-fast --no-default-features --features "nethost-download $("net" + "${{ matrix.dotnet }}".replace(".", "_"))" -- --nocapture 66 | shell: pwsh 67 | 68 | cross: 69 | runs-on: ubuntu-latest 70 | strategy: 71 | fail-fast: false 72 | matrix: 73 | toolchain: ["beta"] 74 | target: ["aarch64-unknown-linux-gnu", "armv7-unknown-linux-gnueabihf"] 75 | 76 | steps: 77 | - uses: actions/checkout@v6 78 | 79 | - name: Install latest ${{ matrix.toolchain }} 80 | uses: dtolnay/rust-toolchain@master 81 | with: 82 | target: ${{ matrix.target }} 83 | toolchain: ${{ matrix.toolchain }} 84 | 85 | - name: Install cross 86 | # temporary fix, see cross-rs/cross#1561 87 | run: RUSTFLAGS="" cargo install cross --git https://github.com/cross-rs/cross 88 | 89 | - name: Build 90 | run: cross build --target ${{ matrix.target }} 91 | 92 | examples: 93 | runs-on: ubuntu-latest 94 | strategy: 95 | matrix: 96 | toolchain: ["beta"] 97 | example: ["run-app", "run-app-with-args", "call-managed-function", "passing-parameters", "return-string-from-managed"] 98 | steps: 99 | - uses: actions/checkout@v6 100 | - name: Install latest ${{ matrix.toolchain }} 101 | uses: dtolnay/rust-toolchain@master 102 | with: 103 | toolchain: ${{ matrix.toolchain }} 104 | - name: Build .NET project for '${{ matrix.example }}' 105 | working-directory: ./examples/${{ matrix.example }}/ExampleProject 106 | run: dotnet build 107 | - name: Run example '${{ matrix.example }}' 108 | run: cargo run --example ${{ matrix.example }} 109 | 110 | documentation: 111 | runs-on: ${{ matrix.os }}-latest 112 | strategy: 113 | matrix: 114 | include: 115 | - os: ubuntu 116 | - os: windows 117 | steps: 118 | - uses: actions/checkout@v6 119 | - name: Install latest nightly 120 | uses: dtolnay/rust-toolchain@nightly 121 | - name: Generate documentation 122 | run: cargo doc --all-features 123 | - name: Install cargo-deadlinks 124 | run: cargo install cargo-deadlinks 125 | - name: Check dead links in doc 126 | run: cargo deadlinks 127 | 128 | clippy: 129 | runs-on: ${{ matrix.os }}-latest 130 | strategy: 131 | matrix: 132 | include: 133 | - os: ubuntu 134 | - os: windows 135 | steps: 136 | - uses: actions/checkout@v6 137 | - name: Install latest nightly 138 | uses: dtolnay/rust-toolchain@nightly 139 | with: 140 | components: clippy 141 | 142 | - name: Clippy check 143 | run: cargo clippy --all-features 144 | 145 | fmt: 146 | runs-on: ${{ matrix.os }}-latest 147 | strategy: 148 | matrix: 149 | include: 150 | - os: ubuntu 151 | - os: windows 152 | steps: 153 | - uses: actions/checkout@v6 154 | - name: Install latest nightly 155 | uses: dtolnay/rust-toolchain@nightly 156 | with: 157 | components: rustfmt 158 | 159 | - name: Format check 160 | run: cargo fmt --all -- --check 161 | -------------------------------------------------------------------------------- /tests/unhandled_managed_exeptions.rs: -------------------------------------------------------------------------------- 1 | #![cfg(all(feature = "netcore3_0", feature = "utils", unix))] 2 | // see https://github.com/OpenByteDev/netcorehost/issues/38 3 | 4 | #[path = "common.rs"] 5 | mod common; 6 | 7 | use netcorehost::{ 8 | nethost, pdcstr, 9 | utils::altstack::{self, State}, 10 | }; 11 | use rusty_fork::{fork, rusty_fork_id, ChildWrapper, ExitStatusWrapper}; 12 | use std::{io::Read, process::Stdio}; 13 | 14 | macro_rules! function_name { 15 | () => {{ 16 | fn f() {} 17 | fn type_name_of(_: T) -> &'static str { 18 | std::any::type_name::() 19 | } 20 | type_name_of(f) 21 | .rsplit("::") 22 | .find(|&part| part != "f" && part != "{{closure}}") 23 | .expect("failed to get function name") 24 | }}; 25 | } 26 | 27 | macro_rules! assert_contains { 28 | ($string:expr, $substring:expr) => {{ 29 | let string_ref: &str = &($string); 30 | let substring_ref: &str = &($substring); 31 | assert!( 32 | string_ref.contains(substring_ref), 33 | "Expected `{}` to contain `{}`", 34 | string_ref, 35 | substring_ref 36 | ); 37 | }}; 38 | } 39 | 40 | macro_rules! assert_not_contains { 41 | ($string:expr, $substring:expr) => {{ 42 | let string_ref: &str = &($string); 43 | let substring_ref: &str = &($substring); 44 | assert!( 45 | !string_ref.contains(substring_ref), 46 | "Expected `{}` NOT to contain `{}`", 47 | string_ref, 48 | substring_ref 49 | ); 50 | }}; 51 | } 52 | 53 | const MANAGED_HANDLER_OUTPUT: &str = "Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object."; 54 | 55 | #[test] 56 | fn segfault_with_small_altstack() { 57 | common::setup(); 58 | altstack_test( 59 | function_name!(), 60 | || { 61 | altstack::set(State::Enabled { size: 2 * 1024 }).unwrap(); 62 | }, 63 | |status, _, stderr| { 64 | assert_eq!(status.unix_signal(), Some(libc::SIGSEGV)); 65 | assert_not_contains!(stderr, MANAGED_HANDLER_OUTPUT); 66 | }, 67 | ); 68 | } 69 | 70 | #[test] 71 | fn no_segfault_with_large_altstack() { 72 | common::setup(); 73 | altstack_test( 74 | function_name!(), 75 | || { 76 | altstack::set(State::Enabled { size: 16 * 1024 }).unwrap(); 77 | }, 78 | |status, _, stderr| { 79 | assert_ne!(status.unix_signal(), Some(libc::SIGSEGV)); 80 | assert_contains!(stderr, MANAGED_HANDLER_OUTPUT); 81 | }, 82 | ); 83 | } 84 | 85 | #[test] 86 | fn no_segfault_with_altstack_disabled() { 87 | common::setup(); 88 | altstack_test( 89 | function_name!(), 90 | || { 91 | altstack::set(State::Disabled).unwrap(); 92 | }, 93 | |status, _, stderr| { 94 | assert_ne!(status.unix_signal(), Some(libc::SIGSEGV)); 95 | assert_contains!(stderr, MANAGED_HANDLER_OUTPUT); 96 | }, 97 | ); 98 | } 99 | 100 | fn altstack_test( 101 | test_name: &str, 102 | configure_altstack: impl FnOnce(), 103 | verify: impl FnOnce(ExitStatusWrapper, /* stdout */ String, /* stderr */ String), 104 | ) { 105 | common::setup(); 106 | 107 | // Defines the code the forked process will execute 108 | let body = || { 109 | configure_altstack(); 110 | 111 | let hostfxr = nethost::load_hostfxr().unwrap(); 112 | 113 | let context = hostfxr 114 | .initialize_for_runtime_config(common::test_runtime_config_path()) 115 | .unwrap(); 116 | let fn_loader = context 117 | .get_delegate_loader_for_assembly(common::test_dll_path()) 118 | .unwrap(); 119 | 120 | let throw_fn = fn_loader 121 | .get_function_with_unmanaged_callers_only::( 122 | pdcstr!("Test.Program, Test"), 123 | pdcstr!("Throw"), 124 | ) 125 | .unwrap(); 126 | unsafe { throw_fn() }; 127 | }; 128 | 129 | // Pipe stdout and stderr 130 | fn configure_child(child: &mut std::process::Command) { 131 | child.stdout(Stdio::piped()); 132 | child.stderr(Stdio::piped()); 133 | } 134 | 135 | // Define how to verifz the output ot the child. 136 | let supervise = |child: &mut ChildWrapper, _file: &mut std::fs::File| { 137 | let mut stdout = String::new(); 138 | child 139 | .inner_mut() 140 | .stdout 141 | .as_mut() 142 | .unwrap() 143 | .read_to_string(&mut stdout) 144 | .unwrap(); 145 | 146 | let mut stderr = String::new(); 147 | child 148 | .inner_mut() 149 | .stderr 150 | .as_mut() 151 | .unwrap() 152 | .read_to_string(&mut stderr) 153 | .unwrap(); 154 | 155 | let status = child.wait().expect("unable to wait for child"); 156 | println!("status: {status}"); 157 | println!("stdout: {stdout}"); 158 | println!("stderr: {stderr}"); 159 | verify(status, stdout, stderr); 160 | }; 161 | 162 | // Run the test in a forked child 163 | fork( 164 | test_name, 165 | rusty_fork_id!(), 166 | configure_child, 167 | supervise, 168 | body, 169 | ) 170 | .expect("fork failed"); 171 | } 172 | -------------------------------------------------------------------------------- /src/hostfxr/library6_0.rs: -------------------------------------------------------------------------------- 1 | use hostfxr_sys::hostfxr_dotnet_environment_info; 2 | 3 | use crate::{ 4 | error::{HostingError, HostingResult}, 5 | hostfxr::Hostfxr, 6 | pdcstring::{PdCStr, PdCString}, 7 | }; 8 | use std::{ffi::c_void, mem::MaybeUninit, path::PathBuf, ptr, slice}; 9 | 10 | use super::UNSUPPORTED_HOST_VERSION_ERROR_CODE; 11 | 12 | /// Information about the current dotnet environment loaded using [Hostfxr::get_dotnet_environment_info]. 13 | #[derive(Debug, Clone)] 14 | pub struct EnvironmentInfo { 15 | /// Version of hostfxr used to load this info. 16 | pub hostfxr_version: String, 17 | /// Commit hash of hostfxr used to load this info. 18 | pub hostfxr_commit_hash: String, 19 | /// Currently installed sdks, ordered by version ascending. 20 | pub sdks: Vec, 21 | /// Currently installed frameworks, ordered by name and then version ascending. 22 | pub frameworks: Vec, 23 | } 24 | 25 | impl PartialEq for EnvironmentInfo { 26 | fn eq(&self, other: &Self) -> bool { 27 | self.hostfxr_version == other.hostfxr_version 28 | && (self 29 | .hostfxr_commit_hash 30 | .starts_with(&other.hostfxr_commit_hash) 31 | || other 32 | .hostfxr_commit_hash 33 | .starts_with(&self.hostfxr_commit_hash)) 34 | && self.sdks == other.sdks 35 | && self.frameworks == other.frameworks 36 | } 37 | } 38 | 39 | impl Eq for EnvironmentInfo {} 40 | 41 | impl PartialOrd for EnvironmentInfo { 42 | fn partial_cmp(&self, other: &Self) -> Option { 43 | Some(self.hostfxr_version.cmp(&other.hostfxr_version)) 44 | } 45 | } 46 | 47 | /// A struct representing an installed sdk. 48 | #[derive(Debug, Clone, PartialEq, Eq)] 49 | pub struct SdkInfo { 50 | /// The version of the sdk. 51 | pub version: String, 52 | /// The directory containing the sdk. 53 | pub path: PathBuf, 54 | } 55 | 56 | impl PartialOrd for SdkInfo { 57 | fn partial_cmp(&self, other: &Self) -> Option { 58 | Some(self.cmp(other)) 59 | } 60 | } 61 | 62 | impl Ord for SdkInfo { 63 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 64 | self.version.cmp(&other.version) 65 | } 66 | } 67 | 68 | /// A struct representing an installed framework. 69 | #[derive(Debug, Clone, PartialEq, Eq)] 70 | pub struct FrameworkInfo { 71 | /// The name of the framework. 72 | pub name: String, 73 | /// The version of the framework. 74 | pub version: String, 75 | /// The directory containing the framework. 76 | pub path: PathBuf, 77 | } 78 | 79 | impl PartialOrd for FrameworkInfo { 80 | fn partial_cmp(&self, other: &Self) -> Option { 81 | Some(self.cmp(other)) 82 | } 83 | } 84 | 85 | impl Ord for FrameworkInfo { 86 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 87 | self.name 88 | .cmp(&other.name) 89 | .then_with(|| self.version.cmp(&other.version)) 90 | } 91 | } 92 | 93 | impl Hostfxr { 94 | /// Loads info about the dotnet environemnt, including the version of hostfxr and installed sdks and frameworks. 95 | /// 96 | /// # Ordering 97 | /// SDks are ordered by version ascending and multi-level lookup locations are put before private locations - items later in the list have priority over items earlier in the list. 98 | /// Frameworks are ordered by name ascending followed by version ascending. Multi-level lookup locations are put before private locations. 99 | /// 100 | /// # Note 101 | /// This is equivalent to the info retrieved using `dotnet --info`. 102 | /// Which means it enumerates SDKs and frameworks from the dotnet root directory (either explicitly specified or using global install location per design). 103 | /// If `DOTNET_MULTILEVEL_LOOKUP` is enabled (Windows-only), and the dotnet root is specified and it's not the global install location, 104 | /// then it will also enumerate SDKs and frameworks from the global install location. 105 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "net6_0")))] 106 | pub fn get_dotnet_environment_info(&self) -> Result { 107 | let dotnet_root = PdCString::from_os_str(self.get_dotnet_root()).ok(); 108 | let dotnet_root_ptr = dotnet_root.as_ref().map_or_else(ptr::null, |p| p.as_ptr()); 109 | let mut info = MaybeUninit::::uninit(); 110 | let result = unsafe { 111 | self.lib.hostfxr_get_dotnet_environment_info( 112 | dotnet_root_ptr, 113 | ptr::null_mut(), 114 | get_dotnet_environment_info_callback, 115 | info.as_mut_ptr().cast(), 116 | ) 117 | } 118 | .unwrap_or(UNSUPPORTED_HOST_VERSION_ERROR_CODE); 119 | HostingResult::from(result).into_result()?; 120 | let info = unsafe { MaybeUninit::assume_init(info) }; 121 | Ok(info) 122 | } 123 | } 124 | 125 | extern "C" fn get_dotnet_environment_info_callback( 126 | info: *const hostfxr_dotnet_environment_info, 127 | result_context: *mut c_void, 128 | ) { 129 | let result = result_context.cast::(); 130 | 131 | let raw_info = unsafe { &*info }; 132 | let hostfxr_version = 133 | unsafe { PdCStr::from_str_ptr(raw_info.hostfxr_version) }.to_string_lossy(); 134 | let hostfxr_commit_hash = 135 | unsafe { PdCStr::from_str_ptr(raw_info.hostfxr_commit_hash) }.to_string_lossy(); 136 | 137 | let raw_sdks = unsafe { slice::from_raw_parts(raw_info.sdks, raw_info.sdk_count) }; 138 | let sdks = raw_sdks 139 | .iter() 140 | .map(|raw_sdk| { 141 | let version = unsafe { PdCStr::from_str_ptr(raw_sdk.version) }.to_string_lossy(); 142 | let path = unsafe { PdCStr::from_str_ptr(raw_sdk.path) } 143 | .to_os_string() 144 | .into(); 145 | SdkInfo { version, path } 146 | }) 147 | .collect::>(); 148 | 149 | let raw_frameworks = 150 | unsafe { slice::from_raw_parts(raw_info.frameworks, raw_info.framework_count) }; 151 | let frameworks = raw_frameworks 152 | .iter() 153 | .map(|raw_framework| { 154 | let name = unsafe { PdCStr::from_str_ptr(raw_framework.name) }.to_string_lossy(); 155 | let version = unsafe { PdCStr::from_str_ptr(raw_framework.version) }.to_string_lossy(); 156 | let path = unsafe { PdCStr::from_str_ptr(raw_framework.path) } 157 | .to_os_string() 158 | .into(); 159 | FrameworkInfo { 160 | name, 161 | version, 162 | path, 163 | } 164 | }) 165 | .collect::>(); 166 | 167 | let info = EnvironmentInfo { 168 | hostfxr_version, 169 | hostfxr_commit_hash, 170 | sdks, 171 | frameworks, 172 | }; 173 | 174 | unsafe { result.write(info) }; 175 | } 176 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # netcorehost 2 | 3 | [![CI](https://github.com/OpenByteDev/netcorehost/actions/workflows/ci.yml/badge.svg)](https://github.com/OpenByteDev/netcorehost/actions/workflows/ci.yml) [![crates.io](https://img.shields.io/crates/v/netcorehost.svg)](https://crates.io/crates/netcorehost) [![Documentation](https://docs.rs/netcorehost/badge.svg)](https://docs.rs/netcorehost) [![dependency status](https://deps.rs/repo/github/openbytedev/netcorehost/status.svg)](https://deps.rs/repo/github/openbytedev/netcorehost) [![MIT](https://img.shields.io/crates/l/netcorehost.svg)](https://github.com/OpenByteDev/netcorehost/blob/master/LICENSE) 4 | 5 | 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/netcorehost/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/netcorehost/*/netcorehost/hostfxr/struct.AssemblyDelegateLoader.html). 23 | This is only supported for [`HostfxrContext`'s](https://docs.rs/netcorehost/*/netcorehost/hostfxr/struct.HostfxrContext.html) that are initialized using [`Hostfxr::initialize_for_runtime_config`](https://docs.rs/netcorehost/*/netcorehost/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 `True` 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/netcorehost/*/netcorehost/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::( 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/netcorehost/*/netcorehost/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::( 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/netcorehost/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/netcorehost/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 | 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/netcorehost/blob/master/LICENSE) or http://opensource.org/licenses/MIT) 152 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "nightly", feature(try_trait_v2, maybe_uninit_slice))] 2 | #![cfg_attr(feature = "doc-cfg", feature(doc_cfg))] 3 | #![warn(clippy::pedantic, clippy::cargo, unsafe_op_in_unsafe_fn, missing_docs)] 4 | #![allow( 5 | clippy::missing_safety_doc, 6 | clippy::missing_errors_doc, 7 | clippy::missing_panics_doc, 8 | clippy::module_name_repetitions, 9 | clippy::multiple_crate_versions, 10 | clippy::doc_markdown, 11 | clippy::cast_sign_loss, 12 | clippy::shadow_unrelated, 13 | clippy::redundant_closure_for_method_calls, 14 | clippy::transmute_ptr_to_ptr 15 | )] 16 | 17 | //! A Rust library for hosting the .NET Core runtime. 18 | //! 19 | //! It utilizes the .NET Core hosting API to load and execute managed code from withing the current process. 20 | //! 21 | //! # Usage 22 | //! ## Running an application 23 | //! The example below will setup the runtime, load `Test.dll` and run its `Main` method: 24 | //! ```rust 25 | //! # #[path = "../tests/common.rs"] 26 | //! # mod common; 27 | //! # common::setup(); 28 | //! # use netcorehost::{nethost, pdcstr}; 29 | //! let hostfxr = nethost::load_hostfxr().unwrap(); 30 | //! let context = hostfxr.initialize_for_dotnet_command_line(common::test_dll_path()).unwrap(); 31 | //! let result = context.run_app().value(); 32 | //! ``` 33 | //! The full example can be found in [examples/run-app](https://github.com/OpenByteDev/netcorehost/tree/master/examples/run-app). 34 | //! 35 | //! ## Calling a managed function 36 | //! A function pointer to a managed method can be aquired using an [`AssemblyDelegateLoader`](crate::hostfxr::AssemblyDelegateLoader). 37 | //! This is only supported for [`HostfxrContext`'s](crate::hostfxr::HostfxrContext) that are initialized using [`Hostfxr::initialize_for_runtime_config`](crate::hostfxr::Hostfxr::initialize_for_runtime_config). 38 | //! 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 `True` to the projects `.csproj` file. 39 | //! ### Using the default signature 40 | //! The default method signature is defined as follows: 41 | //! ```cs 42 | //! public delegate int ComponentEntryPoint(IntPtr args, int sizeBytes); 43 | //! ``` 44 | //! 45 | //! A method with the default signature (see code below) can be loaded using [`AssemblyDelegateLoader::get_function_with_default_signature`]. 46 | //! 47 | //! **C#** 48 | //! ```cs 49 | //! using System; 50 | //! 51 | //! namespace Test { 52 | //! public static class Program { 53 | //! public static int Hello(IntPtr args, int sizeBytes) { 54 | //! Console.WriteLine("Hello from C#!"); 55 | //! return 42; 56 | //! } 57 | //! } 58 | //! } 59 | //! ``` 60 | //! 61 | //! **Rust** 62 | //! ```rust 63 | //! # #[path = "../tests/common.rs"] 64 | //! # mod common; 65 | //! # common::setup(); 66 | //! # use netcorehost::{nethost, pdcstr}; 67 | //! let hostfxr = nethost::load_hostfxr().unwrap(); 68 | //! let context = 69 | //! hostfxr.initialize_for_runtime_config(common::test_runtime_config_path()).unwrap(); 70 | //! let fn_loader = 71 | //! context.get_delegate_loader_for_assembly(common::test_dll_path()).unwrap(); 72 | //! let hello = fn_loader.get_function_with_default_signature( 73 | //! pdcstr!("Test.Program, Test"), 74 | //! pdcstr!("Hello"), 75 | //! ).unwrap(); 76 | //! let result = unsafe { hello(std::ptr::null(), 0) }; 77 | //! assert_eq!(result, 42); 78 | //! ``` 79 | //! 80 | //! ### Using UnmanagedCallersOnly 81 | //! A function pointer to a method annotated with [`UnmanagedCallersOnly`] can be loaded without 82 | //! specifying its signature (as these methods cannot be overloaded). 83 | //! 84 | //! **C#** 85 | //! ```cs 86 | //! using System; 87 | //! using System.Runtime.InteropServices; 88 | //! 89 | //! namespace Test { 90 | //! public static class Program { 91 | //! [UnmanagedCallersOnly] 92 | //! public static void UnmanagedHello() { 93 | //! Console.WriteLine("Hello from C#!"); 94 | //! } 95 | //! } 96 | //! } 97 | //! ``` 98 | //! 99 | //! **Rust** 100 | //! ```rust 101 | //! # #[path = "../tests/common.rs"] 102 | //! # mod common; 103 | //! # common::setup(); 104 | //! # use netcorehost::{nethost, pdcstr}; 105 | //! let hostfxr = nethost::load_hostfxr().unwrap(); 106 | //! let context = 107 | //! hostfxr.initialize_for_runtime_config(common::test_runtime_config_path()).unwrap(); 108 | //! let fn_loader = 109 | //! context.get_delegate_loader_for_assembly(common::test_dll_path()).unwrap(); 110 | //! let hello = fn_loader.get_function_with_unmanaged_callers_only::( 111 | //! pdcstr!("Test.Program, Test"), 112 | //! pdcstr!("UnmanagedHello"), 113 | //! ).unwrap(); 114 | //! hello(); 115 | //! ``` 116 | //! 117 | //! 118 | //! ### Specifying the delegate type 119 | //! Another option is to define a custom delegate type and passing its assembly qualified name to [`AssemblyDelegateLoader::get_function`]. 120 | //! 121 | //! **C#** 122 | //! ```cs 123 | //! using System; 124 | //! 125 | //! namespace Test { 126 | //! public static class Program { 127 | //! public delegate void CustomHelloFunc(); 128 | //! 129 | //! public static void CustomHello() { 130 | //! Console.WriteLine("Hello from C#!"); 131 | //! } 132 | //! } 133 | //! } 134 | //! ``` 135 | //! 136 | //! **Rust** 137 | //! ```rust 138 | //! # #[path = "../tests/common.rs"] 139 | //! # mod common; 140 | //! # common::setup(); 141 | //! # use netcorehost::{nethost, pdcstr}; 142 | //! let hostfxr = nethost::load_hostfxr().unwrap(); 143 | //! let context = 144 | //! hostfxr.initialize_for_runtime_config(common::test_runtime_config_path()).unwrap(); 145 | //! let fn_loader = 146 | //! context.get_delegate_loader_for_assembly(common::test_dll_path()).unwrap(); 147 | //! let hello = fn_loader.get_function::( 148 | //! pdcstr!("Test.Program, Test"), 149 | //! pdcstr!("CustomHello"), 150 | //! pdcstr!("Test.Program+CustomHelloFunc, Test") 151 | //! ).unwrap(); 152 | //! hello(); 153 | //! ``` 154 | //! 155 | //! The full examples can be found in [examples/call-managed-function](https://github.com/OpenByteDev/netcorehost/tree/master/examples/call-managed-function). 156 | //! 157 | //! ## Passing complex parameters 158 | //! Examples for passing non-primitive parameters can be found in [examples/passing-parameters](https://github.com/OpenByteDev/netcorehost/tree/master/examples/passing-parameters). 159 | //! 160 | //! # Features 161 | //! - `nethost` - Links against nethost and allows for automatic detection of the hostfxr library. 162 | //! - `download-nethost` - Automatically downloads the latest nethost binary from [NuGet](https://www.nuget.org/packages/Microsoft.NETCore.DotNetHost/). 163 | //! 164 | //! [`UnmanagedCallersOnly`]: 165 | //! [`AssemblyDelegateLoader`]: crate::hostfxr::AssemblyDelegateLoader 166 | //! [`AssemblyDelegateLoader::get_function_with_default_signature`]: crate::hostfxr::AssemblyDelegateLoader::get_function_with_default_signature 167 | //! [`AssemblyDelegateLoader::get_function`]: crate::hostfxr::AssemblyDelegateLoader::get_function 168 | 169 | /// Module for the raw bindings for hostfxr and nethost. 170 | pub mod bindings; 171 | 172 | /// Module for abstractions of the hostfxr library. 173 | pub mod hostfxr; 174 | 175 | /// Module for abstractions of the nethost library. 176 | #[cfg(feature = "nethost")] 177 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "nethost")))] 178 | pub mod nethost; 179 | 180 | /// Module for a platform dependent c-like string type. 181 | #[allow(missing_docs)] 182 | pub mod pdcstring; 183 | 184 | /// Module containing error enums. 185 | pub mod error; 186 | 187 | /// Module containing additional utilities. (currently unix-only) 188 | #[cfg(feature = "utils")] 189 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "utils")))] 190 | pub mod utils; 191 | 192 | #[doc(hidden)] 193 | pub use hostfxr_sys::dlopen2; 194 | -------------------------------------------------------------------------------- /src/pdcstring/shared.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | borrow::Borrow, 3 | convert::TryFrom, 4 | ffi::{OsStr, OsString}, 5 | fmt::{self, Debug, Display, Formatter}, 6 | ops::Deref, 7 | str::FromStr, 8 | }; 9 | 10 | use super::{ 11 | ContainsNul, MissingNulTerminator, PdCStrInner, PdCStrInnerImpl, PdCStringInner, 12 | PdCStringInnerImpl, PdChar, PdUChar, ToStringError, 13 | }; 14 | 15 | /// A platform-dependent c-like string type for interacting with the .NET hosting components. 16 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Default)] 17 | #[repr(transparent)] 18 | pub struct PdCString(pub(crate) PdCStringInnerImpl); 19 | 20 | impl PdCString { 21 | #[inline] 22 | pub(crate) fn from_inner(inner: PdCStringInnerImpl) -> Self { 23 | Self(inner) 24 | } 25 | #[inline] 26 | pub(crate) fn into_inner(self) -> PdCStringInnerImpl { 27 | self.0 28 | } 29 | 30 | /// Construct a [`PdCString`] copy from an [`OsStr`], reencoding it in a platform-dependent manner. 31 | #[inline] 32 | pub fn from_os_str(s: impl AsRef) -> Result { 33 | PdCStringInner::from_os_str(s).map(Self::from_inner) 34 | } 35 | /// Constructs a new [`PdCString`] copied from a nul-terminated string pointer. 36 | #[inline] 37 | #[must_use] 38 | pub unsafe fn from_str_ptr(ptr: *const PdChar) -> Self { 39 | Self::from_inner(unsafe { PdCStringInner::from_str_ptr(ptr) }) 40 | } 41 | /// Constructs a [`PdCString`] from a container of platform-dependent character data. 42 | #[inline] 43 | pub fn from_vec(vec: impl Into>) -> Result { 44 | PdCStringInner::from_vec(vec).map(Self::from_inner) 45 | } 46 | /// Converts the string into a [`Vec`] without a nul terminator, consuming the string in the process. 47 | #[inline] 48 | #[must_use] 49 | pub fn into_vec(self) -> Vec { 50 | PdCStringInner::into_vec(self.into_inner()) 51 | } 52 | /// Converts the string into a [`Vec`], consuming the string in the process. 53 | #[inline] 54 | #[must_use] 55 | pub fn into_vec_with_nul(self) -> Vec { 56 | PdCStringInner::into_vec_with_nul(self.into_inner()) 57 | } 58 | } 59 | 60 | /// A borrowed slice of a [`PdCString`]. 61 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 62 | #[repr(transparent)] 63 | pub struct PdCStr(pub(crate) PdCStrInnerImpl); 64 | 65 | impl PdCStr { 66 | #[inline] 67 | pub(crate) fn from_inner(inner: &PdCStrInnerImpl) -> &Self { 68 | // Safety: 69 | // Safe because PdCStr has the same layout as PdCStrInnerImpl 70 | unsafe { &*(std::ptr::from_ref::(inner) as *const PdCStr) } 71 | } 72 | #[inline] 73 | pub(crate) fn as_inner(&self) -> &PdCStrInnerImpl { 74 | // Safety: 75 | // Safe because PdCStr has the same layout as PdCStrInnerImpl 76 | unsafe { &*(std::ptr::from_ref::(self) as *const PdCStrInnerImpl) } 77 | } 78 | 79 | /// Returns a raw pointer to the string. 80 | #[inline] 81 | #[must_use] 82 | pub fn as_ptr(&self) -> *const PdChar { 83 | PdCStrInner::as_ptr(self.as_inner()) 84 | } 85 | /// Constructs a [`PdCStr`] from a nul-terminated string pointer. 86 | #[inline] 87 | #[must_use] 88 | pub unsafe fn from_str_ptr<'a>(ptr: *const PdChar) -> &'a Self { 89 | Self::from_inner(unsafe { PdCStrInner::from_str_ptr(ptr) }) 90 | } 91 | /// Constructs a [`PdCStr`] from a slice of characters with a terminating nul, checking for invalid interior nul values. 92 | #[inline] 93 | pub fn from_slice_with_nul(slice: &[PdUChar]) -> Result<&Self, MissingNulTerminator> { 94 | PdCStrInner::from_slice_with_nul(slice).map(Self::from_inner) 95 | } 96 | /// Constructs a [`PdCStr`] from a slice of values without checking for a terminating or interior nul values. 97 | #[inline] 98 | #[must_use] 99 | pub unsafe fn from_slice_with_nul_unchecked(slice: &[PdUChar]) -> &Self { 100 | Self::from_inner(unsafe { PdCStrInner::from_slice_with_nul_unchecked(slice) }) 101 | } 102 | /// Copys the string to an owned [`OsString`]. 103 | #[inline] 104 | #[must_use] 105 | pub fn to_os_string(&self) -> OsString { 106 | PdCStrInner::to_os_string(self.as_inner()) 107 | } 108 | /// Converts this string to a slice of the underlying elements. 109 | /// The slice will **not** include the nul terminator. 110 | #[inline] 111 | #[must_use] 112 | pub fn as_slice(&self) -> &[PdUChar] { 113 | PdCStrInner::as_slice(self.as_inner()) 114 | } 115 | /// Converts this string to a slice of the underlying elements, including the nul terminator. 116 | #[inline] 117 | #[must_use] 118 | pub fn as_slice_with_nul(&self) -> &[PdUChar] { 119 | PdCStrInner::as_slice_with_nul(self.as_inner()) 120 | } 121 | /// Returns whether this string contains no data (i.e. is only the nul terminator). 122 | #[inline] 123 | #[must_use] 124 | pub fn is_empty(&self) -> bool { 125 | PdCStrInner::is_empty(self.as_inner()) 126 | } 127 | /// Returns the length of the string as number of elements (not number of bytes) not including the nul terminator. 128 | #[inline] 129 | #[must_use] 130 | pub fn len(&self) -> usize { 131 | PdCStrInner::len(self.as_inner()) 132 | } 133 | /// Copies the string to a [`String`] if it contains valid encoded data. 134 | #[inline] 135 | pub fn to_string(&self) -> Result { 136 | PdCStrInner::to_string(self.as_inner()) 137 | } 138 | /// Decodes the string to a [`String`] even if it contains invalid data. 139 | /// Any invalid sequences are replaced with U+FFFD REPLACEMENT CHARACTER, which looks like this: �. It will *not have a nul terminator. 140 | #[inline] 141 | #[must_use] 142 | pub fn to_string_lossy(&self) -> String { 143 | PdCStrInner::to_string_lossy(self.as_inner()) 144 | } 145 | } 146 | 147 | impl Borrow for PdCString { 148 | fn borrow(&self) -> &PdCStr { 149 | PdCStr::from_inner(self.0.borrow()) 150 | } 151 | } 152 | 153 | impl AsRef for PdCString { 154 | fn as_ref(&self) -> &PdCStr { 155 | self.borrow() 156 | } 157 | } 158 | 159 | impl Deref for PdCString { 160 | type Target = PdCStr; 161 | 162 | fn deref(&self) -> &Self::Target { 163 | self.borrow() 164 | } 165 | } 166 | 167 | impl Display for PdCStr { 168 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 169 | self.0.fmt(f) 170 | } 171 | } 172 | 173 | impl<'a> From<&'a PdCString> for &'a PdCStr { 174 | fn from(s: &'a PdCString) -> Self { 175 | s.as_ref() 176 | } 177 | } 178 | 179 | impl<'a> From<&'a PdCStr> for PdCString { 180 | fn from(s: &'a PdCStr) -> Self { 181 | s.to_owned() 182 | } 183 | } 184 | 185 | impl FromStr for PdCString { 186 | type Err = ContainsNul; 187 | 188 | fn from_str(s: &str) -> Result { 189 | PdCStringInner::from_str(s).map(Self::from_inner) 190 | } 191 | } 192 | 193 | impl<'a> TryFrom<&'a str> for PdCString { 194 | type Error = ContainsNul; 195 | 196 | fn try_from(s: &'a str) -> Result { 197 | Self::from_str(s) 198 | } 199 | } 200 | 201 | impl<'a> TryFrom<&'a OsStr> for PdCString { 202 | type Error = ContainsNul; 203 | 204 | fn try_from(s: &'a OsStr) -> Result { 205 | Self::from_os_str(s) 206 | } 207 | } 208 | 209 | impl TryFrom> for PdCString { 210 | type Error = ContainsNul; 211 | 212 | fn try_from(vec: Vec) -> Result { 213 | Self::from_vec(vec) 214 | } 215 | } 216 | 217 | impl From for Vec { 218 | fn from(s: PdCString) -> Vec { 219 | s.into_vec() 220 | } 221 | } 222 | 223 | impl AsRef for PdCStr { 224 | fn as_ref(&self) -> &Self { 225 | self 226 | } 227 | } 228 | 229 | impl ToOwned for PdCStr { 230 | type Owned = PdCString; 231 | 232 | fn to_owned(&self) -> Self::Owned { 233 | PdCString::from_inner(self.0.to_owned()) 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /examples/return-string-from-managed/main.rs: -------------------------------------------------------------------------------- 1 | #![warn(unsafe_op_in_unsafe_fn)] 2 | 3 | use core::slice; 4 | use std::{ 5 | ffi::{CStr, CString}, 6 | mem::{self, MaybeUninit}, 7 | os::raw::c_char, 8 | str::Utf8Error, 9 | string::FromUtf16Error, 10 | }; 11 | 12 | use netcorehost::{ 13 | hostfxr::{AssemblyDelegateLoader, ManagedFunction}, 14 | nethost, pdcstr, 15 | }; 16 | use std::sync::OnceLock; 17 | 18 | fn main() { 19 | let hostfxr = nethost::load_hostfxr().unwrap(); 20 | let context = hostfxr.initialize_for_runtime_config(pdcstr!("examples/return-string-from-managed/ExampleProject/bin/Debug/net10.0/ExampleProject.runtimeconfig.json")).unwrap(); 21 | let delegate_loader = context 22 | .get_delegate_loader_for_assembly(pdcstr!( 23 | "examples/return-string-from-managed/ExampleProject/bin/Debug/net10.0/ExampleProject.dll" 24 | )) 25 | .unwrap(); 26 | 27 | print_string_from_csharp_using_c_string(&delegate_loader); 28 | print_string_from_csharp_using_unmanaged_alloc(&delegate_loader); 29 | print_string_from_csharp_using_gc_handle(&delegate_loader); 30 | print_string_from_csharp_using_rust_allocate(&delegate_loader); 31 | } 32 | 33 | // Method 1: using CString 34 | fn print_string_from_csharp_using_c_string(delegate_loader: &AssemblyDelegateLoader) { 35 | let set_copy_to_c_string = delegate_loader 36 | .get_function_with_unmanaged_callers_only:: *mut c_char)>( 37 | pdcstr!("ExampleProject.Method1, ExampleProject"), 38 | pdcstr!("SetCopyToCStringFunctionPtr"), 39 | ) 40 | .unwrap(); 41 | set_copy_to_c_string(copy_to_c_string); 42 | 43 | let get_name = delegate_loader 44 | .get_function_with_unmanaged_callers_only:: *mut c_char>( 45 | pdcstr!("ExampleProject.Method1, ExampleProject"), 46 | pdcstr!("GetNameAsCString"), 47 | ) 48 | .unwrap(); 49 | let name_ptr = get_name(); 50 | let name = unsafe { CString::from_raw(name_ptr) }; 51 | println!("{}", name.to_string_lossy()); 52 | } 53 | 54 | unsafe extern "system" fn copy_to_c_string(ptr: *const u16, length: i32) -> *mut c_char { 55 | let wide_chars = unsafe { slice::from_raw_parts(ptr, length as usize) }; 56 | let string = String::from_utf16_lossy(wide_chars); 57 | let c_string = match CString::new(string) { 58 | Ok(c_string) => c_string, 59 | Err(_) => return std::ptr::null_mut(), 60 | }; 61 | c_string.into_raw() 62 | } 63 | 64 | // Method 2: using GCHandle 65 | fn print_string_from_csharp_using_unmanaged_alloc(delegate_loader: &AssemblyDelegateLoader) { 66 | // one time setup 67 | let free_h_global = delegate_loader 68 | .get_function_with_unmanaged_callers_only::( 69 | pdcstr!("ExampleProject.Method2, ExampleProject"), 70 | pdcstr!("FreeUnmanagedMemory"), 71 | ) 72 | .unwrap(); 73 | FREE_H_GLOBAL 74 | .set(free_h_global) 75 | .ok() 76 | .expect("string interop already init"); 77 | 78 | // actual usage 79 | let get_name = delegate_loader 80 | .get_function_with_unmanaged_callers_only:: *const u8>( 81 | pdcstr!("ExampleProject.Method2, ExampleProject"), 82 | pdcstr!("GetNameAsUnmanagedMemory"), 83 | ) 84 | .unwrap(); 85 | let name_h_global = get_name(); 86 | let name = unsafe { HGlobalString::from_h_global(name_h_global) }; 87 | println!("{}", name.as_str().unwrap()); 88 | } 89 | 90 | static FREE_H_GLOBAL: OnceLock> = OnceLock::new(); 91 | 92 | struct HGlobalString { 93 | ptr: *const u8, 94 | len: usize, 95 | } 96 | 97 | impl HGlobalString { 98 | pub unsafe fn from_h_global(ptr: *const u8) -> Self { 99 | let len = unsafe { CStr::from_ptr(ptr.cast()) }.to_bytes().len(); 100 | Self { ptr, len } 101 | } 102 | #[allow(dead_code)] 103 | pub fn as_bytes(&self) -> &[u8] { 104 | unsafe { slice::from_raw_parts(self.ptr, self.len) } 105 | } 106 | pub fn as_bytes_with_nul(&self) -> &[u8] { 107 | unsafe { slice::from_raw_parts(self.ptr, self.len + 1) } 108 | } 109 | pub fn as_c_str(&self) -> &CStr { 110 | unsafe { CStr::from_bytes_with_nul_unchecked(self.as_bytes_with_nul()) } 111 | } 112 | pub fn as_str(&self) -> Result<&str, Utf8Error> { 113 | self.as_c_str().to_str() 114 | } 115 | } 116 | 117 | impl Drop for HGlobalString { 118 | fn drop(&mut self) { 119 | FREE_H_GLOBAL.get().expect("string interop not init")(self.ptr); 120 | } 121 | } 122 | 123 | // Method 3: using GCHandle 124 | fn print_string_from_csharp_using_gc_handle(delegate_loader: &AssemblyDelegateLoader) { 125 | // one time setup 126 | let free_gc_handle_string = delegate_loader 127 | .get_function_with_unmanaged_callers_only::( 128 | pdcstr!("ExampleProject.Method3, ExampleProject"), 129 | pdcstr!("FreeGCHandleString"), 130 | ) 131 | .unwrap(); 132 | FREE_GC_HANDLE_STRING 133 | .set(free_gc_handle_string) 134 | .ok() 135 | .expect("string interop already init"); 136 | 137 | let get_string_data_offset = delegate_loader 138 | .get_function_with_unmanaged_callers_only:: usize>( 139 | pdcstr!("ExampleProject.Method3, ExampleProject"), 140 | pdcstr!("GetStringDataOffset"), 141 | ) 142 | .unwrap(); 143 | let string_data_offset = get_string_data_offset(); 144 | STRING_DATA_OFFSET 145 | .set(string_data_offset) 146 | .expect("string interop already init"); 147 | 148 | // actual usage 149 | let get_name = delegate_loader 150 | .get_function_with_unmanaged_callers_only:: *const *const u16>( 151 | pdcstr!("ExampleProject.Method3, ExampleProject"), 152 | pdcstr!("GetNameAsGCHandle"), 153 | ) 154 | .unwrap(); 155 | let name_gc_handle = get_name(); 156 | let name = unsafe { GcHandleString::from_gc_handle(name_gc_handle) }; 157 | println!("{}", name.to_string_lossy()); 158 | } 159 | 160 | static FREE_GC_HANDLE_STRING: OnceLock> = 161 | OnceLock::new(); 162 | static STRING_DATA_OFFSET: OnceLock = OnceLock::new(); 163 | 164 | struct GcHandleString(*const *const u16); 165 | 166 | impl GcHandleString { 167 | pub unsafe fn from_gc_handle(ptr: *const *const u16) -> Self { 168 | Self(ptr) 169 | } 170 | pub fn data_ptr(&self) -> *const u16 { 171 | // convert the handle pointer to the actual string pointer by removing the mark. 172 | let unmarked_ptr = (self.0 as usize & !1usize) as *const *const u16; 173 | let string_ptr = unsafe { *unmarked_ptr }; 174 | let string_data_offset = *STRING_DATA_OFFSET.get().expect("string interop not init"); 175 | return unsafe { string_ptr.byte_add(string_data_offset) }.cast::(); 176 | } 177 | pub fn len(&self) -> usize { 178 | // read the length of the string which is stored in front of the data. 179 | let len_ptr = unsafe { self.data_ptr().byte_sub(mem::size_of::()) }.cast::(); 180 | unsafe { *len_ptr as usize } 181 | } 182 | pub fn wide_chars(&self) -> &[u16] { 183 | unsafe { slice::from_raw_parts(self.data_ptr(), self.len()) } 184 | } 185 | #[allow(dead_code)] 186 | pub fn to_string(&self) -> Result { 187 | String::from_utf16(self.wide_chars()) 188 | } 189 | pub fn to_string_lossy(&self) -> String { 190 | String::from_utf16_lossy(self.wide_chars()) 191 | } 192 | } 193 | 194 | impl Drop for GcHandleString { 195 | fn drop(&mut self) { 196 | FREE_GC_HANDLE_STRING 197 | .get() 198 | .expect("string interop not init")(self.0); 199 | } 200 | } 201 | 202 | // Method 4: using rust allocate 203 | fn print_string_from_csharp_using_rust_allocate(delegate_loader: &AssemblyDelegateLoader) { 204 | // one time setup 205 | let set_rust_allocate_memory = delegate_loader 206 | .get_function_with_unmanaged_callers_only::))>( 207 | pdcstr!("ExampleProject.Method4, ExampleProject"), 208 | pdcstr!("SetRustAllocateMemory"), 209 | ) 210 | .unwrap(); 211 | set_rust_allocate_memory(rust_allocate_memory); 212 | 213 | // actual usage 214 | let get_name = delegate_loader 215 | .get_function_with_unmanaged_callers_only::)>( 216 | pdcstr!("ExampleProject.Method4, ExampleProject"), 217 | pdcstr!("GetNameIntoRustVec"), 218 | ) 219 | .unwrap(); 220 | 221 | let mut name_raw_vec = MaybeUninit::uninit(); 222 | get_name(name_raw_vec.as_mut_ptr()); 223 | let name_raw_vec = unsafe { name_raw_vec.assume_init() }; 224 | let name_vec = 225 | unsafe { Vec::from_raw_parts(name_raw_vec.data, name_raw_vec.len, name_raw_vec.capacity) }; 226 | let name = String::from_utf8(name_vec).unwrap(); 227 | println!("{}", name); 228 | } 229 | 230 | extern "system" fn rust_allocate_memory(size: usize, vec: *mut RawVec) { 231 | let mut buf = Vec::::with_capacity(size); 232 | unsafe { 233 | *vec = RawVec { 234 | data: buf.as_mut_ptr(), 235 | len: buf.len(), 236 | capacity: buf.capacity(), 237 | } 238 | }; 239 | mem::forget(buf); 240 | } 241 | 242 | #[repr(C)] 243 | struct RawVec { 244 | data: *mut T, 245 | len: usize, 246 | capacity: usize, 247 | } 248 | -------------------------------------------------------------------------------- /src/hostfxr/library2_1.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | bindings::hostfxr::{ 3 | hostfxr_resolve_sdk2_flags_t, hostfxr_resolve_sdk2_result_key_t, PATH_LIST_SEPARATOR, 4 | }, 5 | error::{HostingError, HostingResult}, 6 | hostfxr::{AppOrHostingResult, Hostfxr}, 7 | pdcstring::{PdCStr, PdUChar}, 8 | }; 9 | 10 | use coreclr_hosting_shared::char_t; 11 | 12 | use std::{cell::RefCell, io, mem::MaybeUninit, path::PathBuf, ptr, slice}; 13 | 14 | use super::UNSUPPORTED_HOST_VERSION_ERROR_CODE; 15 | 16 | impl Hostfxr { 17 | /// Run an application. 18 | /// 19 | /// # Arguments 20 | /// * `app_path` 21 | /// path to the application to run 22 | /// * `args` 23 | /// command-line arguments 24 | /// * `host_path` 25 | /// path to the host application 26 | /// * `dotnet_root` 27 | /// path to the .NET Core installation root 28 | /// 29 | /// This function does not return until the application completes execution. 30 | /// It will shutdown CoreCLR after the application executes. 31 | /// If the application is successfully executed, this value will return the exit code of the application. 32 | /// Otherwise, it will return an error code indicating the failure. 33 | #[cfg_attr( 34 | feature = "netcore3_0", 35 | deprecated(note = "Use `HostfxrContext::run_app` instead"), 36 | allow(deprecated) 37 | )] 38 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore2_1")))] 39 | pub fn run_app_with_args_and_startup_info<'a, A: AsRef>( 40 | &'a self, 41 | app_path: &'a PdCStr, 42 | args: impl IntoIterator, 43 | host_path: &PdCStr, 44 | dotnet_root: &PdCStr, 45 | ) -> io::Result { 46 | let args = [&self.dotnet_exe, app_path] 47 | .into_iter() 48 | .chain(args) 49 | .map(|s| s.as_ptr()) 50 | .collect::>(); 51 | 52 | let result = unsafe { 53 | self.lib.hostfxr_main_startupinfo( 54 | args.len().try_into().unwrap(), 55 | args.as_ptr(), 56 | host_path.as_ptr(), 57 | dotnet_root.as_ptr(), 58 | app_path.as_ptr(), 59 | ) 60 | } 61 | .unwrap_or(UNSUPPORTED_HOST_VERSION_ERROR_CODE); 62 | 63 | Ok(AppOrHostingResult::from(result)) 64 | } 65 | 66 | /// Determine the directory location of the SDK, accounting for `global.json` and multi-level lookup policy. 67 | /// 68 | /// # Arguments 69 | /// * `sdk_dir` - main directory where SDKs are located in `sdk\[version]` sub-folders. 70 | /// * `working_dir` - directory where the search for `global.json` will start and proceed upwards 71 | /// * `allow_prerelease` - allow resolution to return a pre-release SDK version 72 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore2_1")))] 73 | pub fn resolve_sdk( 74 | &self, 75 | sdk_dir: &PdCStr, 76 | working_dir: &PdCStr, 77 | allow_prerelease: bool, 78 | ) -> Result { 79 | let flags = if allow_prerelease { 80 | hostfxr_resolve_sdk2_flags_t::none 81 | } else { 82 | hostfxr_resolve_sdk2_flags_t::disallow_prerelease 83 | }; 84 | 85 | // Reset the output 86 | let raw_result = RawResolveSdkResult::default(); 87 | RESOLVE_SDK2_DATA.with(|sdk| *sdk.borrow_mut() = Some(raw_result)); 88 | 89 | let result = unsafe { 90 | self.lib.hostfxr_resolve_sdk2( 91 | sdk_dir.as_ptr(), 92 | working_dir.as_ptr(), 93 | flags, 94 | resolve_sdk2_callback, 95 | ) 96 | } 97 | .unwrap_or(UNSUPPORTED_HOST_VERSION_ERROR_CODE); 98 | HostingResult::from(result).into_result()?; 99 | 100 | let raw_result = RESOLVE_SDK2_DATA 101 | .with(|sdk| sdk.borrow_mut().take()) 102 | .unwrap(); 103 | Ok(ResolveSdkResult::new(raw_result)) 104 | } 105 | 106 | /// Get the list of all available SDKs ordered by ascending version. 107 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore2_1")))] 108 | #[must_use] 109 | pub fn get_available_sdks(&self) -> Vec { 110 | self.get_available_sdks_raw(None) 111 | } 112 | 113 | /// Get the list of all available SDKs ordered by ascending version, based on the provided `dotnet` executable. 114 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore2_1")))] 115 | #[must_use] 116 | pub fn get_available_sdks_with_dotnet_path(&self, dotnet_path: &PdCStr) -> Vec { 117 | self.get_available_sdks_raw(Some(dotnet_path)) 118 | } 119 | 120 | #[must_use] 121 | fn get_available_sdks_raw(&self, dotnet_path: Option<&PdCStr>) -> Vec { 122 | let dotnet_path = dotnet_path.map_or_else(ptr::null, |s| s.as_ptr()); 123 | unsafe { 124 | self.lib 125 | .hostfxr_get_available_sdks(dotnet_path, get_available_sdks_callback) 126 | }; 127 | GET_AVAILABLE_SDKS_DATA 128 | .with(|sdks| sdks.borrow_mut().take()) 129 | .unwrap() 130 | } 131 | 132 | /// Get the native search directories of the runtime based upon the specified app. 133 | /// 134 | /// # Arguments 135 | /// * `app_path` - path to application 136 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore2_1")))] 137 | pub fn get_native_search_directories( 138 | &self, 139 | app_path: &PdCStr, 140 | ) -> Result, HostingError> { 141 | let mut buffer = Vec::::new(); 142 | let args = [self.dotnet_exe.as_ptr(), app_path.as_ptr()]; 143 | 144 | let mut required_buffer_size = MaybeUninit::uninit(); 145 | unsafe { 146 | self.lib.hostfxr_get_native_search_directories( 147 | args.len().try_into().unwrap(), 148 | args.as_ptr(), 149 | buffer.as_mut_ptr().cast(), 150 | 0, 151 | required_buffer_size.as_mut_ptr(), 152 | ) 153 | }; 154 | let mut required_buffer_size = unsafe { required_buffer_size.assume_init() }; 155 | 156 | buffer.reserve(required_buffer_size.try_into().unwrap()); 157 | let result = unsafe { 158 | self.lib.hostfxr_get_native_search_directories( 159 | args.len().try_into().unwrap(), 160 | args.as_ptr(), 161 | buffer.spare_capacity_mut().as_mut_ptr().cast(), 162 | buffer.spare_capacity_mut().len().try_into().unwrap(), 163 | &raw mut required_buffer_size, 164 | ) 165 | } 166 | .unwrap_or(UNSUPPORTED_HOST_VERSION_ERROR_CODE); 167 | HostingResult::from(result).into_result()?; 168 | unsafe { buffer.set_len(required_buffer_size.try_into().unwrap()) }; 169 | 170 | let mut directories = Vec::new(); 171 | let last_start = 0; 172 | for i in 0..buffer.len() { 173 | if buffer[i] == PATH_LIST_SEPARATOR as PdUChar || buffer[i] == 0 { 174 | buffer[i] = 0; 175 | let directory = PdCStr::from_slice_with_nul(&buffer[last_start..=i]).unwrap(); 176 | directories.push(PathBuf::from(directory.to_os_string())); 177 | break; 178 | } 179 | } 180 | 181 | Ok(directories) 182 | } 183 | } 184 | 185 | thread_local! { 186 | static GET_AVAILABLE_SDKS_DATA: RefCell>> = const { RefCell::new(None) }; 187 | static RESOLVE_SDK2_DATA: RefCell> = const { RefCell::new(None) }; 188 | } 189 | 190 | extern "C" fn get_available_sdks_callback(sdk_count: i32, sdks_ptr: *const *const char_t) { 191 | GET_AVAILABLE_SDKS_DATA.with(|sdks| { 192 | let mut sdks_opt = sdks.borrow_mut(); 193 | let sdks = sdks_opt.get_or_insert_with(Vec::new); 194 | 195 | let raw_sdks = unsafe { slice::from_raw_parts(sdks_ptr, sdk_count as usize) }; 196 | sdks.extend(raw_sdks.iter().copied().map(|raw_sdk| { 197 | unsafe { PdCStr::from_str_ptr(raw_sdk) } 198 | .to_os_string() 199 | .into() 200 | })); 201 | }); 202 | } 203 | 204 | extern "C" fn resolve_sdk2_callback(key: hostfxr_resolve_sdk2_result_key_t, value: *const char_t) { 205 | RESOLVE_SDK2_DATA.with(|sdks| { 206 | let path: PathBuf = unsafe { PdCStr::from_str_ptr(value) }.to_os_string().into(); 207 | let mut guard = sdks.borrow_mut(); 208 | let raw_result = guard.as_mut().unwrap(); 209 | match key { 210 | hostfxr_resolve_sdk2_result_key_t::resolved_sdk_dir => { 211 | assert_eq!(raw_result.resolved_sdk_dir, None); 212 | raw_result.resolved_sdk_dir = Some(path); 213 | } 214 | hostfxr_resolve_sdk2_result_key_t::global_json_path => { 215 | assert_eq!(raw_result.global_json_path, None); 216 | raw_result.global_json_path = Some(path); 217 | } 218 | hostfxr_resolve_sdk2_result_key_t::requested_version => { 219 | assert_eq!(raw_result.requested_version, None); 220 | raw_result.requested_version = Some(path); 221 | } 222 | hostfxr_resolve_sdk2_result_key_t::global_json_state => { 223 | assert_eq!(raw_result.global_json_state, None); 224 | raw_result.global_json_state = Some(path); 225 | } 226 | _ => { 227 | // new key encountered 228 | } 229 | } 230 | }); 231 | } 232 | 233 | #[derive(Debug, Default)] 234 | struct RawResolveSdkResult { 235 | pub resolved_sdk_dir: Option, 236 | pub global_json_path: Option, 237 | pub requested_version: Option, 238 | pub global_json_state: Option, 239 | } 240 | 241 | /// Result of [`Hostfxr::resolve_sdk`]. 242 | #[derive(Debug, Clone, PartialEq, Eq)] 243 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore2_1")))] 244 | #[must_use] 245 | pub struct ResolveSdkResult { 246 | /// Path to the directory of the resolved sdk. 247 | pub sdk_dir: PathBuf, 248 | /// State of the global.json encountered during sdk resolution. 249 | pub global_json: GlobalJsonState, 250 | } 251 | 252 | /// State of global.json during sdk resolution with [`Hostfxr::resolve_sdk`]. 253 | #[derive(Debug, Clone, PartialEq, Eq)] 254 | pub enum GlobalJsonState { 255 | /// The global.json contained invalid data, such as a malformed version number. 256 | InvalidData, 257 | /// The global.json does not contain valid json. 258 | InvalidJson, 259 | /// No global.json was found. 260 | NotFound, 261 | /// A global.json was found and was valid. 262 | Found(GlobalJsonInfo), 263 | } 264 | 265 | impl ResolveSdkResult { 266 | fn new(raw: RawResolveSdkResult) -> Self { 267 | use hostfxr_sys::hostfxr_resolve_sdk2_global_json_state; 268 | let global_json = match raw.global_json_state { 269 | None => GlobalJsonState::NotFound, // assume not found, but could also be invalid 270 | Some(s) => match s.to_string_lossy().as_ref() { 271 | hostfxr_resolve_sdk2_global_json_state::INVALID_DATA => { 272 | GlobalJsonState::InvalidData 273 | } 274 | hostfxr_resolve_sdk2_global_json_state::INVALID_JSON => { 275 | GlobalJsonState::InvalidJson 276 | } 277 | hostfxr_resolve_sdk2_global_json_state::VALID => { 278 | GlobalJsonState::Found(GlobalJsonInfo { 279 | path: raw 280 | .global_json_path 281 | .expect("global.json found but no path provided"), 282 | requested_version: raw 283 | .requested_version 284 | .expect("global.json found but requested version not provided") 285 | .to_string_lossy() 286 | .to_string(), 287 | }) 288 | } 289 | _ => GlobalJsonState::NotFound, 290 | }, 291 | }; 292 | let sdk_dir = raw 293 | .resolved_sdk_dir 294 | .expect("resolve_sdk2 succeeded but no sdk_dir provided."); 295 | Self { 296 | sdk_dir, 297 | global_json, 298 | } 299 | } 300 | } 301 | 302 | /// Info about global.json if valid with [`Hostfxr::resolve_sdk`]. 303 | #[derive(Debug, Clone, PartialEq, Eq)] 304 | pub struct GlobalJsonInfo { 305 | path: PathBuf, 306 | requested_version: String, 307 | } 308 | -------------------------------------------------------------------------------- /src/hostfxr/context.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | bindings::hostfxr::{ 3 | hostfxr_delegate_type, hostfxr_handle, load_assembly_and_get_function_pointer_fn, 4 | }, 5 | error::{HostingError, HostingResult, HostingSuccess}, 6 | hostfxr::{ 7 | AppOrHostingResult, AssemblyDelegateLoader, DelegateLoader, Hostfxr, HostfxrLibrary, 8 | RawFnPtr, SharedHostfxrLibrary, 9 | }, 10 | pdcstring::PdCString, 11 | }; 12 | 13 | #[cfg(feature = "net5_0")] 14 | use crate::bindings::hostfxr::get_function_pointer_fn; 15 | #[cfg(feature = "net8_0")] 16 | use crate::{ 17 | bindings::hostfxr::{load_assembly_bytes_fn, load_assembly_fn}, 18 | pdcstring::PdCStr, 19 | }; 20 | 21 | use std::{ 22 | cell::Cell, 23 | ffi::c_void, 24 | fmt::{self, Debug}, 25 | marker::PhantomData, 26 | mem::{self, ManuallyDrop, MaybeUninit}, 27 | ptr::NonNull, 28 | }; 29 | 30 | #[cfg(feature = "net8_0")] 31 | use std::ptr; 32 | 33 | use destruct_drop::DestructDrop; 34 | use enum_map::EnumMap; 35 | use once_cell::unsync::OnceCell; 36 | 37 | /// A marker struct indicating that the context was initialized with a runtime config. 38 | /// This means that it is not possible to run the application associated with the context. 39 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))] 40 | #[derive(Debug, Clone, Copy)] 41 | pub struct InitializedForRuntimeConfig; 42 | 43 | /// A marker struct indicating that the context was initialized for the dotnet command line. 44 | /// This means that it is possible to run the application associated with the context. 45 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))] 46 | #[derive(Debug, Clone, Copy)] 47 | pub struct InitializedForCommandLine; 48 | 49 | /// Handle of a loaded [`HostfxrContext`]. 50 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 51 | #[repr(transparent)] 52 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))] 53 | pub struct HostfxrHandle(NonNull); 54 | 55 | impl HostfxrHandle { 56 | /// Creates a new hostfxr handle from the given raw handle. 57 | /// 58 | /// # Safety 59 | /// - The given raw handle has to be non-null. 60 | /// - The given handle has to be valid and has to represent a hostfxr context. 61 | #[must_use] 62 | pub const unsafe fn new_unchecked(ptr: hostfxr_handle) -> Self { 63 | Self(unsafe { NonNull::new_unchecked(ptr.cast_mut()) }) 64 | } 65 | 66 | /// Returns the raw underlying handle. 67 | #[must_use] 68 | pub const fn as_raw(&self) -> hostfxr_handle { 69 | self.0.as_ptr() 70 | } 71 | } 72 | 73 | impl From for hostfxr_handle { 74 | fn from(handle: HostfxrHandle) -> Self { 75 | handle.as_raw() 76 | } 77 | } 78 | 79 | /// State which hostfxr creates and maintains and represents a logical operation on the hosting components. 80 | #[derive(DestructDrop)] 81 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))] 82 | pub struct HostfxrContext { 83 | handle: HostfxrHandle, 84 | hostfxr: SharedHostfxrLibrary, 85 | is_primary: bool, 86 | runtime_delegates: EnumMap>, 87 | context_type: PhantomData, 88 | not_sync: PhantomData>, 89 | } 90 | 91 | unsafe impl Send for HostfxrContext {} 92 | 93 | impl Debug for HostfxrContext { 94 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 95 | f.debug_struct("HostfxrContext") 96 | .field("handle", &self.handle) 97 | .field("is_primary", &self.is_primary) 98 | .field("runtime_delegates", &self.runtime_delegates) 99 | .field("context_type", &self.context_type) 100 | .finish_non_exhaustive() 101 | } 102 | } 103 | 104 | impl HostfxrContext { 105 | /// Creates a new context from the given handle. 106 | /// 107 | /// # Safety 108 | /// The context handle has to be match the context type `I`. 109 | /// If the context was initialized using [`initialize_for_dotnet_command_line`] `I` has to be [`InitializedForCommandLine`]. 110 | /// If the context was initialized using [`initialize_for_runtime_config`] `I` has to be [`InitializedForRuntimeConfig`]. 111 | /// 112 | /// [`initialize_for_dotnet_command_line`]: crate::hostfxr::Hostfxr::initialize_for_dotnet_command_line 113 | /// [`initialize_for_runtime_config`]: crate::hostfxr::Hostfxr::initialize_for_runtime_config 114 | #[must_use] 115 | pub unsafe fn from_handle(handle: HostfxrHandle, hostfxr: Hostfxr, is_primary: bool) -> Self { 116 | Self { 117 | handle, 118 | hostfxr: hostfxr.lib, 119 | is_primary, 120 | runtime_delegates: EnumMap::default(), 121 | context_type: PhantomData, 122 | not_sync: PhantomData, 123 | } 124 | } 125 | 126 | /// Gets the underlying handle to the hostfxr context. 127 | #[must_use] 128 | pub const fn handle(&self) -> HostfxrHandle { 129 | self.handle 130 | } 131 | 132 | /// Gets the underlying handle to the hostfxr context and consume this context. 133 | #[must_use] 134 | pub fn into_handle(self) -> HostfxrHandle { 135 | let this = ManuallyDrop::new(self); 136 | this.handle 137 | } 138 | 139 | /// Gets whether the context is the primary hostfxr context. 140 | /// There can only be a single primary context in a process. 141 | /// 142 | /// # Note 143 | /// 144 | #[must_use] 145 | pub const fn is_primary(&self) -> bool { 146 | self.is_primary 147 | } 148 | 149 | #[must_use] 150 | pub(crate) const fn library(&self) -> &SharedHostfxrLibrary { 151 | &self.hostfxr 152 | } 153 | 154 | /// Gets a typed delegate from the currently loaded `CoreCLR` or from a newly created one. 155 | /// You propably want to use [`get_delegate_loader`] or [`get_delegate_loader_for_assembly`] 156 | /// instead of this function if you want to load function pointers. 157 | /// 158 | /// # Remarks 159 | /// If the context was initialized using [`initialize_for_runtime_config`], then all delegate types are supported. 160 | /// If it was initialized using [`initialize_for_dotnet_command_line`], then only the following 161 | /// delegate types are currently supported: 162 | /// * [`hdt_load_assembly_and_get_function_pointer`] 163 | /// * [`hdt_get_function_pointer`] 164 | /// 165 | /// [`get_delegate_loader`]: HostfxrContext::get_delegate_loader 166 | /// [`get_delegate_loader_for_assembly`]: HostfxrContext::get_delegate_loader_for_assembly 167 | /// [`hdt_load_assembly_and_get_function_pointer`]: hostfxr_delegate_type::hdt_load_assembly_and_get_function_pointer 168 | /// [`hdt_get_function_pointer`]: hostfxr_delegate_type::hdt_get_function_pointer 169 | /// [`initialize_for_runtime_config`]: Hostfxr::initialize_for_runtime_config 170 | /// [`initialize_for_dotnet_command_line`]: Hostfxr::initialize_for_dotnet_command_line 171 | pub fn get_runtime_delegate( 172 | &self, 173 | r#type: hostfxr_delegate_type, 174 | ) -> Result { 175 | self.runtime_delegates[r#type] 176 | .get_or_try_init(|| self.get_runtime_delegate_uncached(r#type)) 177 | .copied() 178 | } 179 | fn get_runtime_delegate_uncached( 180 | &self, 181 | r#type: hostfxr_delegate_type, 182 | ) -> Result { 183 | let mut delegate = MaybeUninit::uninit(); 184 | let result = unsafe { 185 | self.hostfxr.hostfxr_get_runtime_delegate( 186 | self.handle.as_raw(), 187 | r#type, 188 | delegate.as_mut_ptr(), 189 | ) 190 | } 191 | .unwrap(); 192 | 193 | HostingResult::from(result).into_result()?; 194 | 195 | Ok(unsafe { delegate.assume_init() }.cast()) 196 | } 197 | fn get_load_assembly_and_get_function_pointer_delegate( 198 | &self, 199 | ) -> Result { 200 | unsafe { 201 | self.get_runtime_delegate( 202 | hostfxr_delegate_type::hdt_load_assembly_and_get_function_pointer, 203 | ) 204 | .map(|ptr| mem::transmute(ptr)) 205 | } 206 | } 207 | #[cfg(feature = "net5_0")] 208 | fn get_get_function_pointer_delegate(&self) -> Result { 209 | unsafe { 210 | self.get_runtime_delegate(hostfxr_delegate_type::hdt_get_function_pointer) 211 | .map(|ptr| mem::transmute(ptr)) 212 | } 213 | } 214 | #[cfg(feature = "net8_0")] 215 | fn get_load_assembly_delegate(&self) -> Result { 216 | unsafe { 217 | self.get_runtime_delegate(hostfxr_delegate_type::hdt_load_assembly) 218 | .map(|ptr| mem::transmute(ptr)) 219 | } 220 | } 221 | #[cfg(feature = "net8_0")] 222 | fn get_load_assembly_bytes_delegate(&self) -> Result { 223 | unsafe { 224 | self.get_runtime_delegate(hostfxr_delegate_type::hdt_load_assembly_bytes) 225 | .map(|ptr| mem::transmute(ptr)) 226 | } 227 | } 228 | 229 | /// Gets a delegate loader for loading an assembly and contained function pointers. 230 | pub fn get_delegate_loader(&self) -> Result { 231 | Ok(DelegateLoader { 232 | get_load_assembly_and_get_function_pointer: self 233 | .get_load_assembly_and_get_function_pointer_delegate()?, 234 | #[cfg(feature = "net5_0")] 235 | get_function_pointer: self.get_get_function_pointer_delegate()?, 236 | hostfxr: self.hostfxr.clone(), 237 | }) 238 | } 239 | 240 | /// Gets a delegate loader for loading function pointers of the assembly with the given path. 241 | /// The assembly will be loaded lazily when the first function pointer is loaded. 242 | pub fn get_delegate_loader_for_assembly( 243 | &self, 244 | assembly_path: impl Into, 245 | ) -> Result { 246 | self.get_delegate_loader() 247 | .map(|loader| AssemblyDelegateLoader::new(loader, assembly_path)) 248 | } 249 | 250 | /// Loads the specified assembly in the default load context from the given path. 251 | /// It uses [`AssemblyDependencyResolver`] to register additional dependency resolution for the load context. 252 | /// Function pointers to methods in the assembly can then be loaded using a [`DelegateLoader`]. 253 | /// 254 | /// [`AssemblyDependencyResolver`]: https://learn.microsoft.com/en-us/dotnet/api/system.runtime.loader.assemblydependencyresolver 255 | /// [`AssemblyLoadContext.LoadFromAssembly`]: https://learn.microsoft.com/en-us/dotnet/api/system.runtime.loader.assemblyloadcontext.loadfromassemblypath 256 | #[cfg(feature = "net8_0")] 257 | pub fn load_assembly_from_path( 258 | &self, 259 | assembly_path: impl AsRef, 260 | ) -> Result<(), HostingError> { 261 | let assembly_path = assembly_path.as_ref(); 262 | let load_assembly = self.get_load_assembly_delegate()?; 263 | let result = unsafe { load_assembly(assembly_path.as_ptr(), ptr::null(), ptr::null()) }; 264 | HostingResult::from(result).into_result()?; 265 | Ok(()) 266 | } 267 | 268 | /// Loads the specified assembly in the default load context from the given buffers. 269 | /// It does not provide a mechanism for registering additional dependency resolution, as mechanisms like `.deps.json` and [`AssemblyDependencyResolver`] are file-based. 270 | /// Dependencies can be pre-loaded (for example, via a previous call to this function) or the specified assembly can explicitly register its own resolution logic (for example, via the [`AssemblyLoadContext.Resolving`] event). 271 | /// It uses [`AssemblyDependencyResolver`] to register additional dependency resolution for the load context. 272 | /// Function pointers to methods in the assembly can then be loaded using a [`DelegateLoader`]. 273 | /// 274 | /// [`AssemblyDependencyResolver`]: https://learn.microsoft.com/en-us/dotnet/api/system.runtime.loader.assemblydependencyresolver 275 | /// [`AssemblyLoadContext.Resolving`]: https://learn.microsoft.com/en-us/dotnet/api/system.runtime.loader.assemblyloadcontext.resolving?view=net-7.0 276 | #[cfg(feature = "net8_0")] 277 | pub fn load_assembly_from_bytes( 278 | &self, 279 | assembly_bytes: impl AsRef<[u8]>, 280 | symbols_bytes: impl AsRef<[u8]>, 281 | ) -> Result<(), HostingError> { 282 | let symbols_bytes = symbols_bytes.as_ref(); 283 | let assembly_bytes = assembly_bytes.as_ref(); 284 | let load_assembly_bytes = self.get_load_assembly_bytes_delegate()?; 285 | let result = unsafe { 286 | load_assembly_bytes( 287 | assembly_bytes.as_ptr(), 288 | assembly_bytes.len(), 289 | symbols_bytes.as_ptr(), 290 | symbols_bytes.len(), 291 | ptr::null_mut(), 292 | ptr::null_mut(), 293 | ) 294 | }; 295 | HostingResult::from(result).into_result()?; 296 | Ok(()) 297 | } 298 | 299 | /// Closes an initialized host context. 300 | /// This method is automatically called on drop, but can be explicitely called to handle errors during closing. 301 | pub fn close(self) -> Result { 302 | let result = unsafe { self.close_raw() }; 303 | self.destruct_drop(); 304 | result 305 | } 306 | 307 | /// Internal non-consuming version of [`close`](HostfxrContext::close) 308 | unsafe fn close_raw(&self) -> Result { 309 | let result = unsafe { self.hostfxr.hostfxr_close(self.handle.as_raw()) }.unwrap(); 310 | HostingResult::from(result).into_result() 311 | } 312 | } 313 | 314 | impl HostfxrContext { 315 | /// Load the dotnet runtime and run the application. 316 | /// 317 | /// # Return value 318 | /// If the app was successfully run, the exit code of the application. Otherwise, the error code result. 319 | #[must_use] 320 | pub fn run_app(self) -> AppOrHostingResult { 321 | let result = unsafe { self.hostfxr.hostfxr_run_app(self.handle.as_raw()) }.unwrap(); 322 | AppOrHostingResult::from(result) 323 | } 324 | } 325 | 326 | impl Drop for HostfxrContext { 327 | fn drop(&mut self) { 328 | let _ = unsafe { self.close_raw() }; 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /src/hostfxr/delegate_loader.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | bindings::{ 3 | char_t, 4 | hostfxr::{component_entry_point_fn, load_assembly_and_get_function_pointer_fn}, 5 | }, 6 | error::{HostingError, HostingResult, HostingSuccess}, 7 | pdcstring::{PdCStr, PdCString}, 8 | }; 9 | use num_enum::TryFromPrimitive; 10 | use std::{convert::TryFrom, mem::MaybeUninit, path::Path, ptr}; 11 | use thiserror::Error; 12 | 13 | use super::{as_managed, FnPtr, ManagedFunction, RawFnPtr, SharedHostfxrLibrary}; 14 | 15 | #[cfg(feature = "net5_0")] 16 | use crate::bindings::hostfxr::{get_function_pointer_fn, UNMANAGED_CALLERS_ONLY_METHOD}; 17 | 18 | /// A pointer to a function with the default signature. 19 | pub type ManagedFunctionWithDefaultSignature = ManagedFunction; 20 | /// A pointer to a function with an unknown signature. 21 | pub type ManagedFunctionWithUnknownSignature = ManagedFunction; 22 | 23 | /// A struct for loading pointers to managed functions for a given [`HostfxrContext`]. 24 | /// 25 | /// [`HostfxrContext`]: super::HostfxrContext 26 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))] 27 | pub struct DelegateLoader { 28 | pub(crate) get_load_assembly_and_get_function_pointer: 29 | load_assembly_and_get_function_pointer_fn, 30 | #[cfg(feature = "net5_0")] 31 | pub(crate) get_function_pointer: get_function_pointer_fn, 32 | #[allow(unused)] 33 | pub(crate) hostfxr: SharedHostfxrLibrary, 34 | } 35 | 36 | impl Clone for DelegateLoader { 37 | fn clone(&self) -> Self { 38 | Self { 39 | get_load_assembly_and_get_function_pointer: self 40 | .get_load_assembly_and_get_function_pointer, 41 | #[cfg(feature = "net5_0")] 42 | get_function_pointer: self.get_function_pointer, 43 | hostfxr: self.hostfxr.clone(), 44 | } 45 | } 46 | } 47 | 48 | impl DelegateLoader { 49 | unsafe fn load_assembly_and_get_function_pointer_raw( 50 | &self, 51 | assembly_path: *const char_t, 52 | type_name: *const char_t, 53 | method_name: *const char_t, 54 | delegate_type_name: *const char_t, 55 | ) -> Result { 56 | let mut delegate = MaybeUninit::uninit(); 57 | 58 | let result = unsafe { 59 | (self.get_load_assembly_and_get_function_pointer)( 60 | assembly_path, 61 | type_name, 62 | method_name, 63 | delegate_type_name, 64 | ptr::null(), 65 | delegate.as_mut_ptr(), 66 | ) 67 | }; 68 | GetManagedFunctionError::from_status_code(result)?; 69 | 70 | Ok(unsafe { delegate.assume_init() }.cast()) 71 | } 72 | 73 | fn validate_assembly_path( 74 | assembly_path: impl AsRef, 75 | ) -> Result<(), GetManagedFunctionError> { 76 | #[cfg(windows)] 77 | let assembly_path = assembly_path.as_ref().to_os_string(); 78 | 79 | #[cfg(not(windows))] 80 | let assembly_path = ::from_bytes( 81 | assembly_path.as_ref().as_slice(), 82 | ); 83 | 84 | if Path::new(&assembly_path).exists() { 85 | Ok(()) 86 | } else { 87 | Err(GetManagedFunctionError::AssemblyNotFound) 88 | } 89 | } 90 | 91 | #[cfg(feature = "net5_0")] 92 | unsafe fn get_function_pointer_raw( 93 | &self, 94 | type_name: *const char_t, 95 | method_name: *const char_t, 96 | delegate_type_name: *const char_t, 97 | ) -> Result { 98 | let mut delegate = MaybeUninit::uninit(); 99 | 100 | let result = unsafe { 101 | (self.get_function_pointer)( 102 | type_name, 103 | method_name, 104 | delegate_type_name, 105 | ptr::null(), 106 | ptr::null(), 107 | delegate.as_mut_ptr(), 108 | ) 109 | }; 110 | GetManagedFunctionError::from_status_code(result)?; 111 | 112 | Ok(unsafe { delegate.assume_init() }.cast()) 113 | } 114 | 115 | /// Calling this function will load the specified assembly in isolation (into its own `AssemblyLoadContext`) 116 | /// and it will use `AssemblyDependencyResolver` on it to provide dependency resolution. 117 | /// Once loaded it will find the specified type and method and return a native function pointer 118 | /// to that method. 119 | /// 120 | /// # Arguments 121 | /// * `assembly_path`: 122 | /// Path to the assembly to load. 123 | /// In case of complex component, this should be the main assembly of the component (the one with the .deps.json next to it). 124 | /// Note that this does not have to be the assembly from which the `type_name` and `method_name` are. 125 | /// * `type_name`: 126 | /// Assembly qualified type name to find 127 | /// * `method_name`: 128 | /// Name of the method on the `type_name` to find. The method must be static and must match the signature of `delegate_type_name`. 129 | /// * `delegate_type_name`: 130 | /// Assembly qualified delegate type name for the method signature. 131 | pub fn load_assembly_and_get_function( 132 | &self, 133 | assembly_path: &PdCStr, 134 | type_name: &PdCStr, 135 | method_name: &PdCStr, 136 | delegate_type_name: &PdCStr, 137 | ) -> Result, GetManagedFunctionError> { 138 | Self::validate_assembly_path(assembly_path)?; 139 | let function = unsafe { 140 | self.load_assembly_and_get_function_pointer_raw( 141 | assembly_path.as_ptr(), 142 | type_name.as_ptr(), 143 | method_name.as_ptr(), 144 | delegate_type_name.as_ptr(), 145 | ) 146 | }?; 147 | Ok(ManagedFunction(unsafe { 148 | ::from_ptr(function) 149 | })) 150 | } 151 | 152 | /// Calling this function will load the specified assembly in isolation (into its own `AssemblyLoadContext`) 153 | /// and it will use `AssemblyDependencyResolver` on it to provide dependency resolution. 154 | /// Once loaded it will find the specified type and method and return a native function pointer 155 | /// to that method. 156 | /// 157 | /// # Arguments 158 | /// * `assembly_path`: 159 | /// Path to the assembly to load. 160 | /// In case of complex component, this should be the main assembly of the component (the one with the .deps.json next to it). 161 | /// Note that this does not have to be the assembly from which the `type_name` and `method_name` are. 162 | /// * `type_name`: 163 | /// Assembly qualified type name to find 164 | /// * `method_name`: 165 | /// Name of the method on the `type_name` to find. The method must be static and must match the following signature: 166 | /// `public delegate int ComponentEntryPoint(IntPtr args, int sizeBytes);` 167 | pub fn load_assembly_and_get_function_with_default_signature( 168 | &self, 169 | assembly_path: &PdCStr, 170 | type_name: &PdCStr, 171 | method_name: &PdCStr, 172 | ) -> Result { 173 | Self::validate_assembly_path(assembly_path)?; 174 | let function = unsafe { 175 | self.load_assembly_and_get_function_pointer_raw( 176 | assembly_path.as_ptr(), 177 | type_name.as_ptr(), 178 | method_name.as_ptr(), 179 | ptr::null(), 180 | ) 181 | }?; 182 | Ok(ManagedFunction(unsafe { FnPtr::from_ptr(function) })) 183 | } 184 | 185 | /// Calling this function will load the specified assembly in isolation (into its own `AssemblyLoadContext`) 186 | /// and it will use `AssemblyDependencyResolver` on it to provide dependency resolution. 187 | /// Once loaded it will find the specified type and method and return a native function pointer 188 | /// to that method. The target method has to be annotated with the [`UnmanagedCallersOnlyAttribute`]. 189 | /// 190 | /// # Arguments 191 | /// * `assembly_path`: 192 | /// Path to the assembly to load. 193 | /// In case of complex component, this should be the main assembly of the component (the one with the .deps.json next to it). 194 | /// Note that this does not have to be the assembly from which the `type_name` and `method_name` are. 195 | /// * `type_name`: 196 | /// Assembly qualified type name to find 197 | /// * `method_name`: 198 | /// Name of the method on the `type_name` to find. The method must be static and must match be annotated with [`[UnmanagedCallersOnly]`][UnmanagedCallersOnly]. 199 | /// 200 | /// [`UnmanagedCallersOnlyAttribute`]: 201 | /// [UnmanagedCallersOnly]: 202 | #[cfg(feature = "net5_0")] 203 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "net5_0")))] 204 | pub fn load_assembly_and_get_function_with_unmanaged_callers_only( 205 | &self, 206 | assembly_path: &PdCStr, 207 | type_name: &PdCStr, 208 | method_name: &PdCStr, 209 | ) -> Result, GetManagedFunctionError> { 210 | Self::validate_assembly_path(assembly_path)?; 211 | let function = unsafe { 212 | self.load_assembly_and_get_function_pointer_raw( 213 | assembly_path.as_ptr(), 214 | type_name.as_ptr(), 215 | method_name.as_ptr(), 216 | UNMANAGED_CALLERS_ONLY_METHOD, 217 | ) 218 | }?; 219 | Ok(ManagedFunction(unsafe { 220 | ::from_ptr(function) 221 | })) 222 | } 223 | 224 | /// Calling this function will find the specified type and method and return a native function pointer to that method. 225 | /// This will **NOT** load the containing assembly. 226 | /// 227 | /// # Arguments 228 | /// * `type_name`: 229 | /// Assembly qualified type name to find 230 | /// * `method_name`: 231 | /// Name of the method on the `type_name` to find. The method must be static and must match the signature of `delegate_type_name`. 232 | /// * `delegate_type_name`: 233 | /// Assembly qualified delegate type name for the method signature. 234 | #[cfg(feature = "net5_0")] 235 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "net5_0")))] 236 | pub fn get_function( 237 | &self, 238 | type_name: &PdCStr, 239 | method_name: &PdCStr, 240 | delegate_type_name: &PdCStr, 241 | ) -> Result, GetManagedFunctionError> { 242 | let function = unsafe { 243 | self.get_function_pointer_raw( 244 | type_name.as_ptr(), 245 | method_name.as_ptr(), 246 | delegate_type_name.as_ptr(), 247 | ) 248 | }?; 249 | Ok(ManagedFunction(unsafe { 250 | ::from_ptr(function) 251 | })) 252 | } 253 | 254 | /// Calling this function will find the specified type and method and return a native function pointer to that method. 255 | /// This will **NOT** load the containing assembly. 256 | /// 257 | /// # Arguments 258 | /// * `type_name`: 259 | /// Assembly qualified type name to find 260 | /// * `method_name`: 261 | /// Name of the method on the `type_name` to find. The method must be static and must match the following signature: 262 | /// `public delegate int ComponentEntryPoint(IntPtr args, int sizeBytes);` 263 | #[cfg(feature = "net5_0")] 264 | pub fn get_function_with_default_signature( 265 | &self, 266 | type_name: &PdCStr, 267 | method_name: &PdCStr, 268 | ) -> Result { 269 | let function = unsafe { 270 | self.get_function_pointer_raw(type_name.as_ptr(), method_name.as_ptr(), ptr::null()) 271 | }?; 272 | Ok(ManagedFunction(unsafe { FnPtr::from_ptr(function) })) 273 | } 274 | 275 | /// Calling this function will find the specified type and method and return a native function pointer to that method. 276 | /// This will **NOT** load the containing assembly. 277 | /// 278 | /// # Arguments 279 | /// * `type_name`: 280 | /// Assembly qualified type name to find 281 | /// * `method_name`: 282 | /// Name of the method on the `type_name` to find. The method must be static and must match be annotated with [`UnmanagedCallersOnly`]. 283 | /// 284 | /// [`UnmanagedCallersOnlyAttribute`]: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute 285 | /// [`UnmanagedCallersOnly`]: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute 286 | #[cfg(feature = "net5_0")] 287 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "net5_0")))] 288 | pub fn get_function_with_unmanaged_callers_only( 289 | &self, 290 | type_name: &PdCStr, 291 | method_name: &PdCStr, 292 | ) -> Result, GetManagedFunctionError> { 293 | let function = unsafe { 294 | self.get_function_pointer_raw( 295 | type_name.as_ptr(), 296 | method_name.as_ptr(), 297 | UNMANAGED_CALLERS_ONLY_METHOD, 298 | ) 299 | }?; 300 | Ok(ManagedFunction(unsafe { 301 | ::from_ptr(function) 302 | })) 303 | } 304 | } 305 | 306 | /// A struct for loading pointers to managed functions for a given [`HostfxrContext`] which automatically loads the 307 | /// assembly from the given path on the first access. 308 | /// 309 | /// [`HostfxrContext`]: super::HostfxrContext 310 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))] 311 | #[derive(Clone)] 312 | pub struct AssemblyDelegateLoader { 313 | loader: DelegateLoader, 314 | assembly_path: PdCString, 315 | } 316 | 317 | impl AssemblyDelegateLoader { 318 | /// Creates a new [`AssemblyDelegateLoader`] wrapping the given [`DelegateLoader`] loading the assembly 319 | /// from the given path on the first access. 320 | pub fn new(loader: DelegateLoader, assembly_path: impl Into) -> Self { 321 | let assembly_path = assembly_path.into(); 322 | Self { 323 | loader, 324 | assembly_path, 325 | } 326 | } 327 | 328 | /// If this is the first loaded function pointer, calling this function will load the specified assembly in 329 | /// isolation (into its own `AssemblyLoadContext`) and it will use `AssemblyDependencyResolver` on it to provide 330 | /// dependency resolution. 331 | /// Otherwise or once loaded it will find the specified type and method and return a native function pointer to that method. 332 | /// Calling this function will find the specified type and method and return a native function pointer to that method. 333 | /// 334 | /// # Arguments 335 | /// * `type_name`: 336 | /// Assembly qualified type name to find 337 | /// * `method_name`: 338 | /// Name of the method on the `type_name` to find. The method must be static and must match the signature of `delegate_type_name`. 339 | /// * `delegate_type_name`: 340 | /// Assembly qualified delegate type name for the method signature. 341 | pub fn get_function( 342 | &self, 343 | type_name: &PdCStr, 344 | method_name: &PdCStr, 345 | delegate_type_name: &PdCStr, 346 | ) -> Result, GetManagedFunctionError> { 347 | self.loader.load_assembly_and_get_function::( 348 | self.assembly_path.as_ref(), 349 | type_name, 350 | method_name, 351 | delegate_type_name, 352 | ) 353 | } 354 | 355 | /// If this is the first loaded function pointer, calling this function will load the specified assembly in 356 | /// isolation (into its own `AssemblyLoadContext`) and it will use `AssemblyDependencyResolver` on it to provide 357 | /// dependency resolution. 358 | /// Otherwise or once loaded it will find the specified type and method and return a native function pointer to that method. 359 | /// Calling this function will find the specified type and method and return a native function pointer to that method. 360 | /// 361 | /// # Arguments 362 | /// * `type_name`: 363 | /// Assembly qualified type name to find 364 | /// * `method_name`: 365 | /// Name of the method on the `type_name` to find. The method must be static and must match the following signature: 366 | /// `public delegate int ComponentEntryPoint(IntPtr args, int sizeBytes);` 367 | pub fn get_function_with_default_signature( 368 | &self, 369 | type_name: &PdCStr, 370 | method_name: &PdCStr, 371 | ) -> Result { 372 | self.loader 373 | .load_assembly_and_get_function_with_default_signature( 374 | self.assembly_path.as_ref(), 375 | type_name, 376 | method_name, 377 | ) 378 | } 379 | 380 | /// If this is the first loaded function pointer, calling this function will load the specified assembly in 381 | /// isolation (into its own `AssemblyLoadContext`) and it will use `AssemblyDependencyResolver` on it to provide 382 | /// dependency resolution. 383 | /// Otherwise or once loaded it will find the specified type and method and return a native function pointer to that method. 384 | /// Calling this function will find the specified type and method and return a native function pointer to that method. 385 | /// 386 | /// # Arguments 387 | /// * `type_name`: 388 | /// Assembly qualified type name to find 389 | /// * `method_name`: 390 | /// Name of the method on the `type_name` to find. The method must be static and must match be annotated with [`UnmanagedCallersOnly`]. 391 | /// 392 | /// [`UnmanagedCallersOnlyAttribute`]: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute 393 | /// [`UnmanagedCallersOnly`]: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute 394 | #[cfg(feature = "net5_0")] 395 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "net5_0")))] 396 | pub fn get_function_with_unmanaged_callers_only( 397 | &self, 398 | type_name: &PdCStr, 399 | method_name: &PdCStr, 400 | ) -> Result, GetManagedFunctionError> { 401 | self.loader 402 | .load_assembly_and_get_function_with_unmanaged_callers_only::( 403 | self.assembly_path.as_ref(), 404 | type_name, 405 | method_name, 406 | ) 407 | } 408 | } 409 | 410 | /// Enum for errors that can occur while loading a managed assembly or managed function pointers. 411 | #[derive(Error, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 412 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))] 413 | pub enum GetManagedFunctionError { 414 | /// An error occured inside the hosting components. 415 | #[error("Error from hosting components: {}.", .0)] 416 | Hosting(#[from] HostingError), 417 | 418 | /// A type with the specified name could not be found or loaded. 419 | #[error("Failed to load the type or method or it has an incompatible signature.")] 420 | TypeOrMethodNotFound, 421 | 422 | /// The specified assembly could not be found. 423 | #[error("The specified assembly could not be found.")] 424 | AssemblyNotFound, 425 | 426 | /// The target method is not annotated with [`UnmanagedCallersOnly`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute). 427 | #[error("The target method is not annotated with UnmanagedCallersOnly.")] 428 | MethodNotUnmanagedCallersOnly, 429 | 430 | /// Some other unknown error occured. 431 | #[error("Unknown error code: {}", format!("{:#08X}", .0))] 432 | Other(u32), 433 | } 434 | 435 | impl GetManagedFunctionError { 436 | /// Converts the given staus code to a [`GetManagedFunctionError`]. 437 | pub fn from_status_code(code: i32) -> Result { 438 | let code = code as u32; 439 | match HostingResult::known_from_status_code(code) { 440 | Ok(HostingResult(Ok(code))) => return Ok(code), 441 | Ok(HostingResult(Err(code))) => return Err(GetManagedFunctionError::Hosting(code)), 442 | _ => {} 443 | } 444 | match HResult::try_from(code) { 445 | Ok( 446 | HResult::COR_E_TYPELOAD | HResult::COR_E_MISSINGMETHOD | HResult::COR_E_ARGUMENT, 447 | ) => return Err(Self::TypeOrMethodNotFound), 448 | Ok(HResult::FILE_NOT_FOUND) => return Err(Self::AssemblyNotFound), 449 | Ok(HResult::COR_E_INVALIDOPERATION) => return Err(Self::MethodNotUnmanagedCallersOnly), 450 | _ => {} 451 | } 452 | Err(Self::Other(code)) 453 | } 454 | } 455 | 456 | #[repr(u32)] 457 | #[non_exhaustive] 458 | #[derive(TryFromPrimitive, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 459 | #[allow(non_camel_case_types)] 460 | #[rustfmt::skip] 461 | enum HResult { 462 | E_POINTER = 0x8000_4003, // System.ArgumentNullException 463 | COR_E_ARGUMENTOUTOFRANGE = 0x8013_1502, // System.ArgumentOutOfRangeException (reserved was not 0) 464 | COR_E_TYPELOAD = 0x8013_1522, // invalid type 465 | COR_E_MISSINGMETHOD = 0x8013_1513, // invalid method 466 | /*COR_E_*/FILE_NOT_FOUND = 0x8007_0002, // assembly with specified name not found (from type name) 467 | COR_E_ARGUMENT = 0x8007_0057, // invalid method signature or method not found 468 | COR_E_INVALIDOPERATION = 0x8013_1509, // invalid assembly path or not unmanaged, 469 | } 470 | --------------------------------------------------------------------------------