├── .github ├── scripts │ ├── install-dotnet-macos │ ├── install-dotnet-ubuntu │ ├── install-dotnet-windows.ps1 │ ├── uninstall-dotnet-macos │ ├── uninstall-dotnet-ubuntu │ └── uninstall-dotnet-windows.ps1 └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── call-managed-function │ ├── ExampleProject │ │ ├── .gitignore │ │ ├── ExampleProject.csproj │ │ ├── ExampleProject.sln │ │ └── Program.cs │ └── 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 ├── run-app-with-args │ ├── ExampleProject │ │ ├── .gitignore │ │ ├── ExampleProject.csproj │ │ ├── ExampleProject.sln │ │ └── Program.cs │ └── main.rs └── run-app │ ├── ExampleProject │ ├── .gitignore │ ├── ExampleProject.csproj │ ├── ExampleProject.sln │ └── Program.cs │ └── main.rs ├── src ├── bindings │ └── mod.rs ├── error │ ├── hosting_result.rs │ ├── mod.rs │ └── univ.rs ├── hostfxr │ ├── context.rs │ ├── delegate_loader.rs │ ├── library.rs │ ├── library1_0.rs │ ├── library2_1.rs │ ├── library3_0.rs │ ├── library6_0.rs │ ├── managed_function.rs │ ├── mod.rs │ └── runtime_property.rs ├── lib.rs ├── nethost.rs └── pdcstring │ ├── error.rs │ ├── impl │ ├── mod.rs │ ├── other │ │ ├── error.rs │ │ ├── ext.rs │ │ ├── mod.rs │ │ ├── pdcstr.rs │ │ └── pdcstring.rs │ ├── traits.rs │ └── windows │ │ ├── error.rs │ │ ├── ext.rs │ │ ├── mod.rs │ │ ├── pdcstr.rs │ │ └── pdcstring.rs │ ├── mod.rs │ └── shared.rs └── tests ├── ClassLibrary ├── .gitignore ├── ClassLibrary-net10.0.csproj ├── ClassLibrary-net8.0.csproj ├── ClassLibrary-net9.0.csproj └── Library.cs ├── Test ├── .gitignore ├── Program.cs ├── Test-net10.0.csproj ├── Test-net8.0.csproj └── Test-net9.0.csproj ├── common.rs ├── custom_delegate_type.rs ├── environment_info.rs ├── error_writer.rs ├── errors.rs ├── hello_world.rs ├── load_assembly_manually.rs ├── macro-build-tests ├── .gitignore ├── pdcstr-compile-fail.other.stderr ├── pdcstr-compile-fail.rs ├── pdcstr-compile-fail.windows.stderr └── pdcstr-pass.rs ├── macro-test-crate ├── .gitignore ├── Cargo.toml └── src │ └── main.rs ├── manual_close_frees_lib.rs ├── pdcstr.rs ├── primary_and_secondary.rs ├── run_app.rs ├── runtime_properties.rs ├── sdk_resolve.rs └── unmanaged_callers_only.rs /.github/scripts/install-dotnet-macos: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | chmod +x ./.github/scripts/uninstall-dotnet-linux 4 | ./.github/scripts/uninstall-dotnet-linux 5 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.github/scripts/uninstall-dotnet-macos: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Download uninstall tool 4 | latest_release_json=$(curl -s 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 --aspnet-runtime --verbosity detailed 16 | sudo "$uninstall_tool_path" remove --yes --force --all --hosting-bundle --verbosity detailed 17 | sudo "$uninstall_tool_path" remove --yes --force --all --runtime --verbosity detailed 18 | sudo "$uninstall_tool_path" remove --yes --force --all --sdk --verbosity detailed 19 | -------------------------------------------------------------------------------- /.github/scripts/uninstall-dotnet-ubuntu: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sudo apt remove 'dotnet*' 4 | sudo apt remove 'aspnetcore*' 5 | -------------------------------------------------------------------------------- /.github/scripts/uninstall-dotnet-windows.ps1: -------------------------------------------------------------------------------- 1 | # Download uninstall tool 2 | $releases = Invoke-RestMethod -Uri "https://api.github.com/repos/dotnet/cli-lab/releases/latest" 3 | $asset = $releases.assets | Where-Object { $_.name -eq "dotnet-core-uninstall.msi" } | Select-Object -First 1 4 | $url = $asset.browser_download_url 5 | Invoke-WebRequest -Uri $url -OutFile $(Split-Path $url -Leaf) 6 | 7 | # Prepare uninstall tool 8 | $extractPath = Join-Path $pwd "dotnet-core-uninstall" # needs to be a new path 9 | msiexec.exe /A dotnet-core-uninstall.msi TARGETDIR=$extractPath /QN /L*V log.txt 10 | $uninstallToolPath = Join-Path $extractPath "dotnet-core-uninstall" "dotnet-core-uninstall.exe" 11 | # wait for the tool to be ready 12 | $maxRetries = 30 13 | $retry = 0 14 | while (-not (Test-Path $uninstallToolPath) -and ($retry -lt $maxRetries)) { 15 | Start-Sleep -Seconds 1 16 | $retry++ 17 | } 18 | if ($retry -eq $maxRetries) { 19 | Write-Error "Uninstall tool was not found after $maxRetries seconds." 20 | Get-Content -Path "log.txt" | Write-Host 21 | exit 1 22 | } 23 | 24 | # Perform uninstall 25 | & $uninstallToolPath remove --yes --force --all --aspnet-runtime --verbosity detailed 26 | & $uninstallToolPath remove --yes --force --all --hosting-bundle --verbosity detailed 27 | & $uninstallToolPath remove --yes --force --all --runtime --verbosity detailed 28 | & $uninstallToolPath remove --yes --force --all --sdk --verbosity detailed 29 | -------------------------------------------------------------------------------- /.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@v4 42 | 43 | - name: Uninstall .NET SDKs 44 | run: ./.github/scripts/uninstall-dotnet-${{ matrix.os }} 45 | 46 | - name: Install .NET SDK ${{ matrix.dotnet }} 47 | run: ./.github/scripts/install-dotnet-${{ matrix.os }} ${{ matrix.dotnet }} ${{ matrix.arch }} 48 | 49 | - name: Check .NET Installation 50 | run: dotnet --info 51 | 52 | - name: Install latest ${{ matrix.toolchain }} 53 | uses: dtolnay/rust-toolchain@stable 54 | with: 55 | target: ${{ matrix.target }} 56 | toolchain: ${{ matrix.toolchain }} 57 | 58 | - name: Build 59 | run: cargo build --target ${{ matrix.target }} --no-default-features --features "nethost-download $("net" + "${{ matrix.dotnet }}".replace(".", "_"))" 60 | shell: pwsh 61 | 62 | - name: Test 63 | run: cargo test --target ${{ matrix.target }} --all-targets --no-fail-fast --no-default-features --features "nethost-download $("net" + "${{ matrix.dotnet }}".replace(".", "_"))" -- --nocapture 64 | shell: pwsh 65 | 66 | cross: 67 | runs-on: ubuntu-latest 68 | strategy: 69 | fail-fast: false 70 | matrix: 71 | toolchain: ["beta"] 72 | target: ["aarch64-unknown-linux-gnu", "armv7-unknown-linux-gnueabihf"] 73 | 74 | steps: 75 | - uses: actions/checkout@v4 76 | 77 | - name: Install latest ${{ matrix.toolchain }} 78 | uses: dtolnay/rust-toolchain@stable 79 | with: 80 | target: ${{ matrix.target }} 81 | toolchain: ${{ matrix.toolchain }} 82 | 83 | - name: Install cross 84 | # temporary fix, see cross-rs/cross#1561 85 | run: RUSTFLAGS="" cargo install cross --git https://github.com/cross-rs/cross 86 | 87 | - name: Build 88 | run: cross build --target ${{ matrix.target }} 89 | 90 | examples: 91 | runs-on: ubuntu-latest 92 | strategy: 93 | matrix: 94 | toolchain: ["beta"] 95 | example: ["run-app", "run-app-with-args", "call-managed-function", "passing-parameters", "return-string-from-managed"] 96 | steps: 97 | - uses: actions/checkout@v4 98 | - name: Install latest ${{ matrix.toolchain }} 99 | uses: dtolnay/rust-toolchain@stable 100 | with: 101 | toolchain: ${{ matrix.toolchain }} 102 | - name: Build .NET project for '${{ matrix.example }}' 103 | working-directory: ./examples/${{ matrix.example }}/ExampleProject 104 | run: dotnet build 105 | - name: Run example '${{ matrix.example }}' 106 | run: cargo run --example ${{ matrix.example }} 107 | 108 | documentation: 109 | runs-on: ${{ matrix.os }}-latest 110 | strategy: 111 | matrix: 112 | include: 113 | - os: ubuntu 114 | - os: windows 115 | steps: 116 | - uses: actions/checkout@v4 117 | - name: Install latest nightly 118 | uses: dtolnay/rust-toolchain@stable 119 | with: 120 | 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@v4 137 | - name: Install latest nightly 138 | uses: dtolnay/rust-toolchain@stable 139 | with: 140 | toolchain: nightly 141 | components: clippy 142 | 143 | - name: Clippy check 144 | run: cargo clippy --all-features 145 | 146 | fmt: 147 | runs-on: ${{ matrix.os }}-latest 148 | strategy: 149 | matrix: 150 | include: 151 | - os: ubuntu 152 | - os: windows 153 | steps: 154 | - uses: actions/checkout@v4 155 | - name: Install latest nightly 156 | uses: dtolnay/rust-toolchain@stable 157 | with: 158 | toolchain: nightly 159 | components: rustfmt 160 | 161 | - name: Format check 162 | run: cargo fmt --all -- --check 163 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .metadata 4 | .vscode 5 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "netcorehost" 3 | version = "0.18.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.12", 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 | ffi-opaque = { version = "2.0", default-features = false } 22 | enum-map = { version = "2.7", default-features = false } 23 | once_cell = { version = "1.21", 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 | 32 | [dev-dependencies] 33 | trybuild = "1.0" 34 | current_platform = "0.2" 35 | glob = "0.3" 36 | widestring = "1.2" 37 | rusty-fork = "0.3" 38 | path-absolutize = "3.1" 39 | 40 | [features] 41 | default = ["nethost-download", "net8_0"] 42 | nethost-download = ["nethost", "nethost-sys/download-nuget"] 43 | nethost = ["nethost-sys"] 44 | nightly = [] 45 | doc-cfg = [] 46 | netcore1_0 = ["hostfxr-sys/netcore1_0"] 47 | netcore2_0 = ["hostfxr-sys/netcore2_0", "netcore1_0"] 48 | netcore2_1 = ["hostfxr-sys/netcore2_1", "netcore2_0"] 49 | netcore3_0 = ["hostfxr-sys/netcore3_0", "netcore2_1"] 50 | net5_0 = ["hostfxr-sys/net5_0", "netcore3_0"] 51 | net6_0 = ["hostfxr-sys/net6_0", "net5_0"] 52 | net7_0 = ["hostfxr-sys/net7_0", "net6_0"] 53 | net8_0 = ["hostfxr-sys/net8_0", "net7_0"] 54 | net9_0 = ["hostfxr-sys/net9_0", "net8_0"] 55 | net10_0 = ["hostfxr-sys/net10_0", "net9_0"] 56 | latest = ["hostfxr-sys/latest", "net10_0"] 57 | 58 | # Prevent downloading nethost library when building on docs.rs. 59 | [package.metadata.docs.rs] 60 | features = ["nethost", "latest", "doc-cfg", "nightly"] 61 | no-default-features = true 62 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 withing 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 | -------------------------------------------------------------------------------- /examples/call-managed-function/ExampleProject/.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | obj/ 3 | bin/ 4 | -------------------------------------------------------------------------------- /examples/call-managed-function/ExampleProject/ExampleProject.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Library 5 | net8.0 6 | true 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/net8.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/net8.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 | -------------------------------------------------------------------------------- /examples/passing-parameters/ExampleProject/.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | obj/ 3 | bin/ 4 | -------------------------------------------------------------------------------- /examples/passing-parameters/ExampleProject/ExampleProject.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Library 5 | net8.0 6 | true 7 | true 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/net8.0/ExampleProject.runtimeconfig.json")).unwrap(); 6 | let delegate_loader = context 7 | .get_delegate_loader_for_assembly(pdcstr!( 8 | "examples/passing-parameters/ExampleProject/bin/Debug/net8.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 | -------------------------------------------------------------------------------- /examples/return-string-from-managed/ExampleProject/.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | obj/ 3 | bin/ 4 | -------------------------------------------------------------------------------- /examples/return-string-from-managed/ExampleProject/ExampleProject.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Library 5 | net8.0 6 | true 7 | true 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/net8.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/net8.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 | -------------------------------------------------------------------------------- /examples/run-app-with-args/ExampleProject/.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | obj/ 3 | bin/ 4 | -------------------------------------------------------------------------------- /examples/run-app-with-args/ExampleProject/ExampleProject.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/net8.0/ExampleProject.dll" 16 | ), 17 | args, 18 | ) 19 | .unwrap(); 20 | 21 | let result = context.run_app().value(); 22 | println!("Exit code: {}", result); 23 | } 24 | -------------------------------------------------------------------------------- /examples/run-app/ExampleProject/.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | obj/ 3 | bin/ 4 | -------------------------------------------------------------------------------- /examples/run-app/ExampleProject/ExampleProject.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/net8.0/ExampleProject.dll" 8 | )) 9 | .unwrap(); 10 | context.run_app().as_hosting_exit_code().unwrap(); 11 | } 12 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/error/mod.rs: -------------------------------------------------------------------------------- 1 | mod hosting_result; 2 | pub use hosting_result::*; 3 | 4 | mod univ; 5 | pub use univ::*; 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | RawFunctionPtr, 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::{FunctionPtr, ManagedFunction, RawFunctionPtr, 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 { F::Managed::from_ptr(function) })) 148 | } 149 | 150 | /// Calling this function will load the specified assembly in isolation (into its own `AssemblyLoadContext`) 151 | /// and it will use `AssemblyDependencyResolver` on it to provide dependency resolution. 152 | /// Once loaded it will find the specified type and method and return a native function pointer 153 | /// to that method. 154 | /// 155 | /// # Arguments 156 | /// * `assembly_path`: 157 | /// Path to the assembly to load. 158 | /// In case of complex component, this should be the main assembly of the component (the one with the .deps.json next to it). 159 | /// Note that this does not have to be the assembly from which the `type_name` and `method_name` are. 160 | /// * `type_name`: 161 | /// Assembly qualified type name to find 162 | /// * `method_name`: 163 | /// Name of the method on the `type_name` to find. The method must be static and must match the following signature: 164 | /// `public delegate int ComponentEntryPoint(IntPtr args, int sizeBytes);` 165 | pub fn load_assembly_and_get_function_with_default_signature( 166 | &self, 167 | assembly_path: &PdCStr, 168 | type_name: &PdCStr, 169 | method_name: &PdCStr, 170 | ) -> Result { 171 | Self::validate_assembly_path(assembly_path)?; 172 | let function = unsafe { 173 | self.load_assembly_and_get_function_pointer_raw( 174 | assembly_path.as_ptr(), 175 | type_name.as_ptr(), 176 | method_name.as_ptr(), 177 | ptr::null(), 178 | ) 179 | }?; 180 | Ok(ManagedFunction(unsafe { FunctionPtr::from_ptr(function) })) 181 | } 182 | 183 | /// Calling this function will load the specified assembly in isolation (into its own `AssemblyLoadContext`) 184 | /// and it will use `AssemblyDependencyResolver` on it to provide dependency resolution. 185 | /// Once loaded it will find the specified type and method and return a native function pointer 186 | /// to that method. The target method has to be annotated with the [`UnmanagedCallersOnlyAttribute`]. 187 | /// 188 | /// # Arguments 189 | /// * `assembly_path`: 190 | /// Path to the assembly to load. 191 | /// In case of complex component, this should be the main assembly of the component (the one with the .deps.json next to it). 192 | /// Note that this does not have to be the assembly from which the `type_name` and `method_name` are. 193 | /// * `type_name`: 194 | /// Assembly qualified type name to find 195 | /// * `method_name`: 196 | /// Name of the method on the `type_name` to find. The method must be static and must match be annotated with [`\[UnmanagedCallersOnly\]`]. 197 | /// 198 | /// [`UnmanagedCallersOnlyAttribute`]: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute 199 | /// [`UnmanagedCallersOnly`]: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute 200 | #[cfg(feature = "net5_0")] 201 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "net5_0")))] 202 | pub fn load_assembly_and_get_function_with_unmanaged_callers_only( 203 | &self, 204 | assembly_path: &PdCStr, 205 | type_name: &PdCStr, 206 | method_name: &PdCStr, 207 | ) -> Result, GetManagedFunctionError> { 208 | Self::validate_assembly_path(assembly_path)?; 209 | let function = unsafe { 210 | self.load_assembly_and_get_function_pointer_raw( 211 | assembly_path.as_ptr(), 212 | type_name.as_ptr(), 213 | method_name.as_ptr(), 214 | UNMANAGED_CALLERS_ONLY_METHOD, 215 | ) 216 | }?; 217 | Ok(ManagedFunction(unsafe { F::Managed::from_ptr(function) })) 218 | } 219 | 220 | /// Calling this function will find the specified type and method and return a native function pointer to that method. 221 | /// This will **NOT** load the containing assembly. 222 | /// 223 | /// # Arguments 224 | /// * `type_name`: 225 | /// Assembly qualified type name to find 226 | /// * `method_name`: 227 | /// Name of the method on the `type_name` to find. The method must be static and must match the signature of `delegate_type_name`. 228 | /// * `delegate_type_name`: 229 | /// Assembly qualified delegate type name for the method signature. 230 | #[cfg(feature = "net5_0")] 231 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "net5_0")))] 232 | pub fn get_function( 233 | &self, 234 | type_name: &PdCStr, 235 | method_name: &PdCStr, 236 | delegate_type_name: &PdCStr, 237 | ) -> Result, GetManagedFunctionError> { 238 | let function = unsafe { 239 | self.get_function_pointer_raw( 240 | type_name.as_ptr(), 241 | method_name.as_ptr(), 242 | delegate_type_name.as_ptr(), 243 | ) 244 | }?; 245 | Ok(ManagedFunction(unsafe { F::Managed::from_ptr(function) })) 246 | } 247 | 248 | /// Calling this function will find the specified type and method and return a native function pointer to that method. 249 | /// This will **NOT** load the containing assembly. 250 | /// 251 | /// # Arguments 252 | /// * `type_name`: 253 | /// Assembly qualified type name to find 254 | /// * `method_name`: 255 | /// Name of the method on the `type_name` to find. The method must be static and must match the following signature: 256 | /// `public delegate int ComponentEntryPoint(IntPtr args, int sizeBytes);` 257 | #[cfg(feature = "net5_0")] 258 | pub fn get_function_with_default_signature( 259 | &self, 260 | type_name: &PdCStr, 261 | method_name: &PdCStr, 262 | ) -> Result { 263 | let function = unsafe { 264 | self.get_function_pointer_raw(type_name.as_ptr(), method_name.as_ptr(), ptr::null()) 265 | }?; 266 | Ok(ManagedFunction(unsafe { FunctionPtr::from_ptr(function) })) 267 | } 268 | 269 | /// Calling this function will find the specified type and method and return a native function pointer to that method. 270 | /// This will **NOT** load the containing assembly. 271 | /// 272 | /// # Arguments 273 | /// * `type_name`: 274 | /// Assembly qualified type name to find 275 | /// * `method_name`: 276 | /// Name of the method on the `type_name` to find. The method must be static and must match be annotated with [`UnmanagedCallersOnly`]. 277 | /// 278 | /// [`UnmanagedCallersOnlyAttribute`]: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute 279 | /// [`UnmanagedCallersOnly`]: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute 280 | #[cfg(feature = "net5_0")] 281 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "net5_0")))] 282 | pub fn get_function_with_unmanaged_callers_only( 283 | &self, 284 | type_name: &PdCStr, 285 | method_name: &PdCStr, 286 | ) -> Result, GetManagedFunctionError> { 287 | let function = unsafe { 288 | self.get_function_pointer_raw( 289 | type_name.as_ptr(), 290 | method_name.as_ptr(), 291 | UNMANAGED_CALLERS_ONLY_METHOD, 292 | ) 293 | }?; 294 | Ok(ManagedFunction(unsafe { F::Managed::from_ptr(function) })) 295 | } 296 | } 297 | 298 | /// A struct for loading pointers to managed functions for a given [`HostfxrContext`] which automatically loads the 299 | /// assembly from the given path on the first access. 300 | /// 301 | /// [`HostfxrContext`]: super::HostfxrContext 302 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))] 303 | #[derive(Clone)] 304 | pub struct AssemblyDelegateLoader { 305 | loader: DelegateLoader, 306 | assembly_path: PdCString, 307 | } 308 | 309 | impl AssemblyDelegateLoader { 310 | /// Creates a new [`AssemblyDelegateLoader`] wrapping the given [`DelegateLoader`] loading the assembly 311 | /// from the given path on the first access. 312 | pub fn new(loader: DelegateLoader, assembly_path: impl Into) -> Self { 313 | let assembly_path = assembly_path.into(); 314 | Self { 315 | loader, 316 | assembly_path, 317 | } 318 | } 319 | 320 | /// If this is the first loaded function pointer, calling this function will load the specified assembly in 321 | /// isolation (into its own `AssemblyLoadContext`) and it will use `AssemblyDependencyResolver` on it to provide 322 | /// dependency resolution. 323 | /// Otherwise or once loaded it will find the specified type and method and return a native function pointer to that method. 324 | /// Calling this function will find the specified type and method and return a native function pointer to that method. 325 | /// 326 | /// # Arguments 327 | /// * `type_name`: 328 | /// Assembly qualified type name to find 329 | /// * `method_name`: 330 | /// Name of the method on the `type_name` to find. The method must be static and must match the signature of `delegate_type_name`. 331 | /// * `delegate_type_name`: 332 | /// Assembly qualified delegate type name for the method signature. 333 | pub fn get_function( 334 | &self, 335 | type_name: &PdCStr, 336 | method_name: &PdCStr, 337 | delegate_type_name: &PdCStr, 338 | ) -> Result, GetManagedFunctionError> { 339 | self.loader.load_assembly_and_get_function::( 340 | self.assembly_path.as_ref(), 341 | type_name, 342 | method_name, 343 | delegate_type_name, 344 | ) 345 | } 346 | 347 | /// If this is the first loaded function pointer, calling this function will load the specified assembly in 348 | /// isolation (into its own `AssemblyLoadContext`) and it will use `AssemblyDependencyResolver` on it to provide 349 | /// dependency resolution. 350 | /// Otherwise or once loaded it will find the specified type and method and return a native function pointer to that method. 351 | /// Calling this function will find the specified type and method and return a native function pointer to that method. 352 | /// 353 | /// # Arguments 354 | /// * `type_name`: 355 | /// Assembly qualified type name to find 356 | /// * `method_name`: 357 | /// Name of the method on the `type_name` to find. The method must be static and must match the following signature: 358 | /// `public delegate int ComponentEntryPoint(IntPtr args, int sizeBytes);` 359 | pub fn get_function_with_default_signature( 360 | &self, 361 | type_name: &PdCStr, 362 | method_name: &PdCStr, 363 | ) -> Result { 364 | self.loader 365 | .load_assembly_and_get_function_with_default_signature( 366 | self.assembly_path.as_ref(), 367 | type_name, 368 | method_name, 369 | ) 370 | } 371 | 372 | /// If this is the first loaded function pointer, calling this function will load the specified assembly in 373 | /// isolation (into its own `AssemblyLoadContext`) and it will use `AssemblyDependencyResolver` on it to provide 374 | /// dependency resolution. 375 | /// Otherwise or once loaded it will find the specified type and method and return a native function pointer to that method. 376 | /// Calling this function will find the specified type and method and return a native function pointer to that method. 377 | /// 378 | /// # Arguments 379 | /// * `type_name`: 380 | /// Assembly qualified type name to find 381 | /// * `method_name`: 382 | /// Name of the method on the `type_name` to find. The method must be static and must match be annotated with [`UnmanagedCallersOnly`]. 383 | /// 384 | /// [`UnmanagedCallersOnlyAttribute`]: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute 385 | /// [`UnmanagedCallersOnly`]: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute 386 | #[cfg(feature = "net5_0")] 387 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "net5_0")))] 388 | pub fn get_function_with_unmanaged_callers_only( 389 | &self, 390 | type_name: &PdCStr, 391 | method_name: &PdCStr, 392 | ) -> Result, GetManagedFunctionError> { 393 | self.loader 394 | .load_assembly_and_get_function_with_unmanaged_callers_only::( 395 | self.assembly_path.as_ref(), 396 | type_name, 397 | method_name, 398 | ) 399 | } 400 | } 401 | 402 | /// Enum for errors that can occur while loading a managed assembly or managed function pointers. 403 | #[derive(Error, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 404 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))] 405 | pub enum GetManagedFunctionError { 406 | /// An error occured inside the hosting components. 407 | #[error("Error from hosting components: {}.", .0)] 408 | Hosting(#[from] HostingError), 409 | 410 | /// A type with the specified name could not be found or loaded. 411 | #[error("Failed to load the type or method or it has an incompatible signature.")] 412 | TypeOrMethodNotFound, 413 | 414 | /// The specified assembly could not be found. 415 | #[error("The specified assembly could not be found.")] 416 | AssemblyNotFound, 417 | 418 | /// The target method is not annotated with [`UnmanagedCallersOnly`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.unmanagedcallersonlyattribute). 419 | #[error("The target method is not annotated with UnmanagedCallersOnly.")] 420 | MethodNotUnmanagedCallersOnly, 421 | 422 | /// Some other unknown error occured. 423 | #[error("Unknown error code: {}", format!("{:#08X}", .0))] 424 | Other(u32), 425 | } 426 | 427 | impl GetManagedFunctionError { 428 | /// Converts the given staus code to a [`GetManagedFunctionError`]. 429 | pub fn from_status_code(code: i32) -> Result { 430 | let code = code as u32; 431 | match HostingResult::known_from_status_code(code) { 432 | Ok(HostingResult(Ok(code))) => return Ok(code), 433 | Ok(HostingResult(Err(code))) => return Err(GetManagedFunctionError::Hosting(code)), 434 | _ => {} 435 | } 436 | match HResult::try_from(code) { 437 | Ok( 438 | HResult::COR_E_TYPELOAD | HResult::COR_E_MISSINGMETHOD | HResult::COR_E_ARGUMENT, 439 | ) => return Err(Self::TypeOrMethodNotFound), 440 | Ok(HResult::FILE_NOT_FOUND) => return Err(Self::AssemblyNotFound), 441 | Ok(HResult::COR_E_INVALIDOPERATION) => return Err(Self::MethodNotUnmanagedCallersOnly), 442 | _ => {} 443 | } 444 | Err(Self::Other(code)) 445 | } 446 | } 447 | 448 | #[repr(u32)] 449 | #[non_exhaustive] 450 | #[derive(TryFromPrimitive, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 451 | #[allow(non_camel_case_types)] 452 | #[rustfmt::skip] 453 | enum HResult { 454 | E_POINTER = 0x8000_4003, // System.ArgumentNullException 455 | COR_E_ARGUMENTOUTOFRANGE = 0x8013_1502, // System.ArgumentOutOfRangeException (reserved was not 0) 456 | COR_E_TYPELOAD = 0x8013_1522, // invalid type 457 | COR_E_MISSINGMETHOD = 0x8013_1513, // invalid method 458 | /*COR_E_*/FILE_NOT_FOUND = 0x8007_0002, // assembly with specified name not found (from type name) 459 | COR_E_ARGUMENT = 0x8007_0057, // invalid method signature or method not found 460 | COR_E_INVALIDOPERATION = 0x8013_1509, // invalid assembly path or not unmanaged, 461 | } 462 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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::{ 13 | cell::RefCell, 14 | io, 15 | mem::MaybeUninit, 16 | path::{Path, PathBuf}, 17 | ptr, slice, 18 | }; 19 | 20 | use super::UNSUPPORTED_HOST_VERSION_ERROR_CODE; 21 | 22 | impl Hostfxr { 23 | /// Run an application. 24 | /// 25 | /// # Arguments 26 | /// * `app_path` 27 | /// path to the application to run 28 | /// * `args` 29 | /// command-line arguments 30 | /// * `host_path` 31 | /// path to the host application 32 | /// * `dotnet_root` 33 | /// path to the .NET Core installation root 34 | /// 35 | /// This function does not return until the application completes execution. 36 | /// It will shutdown CoreCLR after the application executes. 37 | /// If the application is successfully executed, this value will return the exit code of the application. 38 | /// Otherwise, it will return an error code indicating the failure. 39 | #[cfg_attr( 40 | feature = "netcore3_0", 41 | deprecated(note = "Use `HostfxrContext::run_app` instead"), 42 | allow(deprecated) 43 | )] 44 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore2_1")))] 45 | pub fn run_app_with_args_and_startup_info<'a, A: AsRef>( 46 | &'a self, 47 | app_path: &'a PdCStr, 48 | args: impl IntoIterator, 49 | host_path: &PdCStr, 50 | dotnet_root: &PdCStr, 51 | ) -> io::Result { 52 | let args = [&self.dotnet_exe, app_path] 53 | .into_iter() 54 | .chain(args) 55 | .map(|s| s.as_ptr()) 56 | .collect::>(); 57 | 58 | let result = unsafe { 59 | self.lib.hostfxr_main_startupinfo( 60 | args.len().try_into().unwrap(), 61 | args.as_ptr(), 62 | host_path.as_ptr(), 63 | dotnet_root.as_ptr(), 64 | app_path.as_ptr(), 65 | ) 66 | } 67 | .unwrap_or(UNSUPPORTED_HOST_VERSION_ERROR_CODE); 68 | 69 | Ok(AppOrHostingResult::from(result)) 70 | } 71 | 72 | /// Determine the directory location of the SDK, accounting for `global.json` and multi-level lookup policy. 73 | /// 74 | /// # Arguments 75 | /// * `sdk_dir` - main directory where SDKs are located in `sdk\[version]` sub-folders. 76 | /// * `working_dir` - directory where the search for `global.json` will start and proceed upwards 77 | /// * `allow_prerelease` - allow resolution to return a pre-release SDK version 78 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore2_1")))] 79 | pub fn resolve_sdk( 80 | &self, 81 | sdk_dir: &PdCStr, 82 | working_dir: &PdCStr, 83 | allow_prerelease: bool, 84 | ) -> Result { 85 | let flags = if allow_prerelease { 86 | hostfxr_resolve_sdk2_flags_t::none 87 | } else { 88 | hostfxr_resolve_sdk2_flags_t::disallow_prerelease 89 | }; 90 | let result = unsafe { 91 | self.lib.hostfxr_resolve_sdk2( 92 | sdk_dir.as_ptr(), 93 | working_dir.as_ptr(), 94 | flags, 95 | resolve_sdk2_callback, 96 | ) 97 | } 98 | .unwrap_or(UNSUPPORTED_HOST_VERSION_ERROR_CODE); 99 | HostingResult::from(result).into_result()?; 100 | 101 | let sdk_path = RESOLVE_SDK2_DATA 102 | .with(|sdk| sdk.borrow_mut().take()) 103 | .unwrap(); 104 | Ok(sdk_path) 105 | } 106 | 107 | /// Get the list of all available SDKs ordered by ascending version. 108 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore2_1")))] 109 | #[must_use] 110 | pub fn get_available_sdks(&self) -> Vec { 111 | self.get_available_sdks_raw(None) 112 | } 113 | 114 | /// Get the list of all available SDKs ordered by ascending version, based on the provided `dotnet` executable. 115 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore2_1")))] 116 | #[must_use] 117 | pub fn get_available_sdks_with_dotnet_path(&self, dotnet_path: &PdCStr) -> Vec { 118 | self.get_available_sdks_raw(Some(dotnet_path)) 119 | } 120 | 121 | #[must_use] 122 | fn get_available_sdks_raw(&self, dotnet_path: Option<&PdCStr>) -> Vec { 123 | let dotnet_path = dotnet_path.map_or_else(ptr::null, |s| s.as_ptr()); 124 | unsafe { 125 | self.lib 126 | .hostfxr_get_available_sdks(dotnet_path, get_available_sdks_callback) 127 | }; 128 | GET_AVAILABLE_SDKS_DATA 129 | .with(|sdks| sdks.borrow_mut().take()) 130 | .unwrap() 131 | } 132 | 133 | /// Get the native search directories of the runtime based upon the specified app. 134 | /// 135 | /// # Arguments 136 | /// * `app_path` - path to application 137 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore2_1")))] 138 | pub fn get_native_search_directories( 139 | &self, 140 | app_path: &PdCStr, 141 | ) -> Result, HostingError> { 142 | let mut buffer = Vec::::new(); 143 | let args = [self.dotnet_exe.as_ptr(), app_path.as_ptr()]; 144 | 145 | let mut required_buffer_size = MaybeUninit::uninit(); 146 | unsafe { 147 | self.lib.hostfxr_get_native_search_directories( 148 | args.len().try_into().unwrap(), 149 | args.as_ptr(), 150 | buffer.as_mut_ptr().cast(), 151 | 0, 152 | required_buffer_size.as_mut_ptr(), 153 | ) 154 | }; 155 | let mut required_buffer_size = unsafe { required_buffer_size.assume_init() }; 156 | 157 | buffer.reserve(required_buffer_size.try_into().unwrap()); 158 | let result = unsafe { 159 | self.lib.hostfxr_get_native_search_directories( 160 | args.len().try_into().unwrap(), 161 | args.as_ptr(), 162 | buffer.spare_capacity_mut().as_mut_ptr().cast(), 163 | buffer.spare_capacity_mut().len().try_into().unwrap(), 164 | &raw mut required_buffer_size, 165 | ) 166 | } 167 | .unwrap_or(UNSUPPORTED_HOST_VERSION_ERROR_CODE); 168 | HostingResult::from(result).into_result()?; 169 | unsafe { buffer.set_len(required_buffer_size.try_into().unwrap()) }; 170 | 171 | let mut directories = Vec::new(); 172 | let last_start = 0; 173 | for i in 0..buffer.len() { 174 | if buffer[i] == PATH_LIST_SEPARATOR as PdUChar || buffer[i] == 0 { 175 | buffer[i] = 0; 176 | let directory = PdCStr::from_slice_with_nul(&buffer[last_start..=i]).unwrap(); 177 | directories.push(PathBuf::from(directory.to_os_string())); 178 | break; 179 | } 180 | } 181 | 182 | Ok(directories) 183 | } 184 | } 185 | 186 | thread_local! { 187 | static GET_AVAILABLE_SDKS_DATA: RefCell>> = const { RefCell::new(None) }; 188 | static RESOLVE_SDK2_DATA: RefCell> = const { RefCell::new(None) }; 189 | } 190 | 191 | extern "C" fn get_available_sdks_callback(sdk_count: i32, sdks_ptr: *const *const char_t) { 192 | GET_AVAILABLE_SDKS_DATA.with(|sdks| { 193 | let mut sdks_opt = sdks.borrow_mut(); 194 | let sdks = sdks_opt.get_or_insert_with(Vec::new); 195 | 196 | let raw_sdks = unsafe { slice::from_raw_parts(sdks_ptr, sdk_count as usize) }; 197 | sdks.extend(raw_sdks.iter().copied().map(|raw_sdk| { 198 | unsafe { PdCStr::from_str_ptr(raw_sdk) } 199 | .to_os_string() 200 | .into() 201 | })); 202 | }); 203 | } 204 | 205 | extern "C" fn resolve_sdk2_callback(key: hostfxr_resolve_sdk2_result_key_t, value: *const char_t) { 206 | RESOLVE_SDK2_DATA.with(|sdks| { 207 | let path = unsafe { PdCStr::from_str_ptr(value) }.to_os_string().into(); 208 | *sdks.borrow_mut() = Some(match key { 209 | hostfxr_resolve_sdk2_result_key_t::resolved_sdk_dir => { 210 | ResolveSdkResult::ResolvedSdkDirectory(path) 211 | } 212 | hostfxr_resolve_sdk2_result_key_t::global_json_path => { 213 | ResolveSdkResult::GlobalJsonPath(path) 214 | } 215 | }); 216 | }); 217 | } 218 | 219 | /// Result of [`Hostfxr::resolve_sdk`]. 220 | #[derive(Debug, Clone, PartialEq, Eq)] 221 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore2_1")))] 222 | #[must_use] 223 | pub enum ResolveSdkResult { 224 | /// `global.json` was not present or did not impact the resolved SDK location. 225 | ResolvedSdkDirectory(PathBuf), 226 | /// `global.json` was used during resolution. 227 | GlobalJsonPath(PathBuf), 228 | } 229 | 230 | impl ResolveSdkResult { 231 | /// Returns the path to the resolved SDK directory. 232 | #[must_use] 233 | pub fn into_path(self) -> PathBuf { 234 | match self { 235 | ResolveSdkResult::GlobalJsonPath(path) 236 | | ResolveSdkResult::ResolvedSdkDirectory(path) => path, 237 | } 238 | } 239 | 240 | /// Returns the path to the resolved SDK directory. 241 | #[must_use] 242 | pub fn path(&self) -> &Path { 243 | match self { 244 | ResolveSdkResult::GlobalJsonPath(path) 245 | | ResolveSdkResult::ResolvedSdkDirectory(path) => path, 246 | } 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/hostfxr/library3_0.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | bindings::hostfxr::{hostfxr_handle, hostfxr_initialize_parameters}, 3 | error::{HostingError, HostingResult, HostingSuccess}, 4 | hostfxr::{ 5 | Hostfxr, HostfxrContext, HostfxrHandle, InitializedForCommandLine, 6 | InitializedForRuntimeConfig, 7 | }, 8 | pdcstring::{PdCStr, PdChar}, 9 | }; 10 | use std::{iter, mem::MaybeUninit, ptr}; 11 | 12 | use super::UNSUPPORTED_HOST_VERSION_ERROR_CODE; 13 | 14 | impl Hostfxr { 15 | /// Initializes the hosting components for a dotnet command line running an application 16 | /// 17 | /// Like all the other `initialize` functions, this function will 18 | /// * Process the `.runtimeconfig.json` 19 | /// * Resolve framework references and find actual frameworks 20 | /// * Find the root framework (`Microsoft.NETCore.App`) and load the hostpolicy from it 21 | /// * The hostpolicy will then process all relevant `.deps.json` files and produce the list of assemblies, native search paths and other artifacts needed to initialize the runtime. 22 | /// 23 | /// The functions will **NOT** load the `CoreCLR` runtime. They just prepare everything to the point where it can be loaded. 24 | /// 25 | /// # Arguments 26 | /// * `app_path`: 27 | /// The path to the target application. 28 | /// 29 | /// # Remarks 30 | /// This function parses the specified command-line arguments to determine the application to run. It will 31 | /// then find the corresponding `.runtimeconfig.json` and `.deps.json` with which to resolve frameworks and 32 | /// dependencies and prepare everything needed to load the runtime. 33 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))] 34 | pub fn initialize_for_dotnet_command_line( 35 | &self, 36 | app_path: impl AsRef, 37 | ) -> Result, HostingError> { 38 | self.initialize_for_dotnet_command_line_with_args(app_path, iter::empty::<&PdCStr>()) 39 | } 40 | 41 | /// Initializes the hosting components for a dotnet command line running an application 42 | /// 43 | /// Like all the other `initialize` functions, this function will 44 | /// * Process the `.runtimeconfig.json` 45 | /// * Resolve framework references and find actual frameworks 46 | /// * Find the root framework (`Microsoft.NETCore.App`) and load the hostpolicy from it 47 | /// * The hostpolicy will then process all relevant `.deps.json` files and produce the list of assemblies, native search paths and other artifacts needed to initialize the runtime. 48 | /// 49 | /// The functions will **NOT** load the `CoreCLR` runtime. They just prepare everything to the point where it can be loaded. 50 | /// 51 | /// # Arguments 52 | /// * `app_path`: 53 | /// The path to the target application. 54 | /// * `host_path`: 55 | /// Path to the native host (typically the `.exe`). 56 | /// This value is not used for anything by the hosting components. 57 | /// It's just passed to the `CoreCLR` as the path to the executable. 58 | /// It can point to a file which is not executable itself, if such file doesn't exist (for example in COM activation scenarios this points to the `comhost.dll`). 59 | /// This is used by PAL to initialize internal command line structures, process name and so on. 60 | /// 61 | /// # Remarks 62 | /// This function parses the specified command-line arguments to determine the application to run. It will 63 | /// then find the corresponding `.runtimeconfig.json` and `.deps.json` with which to resolve frameworks and 64 | /// dependencies and prepare everything needed to load the runtime. 65 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))] 66 | pub fn initialize_for_dotnet_command_line_with_host_path( 67 | &self, 68 | app_path: impl AsRef, 69 | host_path: impl AsRef, 70 | ) -> Result, HostingError> { 71 | self.initialize_for_dotnet_command_line_with_args_and_host_path( 72 | app_path, 73 | iter::empty::<&PdCStr>(), 74 | host_path, 75 | ) 76 | } 77 | 78 | /// Initializes the hosting components for a dotnet command line running an application 79 | /// 80 | /// Like all the other `initialize` functions, this function will 81 | /// * Process the `.runtimeconfig.json` 82 | /// * Resolve framework references and find actual frameworks 83 | /// * Find the root framework (`Microsoft.NETCore.App`) and load the hostpolicy from it 84 | /// * The hostpolicy will then process all relevant `.deps.json` files and produce the list of assemblies, native search paths and other artifacts needed to initialize the runtime. 85 | /// 86 | /// The functions will **NOT** load the `CoreCLR` runtime. They just prepare everything to the point where it can be loaded. 87 | /// 88 | /// # Arguments 89 | /// * `app_path`: 90 | /// The path to the target application. 91 | /// * `dotnet_root`: 92 | /// Path to the root of the .NET Core installation in use. 93 | /// This typically points to the install location from which the hostfxr has been loaded. 94 | /// For example on Windows this would typically point to `C:\Program Files\dotnet`. 95 | /// The path is used to search for shared frameworks and potentially SDKs. 96 | /// 97 | /// # Remarks 98 | /// This function parses the specified command-line arguments to determine the application to run. It will 99 | /// then find the corresponding `.runtimeconfig.json` and `.deps.json` with which to resolve frameworks and 100 | /// dependencies and prepare everything needed to load the runtime. 101 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))] 102 | pub fn initialize_for_dotnet_command_line_with_dotnet_root( 103 | &self, 104 | app_path: impl AsRef, 105 | dotnet_root: impl AsRef, 106 | ) -> Result, HostingError> { 107 | self.initialize_for_dotnet_command_line_with_args_and_dotnet_root( 108 | app_path, 109 | iter::empty::<&PdCStr>(), 110 | dotnet_root, 111 | ) 112 | } 113 | 114 | /// Initializes the hosting components for a dotnet command line running an application 115 | /// 116 | /// Like all the other `initialize` functions, this function will 117 | /// * Process the `.runtimeconfig.json` 118 | /// * Resolve framework references and find actual frameworks 119 | /// * Find the root framework (`Microsoft.NETCore.App`) and load the hostpolicy from it 120 | /// * The hostpolicy will then process all relevant `.deps.json` files and produce the list of assemblies, native search paths and other artifacts needed to initialize the runtime. 121 | /// 122 | /// The functions will **NOT** load the `CoreCLR` runtime. They just prepare everything to the point where it can be loaded. 123 | /// 124 | /// # Arguments 125 | /// * `app_path`: 126 | /// The path to the target application. 127 | /// * `args`: 128 | /// The command line arguments for the managed application. 129 | /// 130 | /// # Remarks 131 | /// This function parses the specified command-line arguments to determine the application to run. It will 132 | /// then find the corresponding `.runtimeconfig.json` and `.deps.json` with which to resolve frameworks and 133 | /// dependencies and prepare everything needed to load the runtime. 134 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))] 135 | pub fn initialize_for_dotnet_command_line_with_args( 136 | &self, 137 | app_path: impl AsRef, 138 | args: impl Iterator>, 139 | ) -> Result, HostingError> { 140 | unsafe { 141 | self.initialize_for_dotnet_command_line_with_parameters(app_path, args, ptr::null()) 142 | } 143 | } 144 | 145 | /// Initializes the hosting components for a dotnet command line running an application 146 | /// 147 | /// Like all the other `initialize` functions, this function will 148 | /// * Process the `.runtimeconfig.json` 149 | /// * Resolve framework references and find actual frameworks 150 | /// * Find the root framework (`Microsoft.NETCore.App`) and load the hostpolicy from it 151 | /// * The hostpolicy will then process all relevant `.deps.json` files and produce the list of assemblies, native search paths and other artifacts needed to initialize the runtime. 152 | /// 153 | /// The functions will **NOT** load the `CoreCLR` runtime. They just prepare everything to the point where it can be loaded. 154 | /// 155 | /// # Arguments 156 | /// * `app_path`: 157 | /// The path to the target application. 158 | /// * `args`: 159 | /// The command line arguments for the managed application. 160 | /// * `host_path`: 161 | /// Path to the native host (typically the `.exe`). 162 | /// This value is not used for anything by the hosting components. 163 | /// It's just passed to the `CoreCLR` as the path to the executable. 164 | /// It can point to a file which is not executable itself, if such file doesn't exist (for example in COM activation scenarios this points to the `comhost.dll`). 165 | /// This is used by PAL to initialize internal command line structures, process name and so on. 166 | /// 167 | /// # Remarks 168 | /// This function parses the specified command-line arguments to determine the application to run. It will 169 | /// then find the corresponding `.runtimeconfig.json` and `.deps.json` with which to resolve frameworks and 170 | /// dependencies and prepare everything needed to load the runtime. 171 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))] 172 | pub fn initialize_for_dotnet_command_line_with_args_and_host_path( 173 | &self, 174 | app_path: impl AsRef, 175 | args: impl Iterator>, 176 | host_path: impl AsRef, 177 | ) -> Result, HostingError> { 178 | let parameters = hostfxr_initialize_parameters::with_host_path(host_path.as_ref().as_ptr()); 179 | unsafe { 180 | self.initialize_for_dotnet_command_line_with_parameters( 181 | app_path, 182 | args, 183 | &raw const parameters, 184 | ) 185 | } 186 | } 187 | 188 | /// Initializes the hosting components for a dotnet command line running an application 189 | /// 190 | /// Like all the other `initialize` functions, this function will 191 | /// * Process the `.runtimeconfig.json` 192 | /// * Resolve framework references and find actual frameworks 193 | /// * Find the root framework (`Microsoft.NETCore.App`) and load the hostpolicy from it 194 | /// * The hostpolicy will then process all relevant `.deps.json` files and produce the list of assemblies, native search paths and other artifacts needed to initialize the runtime. 195 | /// 196 | /// The functions will **NOT** load the `CoreCLR` runtime. They just prepare everything to the point where it can be loaded. 197 | /// 198 | /// # Arguments 199 | /// * `app_path`: 200 | /// The path to the target application. 201 | /// * `args`: 202 | /// The command line arguments for the managed application. 203 | /// * `dotnet_root`: 204 | /// Path to the root of the .NET Core installation in use. 205 | /// This typically points to the install location from which the hostfxr has been loaded. 206 | /// For example on Windows this would typically point to `C:\Program Files\dotnet`. 207 | /// The path is used to search for shared frameworks and potentially SDKs. 208 | /// 209 | /// # Remarks 210 | /// This function parses the specified command-line arguments to determine the application to run. It will 211 | /// then find the corresponding `.runtimeconfig.json` and `.deps.json` with which to resolve frameworks and 212 | /// dependencies and prepare everything needed to load the runtime. 213 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))] 214 | pub fn initialize_for_dotnet_command_line_with_args_and_dotnet_root( 215 | &self, 216 | app_path: impl AsRef, 217 | args: impl Iterator>, 218 | dotnet_root: impl AsRef, 219 | ) -> Result, HostingError> { 220 | let parameters = 221 | hostfxr_initialize_parameters::with_dotnet_root(dotnet_root.as_ref().as_ptr()); 222 | unsafe { 223 | self.initialize_for_dotnet_command_line_with_parameters( 224 | app_path, 225 | args, 226 | &raw const parameters, 227 | ) 228 | } 229 | } 230 | 231 | unsafe fn initialize_for_dotnet_command_line_with_parameters( 232 | &self, 233 | app_path: impl AsRef, 234 | args: impl Iterator>, 235 | parameters: *const hostfxr_initialize_parameters, 236 | ) -> Result, HostingError> { 237 | let mut hostfxr_handle = MaybeUninit::::uninit(); 238 | 239 | let app_path = app_path.as_ref().as_ptr(); 240 | let args = args.map(|arg| arg.as_ref().as_ptr()); 241 | let app_path_and_args = iter::once(app_path).chain(args).collect::>(); 242 | let result = unsafe { 243 | self.lib.hostfxr_initialize_for_dotnet_command_line( 244 | app_path_and_args.len().try_into().unwrap(), 245 | app_path_and_args.as_ptr(), 246 | parameters, 247 | hostfxr_handle.as_mut_ptr(), 248 | ) 249 | } 250 | .unwrap_or(UNSUPPORTED_HOST_VERSION_ERROR_CODE); 251 | 252 | let success_code = HostingResult::from(result).into_result()?; 253 | 254 | let is_primary = matches!(success_code, HostingSuccess::Success); 255 | 256 | Ok(unsafe { 257 | HostfxrContext::from_handle( 258 | HostfxrHandle::new_unchecked(hostfxr_handle.assume_init()), 259 | self.clone(), 260 | is_primary, 261 | ) 262 | }) 263 | } 264 | 265 | /// This function loads the specified `.runtimeconfig.json`, resolve all frameworks, resolve all the assets from those frameworks and 266 | /// then prepare runtime initialization where the TPA contains only frameworks. 267 | /// Note that this case does **NOT** consume any `.deps.json` from the app/component (only processes the framework's `.deps.json`). 268 | /// 269 | /// Like all the other `initialize` functions, this function will 270 | /// * Process the `.runtimeconfig.json` 271 | /// * Resolve framework references and find actual frameworks 272 | /// * Find the root framework (`Microsoft.NETCore.App`) and load the hostpolicy from it 273 | /// * The hostpolicy will then process all relevant `.deps.json` files and produce the list of assemblies, native search paths and other artifacts needed to initialize the runtime. 274 | /// 275 | /// The functions will **NOT** load the `CoreCLR` runtime. They just prepare everything to the point where it can be loaded. 276 | /// 277 | /// # Arguments 278 | /// * `runtime_config_path`: 279 | /// Path to the `.runtimeconfig.json` file to process. 280 | /// Unlike with [`initialize_for_dotnet_command_line`], any `.deps.json` from the app/component will not be processed by the hosting layers. 281 | /// 282 | /// [`initialize_for_dotnet_command_line`]: Hostfxr::initialize_for_dotnet_command_line 283 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))] 284 | pub fn initialize_for_runtime_config( 285 | &self, 286 | runtime_config_path: impl AsRef, 287 | ) -> Result, HostingError> { 288 | unsafe { 289 | self.initialize_for_runtime_config_with_parameters(runtime_config_path, ptr::null()) 290 | } 291 | } 292 | 293 | /// This function loads the specified `.runtimeconfig.json`, resolve all frameworks, resolve all the assets from those frameworks and 294 | /// then prepare runtime initialization where the TPA contains only frameworks. 295 | /// Note that this case does **NOT** consume any `.deps.json` from the app/component (only processes the framework's `.deps.json`). 296 | /// 297 | /// Like all the other `initialize` functions, this function will 298 | /// * Process the `.runtimeconfig.json` 299 | /// * Resolve framework references and find actual frameworks 300 | /// * Find the root framework (`Microsoft.NETCore.App`) and load the hostpolicy from it 301 | /// * The hostpolicy will then process all relevant `.deps.json` files and produce the list of assemblies, native search paths and other artifacts needed to initialize the runtime. 302 | /// 303 | /// The functions will **NOT** load the `CoreCLR` runtime. They just prepare everything to the point where it can be loaded. 304 | /// 305 | /// # Arguments 306 | /// * `runtime_config_path`: 307 | /// Path to the `.runtimeconfig.json` file to process. 308 | /// Unlike with [`initialize_for_dotnet_command_line`], any `.deps.json` from the app/component will not be processed by the hosting layers. 309 | /// * `host_path`: 310 | /// Path to the native host (typically the `.exe`). 311 | /// This value is not used for anything by the hosting components. 312 | /// It's just passed to the `CoreCLR` as the path to the executable. 313 | /// It can point to a file which is not executable itself, if such file doesn't exist (for example in COM activation scenarios this points to the `comhost.dll`). 314 | /// This is used by PAL to initialize internal command line structures, process name and so on. 315 | /// 316 | /// [`initialize_for_dotnet_command_line`]: Hostfxr::initialize_for_dotnet_command_line 317 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))] 318 | pub fn initialize_for_runtime_config_with_host_path( 319 | &self, 320 | runtime_config_path: impl AsRef, 321 | host_path: impl AsRef, 322 | ) -> Result, HostingError> { 323 | let parameters = hostfxr_initialize_parameters::with_host_path(host_path.as_ref().as_ptr()); 324 | unsafe { 325 | self.initialize_for_runtime_config_with_parameters( 326 | runtime_config_path, 327 | &raw const parameters, 328 | ) 329 | } 330 | } 331 | /// This function loads the specified `.runtimeconfig.json`, resolve all frameworks, resolve all the assets from those frameworks and 332 | /// then prepare runtime initialization where the TPA contains only frameworks. 333 | /// Note that this case does **NOT** consume any `.deps.json` from the app/component (only processes the framework's `.deps.json`). 334 | /// 335 | /// Like all the other `initialize` functions, this function will 336 | /// * Process the `.runtimeconfig.json` 337 | /// * Resolve framework references and find actual frameworks 338 | /// * Find the root framework (`Microsoft.NETCore.App`) and load the hostpolicy from it 339 | /// * The hostpolicy will then process all relevant `.deps.json` files and produce the list of assemblies, native search paths and other artifacts needed to initialize the runtime. 340 | /// 341 | /// The functions will **NOT** load the `CoreCLR` runtime. They just prepare everything to the point where it can be loaded. 342 | /// 343 | /// # Arguments 344 | /// * `runtime_config_path`: 345 | /// Path to the `.runtimeconfig.json` file to process. 346 | /// Unlike with [`initialize_for_dotnet_command_line`], any `.deps.json` from the app/component will not be processed by the hosting layers. 347 | /// * `dotnet_root`: 348 | /// Path to the root of the .NET Core installation in use. 349 | /// This typically points to the install location from which the hostfxr has been loaded. 350 | /// For example on Windows this would typically point to `C:\Program Files\dotnet`. 351 | /// The path is used to search for shared frameworks and potentially SDKs. 352 | /// 353 | /// [`initialize_for_dotnet_command_line`]: Hostfxr::initialize_for_dotnet_command_line 354 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))] 355 | pub fn initialize_for_runtime_config_with_dotnet_root( 356 | &self, 357 | runtime_config_path: impl AsRef, 358 | dotnet_root: impl AsRef, 359 | ) -> Result, HostingError> { 360 | let parameters = 361 | hostfxr_initialize_parameters::with_dotnet_root(dotnet_root.as_ref().as_ptr()); 362 | unsafe { 363 | self.initialize_for_runtime_config_with_parameters( 364 | runtime_config_path, 365 | &raw const parameters, 366 | ) 367 | } 368 | } 369 | 370 | unsafe fn initialize_for_runtime_config_with_parameters( 371 | &self, 372 | runtime_config_path: impl AsRef, 373 | parameters: *const hostfxr_initialize_parameters, 374 | ) -> Result, HostingError> { 375 | let mut hostfxr_handle = MaybeUninit::uninit(); 376 | 377 | let result = unsafe { 378 | self.lib.hostfxr_initialize_for_runtime_config( 379 | runtime_config_path.as_ref().as_ptr(), 380 | parameters, 381 | hostfxr_handle.as_mut_ptr(), 382 | ) 383 | } 384 | .unwrap_or(UNSUPPORTED_HOST_VERSION_ERROR_CODE); 385 | 386 | let success_code = HostingResult::from(result).into_result()?; 387 | 388 | let is_primary = matches!(success_code, HostingSuccess::Success); 389 | 390 | Ok(unsafe { 391 | HostfxrContext::from_handle( 392 | HostfxrHandle::new_unchecked(hostfxr_handle.assume_init()), 393 | self.clone(), 394 | is_primary, 395 | ) 396 | }) 397 | } 398 | 399 | /// Sets a callback which is to be used to write errors to. 400 | /// 401 | /// # Arguments 402 | /// * `error_writer` 403 | /// A callback function which will be invoked every time an error is to be reported. 404 | /// Or [`None`] to unregister previously registered callbacks and return to the default behavior. 405 | /// 406 | /// # Remarks 407 | /// The error writer is registered per-thread, so the registration is thread-local. On each thread 408 | /// only one callback can be registered. Subsequent registrations overwrite the previous ones. 409 | /// 410 | /// By default no callback is registered in which case the errors are written to stderr. 411 | /// 412 | /// Each call to the error writer is sort of like writing a single line (the EOL character is omitted). 413 | /// Multiple calls to the error writer may occure for one failure. 414 | /// 415 | /// If the hostfxr invokes functions in hostpolicy as part of its operation, the error writer 416 | /// will be propagated to hostpolicy for the duration of the call. This means that errors from 417 | /// both hostfxr and hostpolicy will be reporter through the same error writer. 418 | #[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))] 419 | pub fn set_error_writer(&self, error_writer: Option) { 420 | let new_raw_error_writer = error_writer 421 | .as_ref() 422 | .map(|_| error_writer_trampoline as hostfxr_sys::hostfxr_error_writer_fn); 423 | unsafe { self.lib.hostfxr_set_error_writer(new_raw_error_writer) }; 424 | 425 | CURRENT_ERROR_WRITER.with(|current_writer| { 426 | *current_writer.borrow_mut() = error_writer; 427 | }); 428 | } 429 | } 430 | 431 | type ErrorWriter = Box; 432 | 433 | thread_local! { 434 | static CURRENT_ERROR_WRITER: std::cell::RefCell> = std::cell::RefCell::new(None); 435 | } 436 | 437 | extern "C" fn error_writer_trampoline(raw_error: *const PdChar) { 438 | CURRENT_ERROR_WRITER.with(|writer_holder| { 439 | if let Some(writer) = writer_holder.borrow_mut().as_mut() { 440 | let error_message = unsafe { PdCStr::from_str_ptr(raw_error) }; 441 | writer(error_message); 442 | } 443 | }); 444 | } 445 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/hostfxr/managed_function.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Debug, ops::Deref}; 2 | 3 | /// A wrapper around a managed function pointer. 4 | pub struct ManagedFunction(pub(crate) F); 5 | 6 | impl Debug for ManagedFunction { 7 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 8 | f.debug_struct("ManagedFunction") 9 | .field("ptr", &self.0.as_ptr()) 10 | .field("sig", &std::any::type_name::()) 11 | .finish() 12 | } 13 | } 14 | 15 | impl Deref for ManagedFunction { 16 | type Target = F; 17 | 18 | fn deref(&self) -> &Self::Target { 19 | &self.0 20 | } 21 | } 22 | 23 | ffi_opaque::opaque! { 24 | /// A struct representing an opaque function. 25 | pub struct OpaqueFunction; 26 | } 27 | 28 | /// Type alias for a raw untyped function pointer. 29 | pub type RawFunctionPtr = *const OpaqueFunction; 30 | 31 | /// Trait representing a function pointer. 32 | /// 33 | /// # Safety 34 | /// This trait should only be implemented for function pointers and the associated types and constants have to match the function pointer type. 35 | pub unsafe trait FunctionPtr: Sized + Copy + Send + Sync + 'static { 36 | /// The argument types as a tuple. 37 | type Args; 38 | 39 | /// The return type. 40 | type Output; 41 | 42 | /// The function's arity (number of arguments). 43 | const ARITY: usize; 44 | 45 | /// The `extern "system"` version of this function pointer. 46 | type Managed: ManagedFunctionPtr; 47 | 48 | /// Constructs a [`FunctionPtr`] from an untyped function pointer. 49 | /// 50 | /// # Safety 51 | /// This function is unsafe because it can not check if the argument points to a function 52 | /// of the correct type. 53 | unsafe fn from_ptr(ptr: RawFunctionPtr) -> Self; 54 | 55 | /// Returns a untyped function pointer for this function. 56 | fn as_ptr(&self) -> RawFunctionPtr; 57 | } 58 | 59 | /// Trait representing a managed function pointer. 60 | /// 61 | /// # Safety 62 | /// This trait should only be implemented for `extern "system"` function pointers and the associated types and constants have to match the function pointer type. 63 | pub unsafe trait ManagedFunctionPtr: FunctionPtr { 64 | /// The argument types as a tuple. 65 | type Args; 66 | 67 | /// The return type. 68 | type Output; 69 | 70 | /// The function's arity (number of arguments). 71 | const ARITY: usize; 72 | } 73 | 74 | macro_rules! impl_fn { 75 | (@recurse () ($($nm:ident : $ty:ident),*)) => { 76 | impl_fn!(@impl_all ($($nm : $ty),*)); 77 | }; 78 | (@recurse ($hd_nm:ident : $hd_ty:ident $(, $tl_nm:ident : $tl_ty:ident)*) ($($nm:ident : $ty:ident),*)) => { 79 | impl_fn!(@impl_all ($($nm : $ty),*)); 80 | impl_fn!(@recurse ($($tl_nm : $tl_ty),*) ($($nm : $ty,)* $hd_nm : $hd_ty)); 81 | }; 82 | 83 | (@impl_all ($($nm:ident : $ty:ident),*)) => { 84 | impl_fn!(@impl_u_and_s ($($nm : $ty),*) fn($($ty),*) -> Ret); 85 | }; 86 | 87 | (@impl_u_and_s ($($nm:ident : $ty:ident),*) fn($($param_ty:ident),*) -> $ret:ty) => { 88 | impl_fn!(@impl_core ($($nm : $ty),*) (fn($($param_ty),*) -> $ret) (extern "system" fn($($param_ty),*) -> $ret)); 89 | impl_fn!(@impl_core ($($nm : $ty),*) (unsafe fn($($param_ty),*) -> $ret) (unsafe extern "system" fn($($param_ty),*) -> $ret)); 90 | }; 91 | 92 | (@impl_core ($($nm:ident : $ty:ident),*) ($fn_type:ty) ($managed_fn_type:ty)) => { 93 | unsafe impl crate::hostfxr::FunctionPtr for $fn_type { 94 | type Args = ($($ty,)*); 95 | type Output = Ret; 96 | type Managed = $managed_fn_type; 97 | 98 | const ARITY: ::core::primitive::usize = impl_fn!(@count ($($ty)*)); 99 | 100 | unsafe fn from_ptr(ptr: crate::hostfxr::RawFunctionPtr) -> Self { 101 | ::core::assert!(!ptr.is_null()); 102 | unsafe { ::core::mem::transmute(ptr) } 103 | } 104 | 105 | fn as_ptr(&self) -> crate::hostfxr::RawFunctionPtr { 106 | *self as crate::hostfxr::RawFunctionPtr 107 | } 108 | } 109 | 110 | unsafe impl crate::hostfxr::FunctionPtr for $managed_fn_type { 111 | type Args = ($($ty,)*); 112 | type Output = Ret; 113 | type Managed = $managed_fn_type; 114 | 115 | const ARITY: ::core::primitive::usize = impl_fn!(@count ($($ty)*)); 116 | 117 | unsafe fn from_ptr(ptr: crate::hostfxr::RawFunctionPtr) -> Self { 118 | ::core::assert!(!ptr.is_null()); 119 | unsafe { ::core::mem::transmute(ptr) } 120 | } 121 | 122 | fn as_ptr(&self) -> crate::hostfxr::RawFunctionPtr { 123 | *self as crate::hostfxr::RawFunctionPtr 124 | } 125 | } 126 | 127 | unsafe impl crate::hostfxr::ManagedFunctionPtr for $managed_fn_type { 128 | type Args = ($($ty,)*); 129 | type Output = Ret; 130 | 131 | const ARITY: ::core::primitive::usize = impl_fn!(@count ($($ty)*)); 132 | } 133 | }; 134 | 135 | (@count ()) => { 136 | 0 137 | }; 138 | (@count ($hd:tt $($tl:tt)*)) => { 139 | 1 + impl_fn!(@count ($($tl)*)) 140 | }; 141 | 142 | ($($nm:ident : $ty:ident),*) => { 143 | impl_fn!(@recurse ($($nm : $ty),*) ()); 144 | }; 145 | } 146 | 147 | impl_fn! { 148 | __arg_0: A, __arg_1: B, __arg_2: C, __arg_3: D, __arg_4: E, __arg_5: F, __arg_6: G, 149 | __arg_7: H, __arg_8: I, __arg_9: J, __arg_10: K, __arg_11: L 150 | } 151 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | #[doc(hidden)] 188 | pub use hostfxr_sys::dlopen2; 189 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/ClassLibrary/.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | obj/ 3 | bin/ 4 | -------------------------------------------------------------------------------- /tests/ClassLibrary/ClassLibrary-net10.0.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ClassLibrary 5 | net10.0 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/Library.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace ClassLibrary { 5 | public class Library { 6 | [UnmanagedCallersOnly] 7 | public static int Hello() { 8 | Console.WriteLine("Hello from Library!"); 9 | return 42; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/Test/.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | obj/ 3 | bin/ 4 | -------------------------------------------------------------------------------- /tests/Test/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace Test { 5 | public static class Program { 6 | public static int Hello(IntPtr arg, int argLength) { 7 | Console.WriteLine("Hello from C#!"); 8 | return 42; 9 | } 10 | public static int Hello2(IntPtr arg, int argLength) { 11 | Console.WriteLine("Hello again?"); 12 | return 0; 13 | } 14 | 15 | public delegate void CustomHelloFunc(); 16 | public static void CustomHello() { 17 | Console.WriteLine("Hello from C#!"); 18 | } 19 | 20 | [UnmanagedCallersOnly] 21 | public static int UnmanagedHello() { 22 | return Hello(default, default); 23 | } 24 | 25 | public static int Main() => Hello(default, default); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /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/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(|_| "net8.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 setup() { 95 | build_test_project(); 96 | build_library_project(); 97 | } 98 | 99 | pub fn build_test_project() { 100 | if Path::new(&test_dll_path().to_os_string()).exists() { 101 | return; 102 | } 103 | 104 | let netcore_version = test_netcore_version(); 105 | let project_file_path = test_project_file_path(); 106 | let project_dir = project_file_path.parent().unwrap(); 107 | 108 | Command::new("dotnet") 109 | .arg("build") 110 | .arg(&project_file_path) 111 | .arg("--framework") 112 | .arg(netcore_version) 113 | .current_dir(project_dir) 114 | .spawn() 115 | .expect("dotnet build failed") 116 | .wait() 117 | .expect("dotnet build failed"); 118 | } 119 | 120 | pub fn build_library_project() { 121 | if Path::new(&library_dll_path().to_os_string()).exists() { 122 | return; 123 | } 124 | 125 | let netcore_version = test_netcore_version(); 126 | let project_file_path = library_project_file_path(); 127 | let project_dir = project_file_path.parent().unwrap(); 128 | 129 | Command::new("dotnet") 130 | .arg("build") 131 | .arg(&project_file_path) 132 | .arg("--framework") 133 | .arg(netcore_version) 134 | .current_dir(project_dir) 135 | .spawn() 136 | .expect("dotnet build failed") 137 | .wait() 138 | .expect("dotnet build failed"); 139 | } 140 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/macro-build-tests/.gitignore: -------------------------------------------------------------------------------- 1 | pdcstr-compile-fail.stderr 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/macro-build-tests/pdcstr-compile-fail.windows.stderr: -------------------------------------------------------------------------------- 1 | error[E0080]: evaluation of constant value failed 2 | --> tests/macro-build-tests/pdcstr-compile-fail.rs:3:13 3 | | 4 | 3 | let _ = netcorehost::pdcstr!("\0"); 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation panicked: invalid NUL value found in string literal 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 of constant value failed 10 | --> tests/macro-build-tests/pdcstr-compile-fail.rs:4:13 11 | | 12 | 4 | let _ = netcorehost::pdcstr!("somerandomteststring\0"); 13 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation panicked: invalid NUL value found in string literal 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 of constant value failed 18 | --> tests/macro-build-tests/pdcstr-compile-fail.rs:5:13 19 | | 20 | 5 | let _ = netcorehost::pdcstr!("somerandomteststring\0somerandomteststring"); 21 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation panicked: invalid NUL value found in string literal 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/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 | -------------------------------------------------------------------------------- /tests/macro-test-crate/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .metadata 4 | .vscode 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 | -------------------------------------------------------------------------------- /tests/macro-test-crate/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let _ = netcorehost::pdcstr!("test"); 3 | } 4 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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.into_path())); 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/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 | --------------------------------------------------------------------------------