1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
use crate::{
bindings::hostfxr::{
hostfxr_delegate_type, hostfxr_handle, load_assembly_and_get_function_pointer_fn,
},
error::{HostingError, HostingResult, HostingSuccess},
hostfxr::{
AppOrHostingResult, AssemblyDelegateLoader, DelegateLoader, Hostfxr, HostfxrLibrary,
RawFnPtr, SharedHostfxrLibrary,
},
pdcstring::PdCString,
};
#[cfg(feature = "net5_0")]
use crate::bindings::hostfxr::get_function_pointer_fn;
#[cfg(feature = "net8_0")]
use crate::{
bindings::hostfxr::{load_assembly_bytes_fn, load_assembly_fn},
pdcstring::PdCStr,
};
use std::{
cell::Cell,
ffi::c_void,
fmt::{self, Debug},
marker::PhantomData,
mem::{self, ManuallyDrop, MaybeUninit},
ptr::NonNull,
};
#[cfg(feature = "net8_0")]
use std::ptr;
use destruct_drop::DestructDrop;
use enum_map::EnumMap;
use once_cell::unsync::OnceCell;
/// A marker struct indicating that the context was initialized with a runtime config.
/// This means that it is not possible to run the application associated with the context.
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))]
#[derive(Debug, Clone, Copy)]
pub struct InitializedForRuntimeConfig;
/// A marker struct indicating that the context was initialized for the dotnet command line.
/// This means that it is possible to run the application associated with the context.
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))]
#[derive(Debug, Clone, Copy)]
pub struct InitializedForCommandLine;
/// Handle of a loaded [`HostfxrContext`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(transparent)]
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))]
pub struct HostfxrHandle(NonNull<c_void>);
impl HostfxrHandle {
/// Creates a new hostfxr handle from the given raw handle.
///
/// # Safety
/// - The given raw handle has to be non-null.
/// - The given handle has to be valid and has to represent a hostfxr context.
#[must_use]
pub const unsafe fn new_unchecked(ptr: hostfxr_handle) -> Self {
Self(unsafe { NonNull::new_unchecked(ptr.cast_mut()) })
}
/// Returns the raw underlying handle.
#[must_use]
pub const fn as_raw(&self) -> hostfxr_handle {
self.0.as_ptr()
}
}
impl From<HostfxrHandle> for hostfxr_handle {
fn from(handle: HostfxrHandle) -> Self {
handle.as_raw()
}
}
/// State which hostfxr creates and maintains and represents a logical operation on the hosting components.
#[derive(DestructDrop)]
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))]
pub struct HostfxrContext<I> {
handle: HostfxrHandle,
hostfxr: SharedHostfxrLibrary,
is_primary: bool,
runtime_delegates: EnumMap<hostfxr_delegate_type, OnceCell<RawFnPtr>>,
context_type: PhantomData<I>,
not_sync: PhantomData<Cell<HostfxrLibrary>>,
}
unsafe impl<I> Send for HostfxrContext<I> {}
impl<I> Debug for HostfxrContext<I> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HostfxrContext")
.field("handle", &self.handle)
.field("is_primary", &self.is_primary)
.field("runtime_delegates", &self.runtime_delegates)
.field("context_type", &self.context_type)
.finish_non_exhaustive()
}
}
impl<I> HostfxrContext<I> {
/// Creates a new context from the given handle.
///
/// # Safety
/// The context handle has to be match the context type `I`.
/// If the context was initialized using [`initialize_for_dotnet_command_line`] `I` has to be [`InitializedForCommandLine`].
/// If the context was initialized using [`initialize_for_runtime_config`] `I` has to be [`InitializedForRuntimeConfig`].
///
/// [`initialize_for_dotnet_command_line`]: crate::hostfxr::Hostfxr::initialize_for_dotnet_command_line
/// [`initialize_for_runtime_config`]: crate::hostfxr::Hostfxr::initialize_for_runtime_config
#[must_use]
pub unsafe fn from_handle(handle: HostfxrHandle, hostfxr: Hostfxr, is_primary: bool) -> Self {
Self {
handle,
hostfxr: hostfxr.lib,
is_primary,
runtime_delegates: EnumMap::default(),
context_type: PhantomData,
not_sync: PhantomData,
}
}
/// Gets the underlying handle to the hostfxr context.
#[must_use]
pub const fn handle(&self) -> HostfxrHandle {
self.handle
}
/// Gets the underlying handle to the hostfxr context and consume this context.
#[must_use]
pub fn into_handle(self) -> HostfxrHandle {
let this = ManuallyDrop::new(self);
this.handle
}
/// Gets whether the context is the primary hostfxr context.
/// There can only be a single primary context in a process.
///
/// # Note
/// <https://github.com/dotnet/core-setup/blob/master/Documentation/design-docs/native-hosting.md#synchronization>
#[must_use]
pub const fn is_primary(&self) -> bool {
self.is_primary
}
#[must_use]
pub(crate) const fn library(&self) -> &SharedHostfxrLibrary {
&self.hostfxr
}
/// Gets a typed delegate from the currently loaded `CoreCLR` or from a newly created one.
/// You propably want to use [`get_delegate_loader`] or [`get_delegate_loader_for_assembly`]
/// instead of this function if you want to load function pointers.
///
/// # Remarks
/// If the context was initialized using [`initialize_for_runtime_config`], then all delegate types are supported.
/// If it was initialized using [`initialize_for_dotnet_command_line`], then only the following
/// delegate types are currently supported:
/// * [`hdt_load_assembly_and_get_function_pointer`]
/// * [`hdt_get_function_pointer`]
///
/// [`get_delegate_loader`]: HostfxrContext::get_delegate_loader
/// [`get_delegate_loader_for_assembly`]: HostfxrContext::get_delegate_loader_for_assembly
/// [`hdt_load_assembly_and_get_function_pointer`]: hostfxr_delegate_type::hdt_load_assembly_and_get_function_pointer
/// [`hdt_get_function_pointer`]: hostfxr_delegate_type::hdt_get_function_pointer
/// [`initialize_for_runtime_config`]: Hostfxr::initialize_for_runtime_config
/// [`initialize_for_dotnet_command_line`]: Hostfxr::initialize_for_dotnet_command_line
pub fn get_runtime_delegate(
&self,
r#type: hostfxr_delegate_type,
) -> Result<RawFnPtr, HostingError> {
self.runtime_delegates[r#type]
.get_or_try_init(|| self.get_runtime_delegate_uncached(r#type))
.copied()
}
fn get_runtime_delegate_uncached(
&self,
r#type: hostfxr_delegate_type,
) -> Result<RawFnPtr, HostingError> {
let mut delegate = MaybeUninit::uninit();
let result = unsafe {
self.hostfxr.hostfxr_get_runtime_delegate(
self.handle.as_raw(),
r#type,
delegate.as_mut_ptr(),
)
}
.unwrap();
HostingResult::from(result).into_result()?;
Ok(unsafe { delegate.assume_init() }.cast())
}
fn get_load_assembly_and_get_function_pointer_delegate(
&self,
) -> Result<load_assembly_and_get_function_pointer_fn, HostingError> {
unsafe {
self.get_runtime_delegate(
hostfxr_delegate_type::hdt_load_assembly_and_get_function_pointer,
)
.map(|ptr| mem::transmute(ptr))
}
}
#[cfg(feature = "net5_0")]
fn get_get_function_pointer_delegate(&self) -> Result<get_function_pointer_fn, HostingError> {
unsafe {
self.get_runtime_delegate(hostfxr_delegate_type::hdt_get_function_pointer)
.map(|ptr| mem::transmute(ptr))
}
}
#[cfg(feature = "net8_0")]
fn get_load_assembly_delegate(&self) -> Result<load_assembly_fn, HostingError> {
unsafe {
self.get_runtime_delegate(hostfxr_delegate_type::hdt_load_assembly)
.map(|ptr| mem::transmute(ptr))
}
}
#[cfg(feature = "net8_0")]
fn get_load_assembly_bytes_delegate(&self) -> Result<load_assembly_bytes_fn, HostingError> {
unsafe {
self.get_runtime_delegate(hostfxr_delegate_type::hdt_load_assembly_bytes)
.map(|ptr| mem::transmute(ptr))
}
}
/// Gets a delegate loader for loading an assembly and contained function pointers.
pub fn get_delegate_loader(&self) -> Result<DelegateLoader, HostingError> {
Ok(DelegateLoader {
get_load_assembly_and_get_function_pointer: self
.get_load_assembly_and_get_function_pointer_delegate()?,
#[cfg(feature = "net5_0")]
get_function_pointer: self.get_get_function_pointer_delegate()?,
hostfxr: self.hostfxr.clone(),
})
}
/// Gets a delegate loader for loading function pointers of the assembly with the given path.
/// The assembly will be loaded lazily when the first function pointer is loaded.
pub fn get_delegate_loader_for_assembly(
&self,
assembly_path: impl Into<PdCString>,
) -> Result<AssemblyDelegateLoader, HostingError> {
self.get_delegate_loader()
.map(|loader| AssemblyDelegateLoader::new(loader, assembly_path))
}
/// Loads the specified assembly in the default load context from the given path.
/// It uses [`AssemblyDependencyResolver`] to register additional dependency resolution for the load context.
/// Function pointers to methods in the assembly can then be loaded using a [`DelegateLoader`].
///
/// [`AssemblyDependencyResolver`]: https://learn.microsoft.com/en-us/dotnet/api/system.runtime.loader.assemblydependencyresolver
/// [`AssemblyLoadContext.LoadFromAssembly`]: https://learn.microsoft.com/en-us/dotnet/api/system.runtime.loader.assemblyloadcontext.loadfromassemblypath
#[cfg(feature = "net8_0")]
pub fn load_assembly_from_path(
&self,
assembly_path: impl AsRef<PdCStr>,
) -> Result<(), HostingError> {
let assembly_path = assembly_path.as_ref();
let load_assembly = self.get_load_assembly_delegate()?;
let result = unsafe { load_assembly(assembly_path.as_ptr(), ptr::null(), ptr::null()) };
HostingResult::from(result).into_result()?;
Ok(())
}
/// Loads the specified assembly in the default load context from the given buffers.
/// It does not provide a mechanism for registering additional dependency resolution, as mechanisms like `.deps.json` and [`AssemblyDependencyResolver`] are file-based.
/// Dependencies can be pre-loaded (for example, via a previous call to this function) or the specified assembly can explicitly register its own resolution logic (for example, via the [`AssemblyLoadContext.Resolving`] event).
/// It uses [`AssemblyDependencyResolver`] to register additional dependency resolution for the load context.
/// Function pointers to methods in the assembly can then be loaded using a [`DelegateLoader`].
///
/// [`AssemblyDependencyResolver`]: https://learn.microsoft.com/en-us/dotnet/api/system.runtime.loader.assemblydependencyresolver
/// [`AssemblyLoadContext.Resolving`]: https://learn.microsoft.com/en-us/dotnet/api/system.runtime.loader.assemblyloadcontext.resolving?view=net-7.0
#[cfg(feature = "net8_0")]
pub fn load_assembly_from_bytes(
&self,
assembly_bytes: impl AsRef<[u8]>,
symbols_bytes: impl AsRef<[u8]>,
) -> Result<(), HostingError> {
let symbols_bytes = symbols_bytes.as_ref();
let assembly_bytes = assembly_bytes.as_ref();
let load_assembly_bytes = self.get_load_assembly_bytes_delegate()?;
let result = unsafe {
load_assembly_bytes(
assembly_bytes.as_ptr(),
assembly_bytes.len(),
symbols_bytes.as_ptr(),
symbols_bytes.len(),
ptr::null_mut(),
ptr::null_mut(),
)
};
HostingResult::from(result).into_result()?;
Ok(())
}
/// Closes an initialized host context.
/// This method is automatically called on drop, but can be explicitely called to handle errors during closing.
pub fn close(self) -> Result<HostingSuccess, HostingError> {
let result = unsafe { self.close_raw() };
self.destruct_drop();
result
}
/// Internal non-consuming version of [`close`](HostfxrContext::close)
unsafe fn close_raw(&self) -> Result<HostingSuccess, HostingError> {
let result = unsafe { self.hostfxr.hostfxr_close(self.handle.as_raw()) }.unwrap();
HostingResult::from(result).into_result()
}
}
impl HostfxrContext<InitializedForCommandLine> {
/// Load the dotnet runtime and run the application.
///
/// # Return value
/// If the app was successfully run, the exit code of the application. Otherwise, the error code result.
#[must_use]
pub fn run_app(self) -> AppOrHostingResult {
let result = unsafe { self.hostfxr.hostfxr_run_app(self.handle.as_raw()) }.unwrap();
AppOrHostingResult::from(result)
}
}
impl<I> Drop for HostfxrContext<I> {
fn drop(&mut self) {
let _ = unsafe { self.close_raw() };
}
}