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