Skip to content

Commit dc5e759

Browse files
committed
Fix #20
1 parent ec2e4bb commit dc5e759

File tree

3 files changed

+105
-1
lines changed

3 files changed

+105
-1
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ keywords = ["nethost", "hostfxr", "dotnet", "bindings", "coreclr"]
1515
num_enum = { version = "0.7", default-features = false }
1616
thiserror = { version = "2.0", default-features = false }
1717
derive_more = { version = "2.0", features = ["deref", "from", "display"], default-features = false }
18-
hostfxr-sys = { version = "0.11.1", features = ["enum-map", "undocumented", "wrapper", "optional-apis"], default-features = false }
18+
hostfxr-sys = { version = "0.12", features = ["enum-map", "undocumented", "wrapper", "optional-apis"], default-features = false }
1919
coreclr-hosting-shared = { version = "0.1", default-features = false }
2020
destruct-drop = { version = "0.2", default-features = false }
2121
ffi-opaque = { version = "2.0", default-features = false }

src/hostfxr/library3_0.rs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use widestring::U16CStr;
2+
13
use crate::{
24
bindings::hostfxr::{hostfxr_handle, hostfxr_initialize_parameters},
35
error::{HostingError, HostingResult, HostingSuccess},
@@ -395,4 +397,50 @@ impl Hostfxr {
395397
)
396398
})
397399
}
400+
401+
/// Sets a callback which is to be used to write errors to.
402+
///
403+
/// # Arguments
404+
/// * `error_writer`
405+
/// A callback function which will be invoked every time an error is to be reported.
406+
/// Or [`None`] to unregister previously registered callbacks and return to the default behavior.
407+
///
408+
/// # Remarks
409+
/// The error writer is registered per-thread, so the registration is thread-local. On each thread
410+
/// only one callback can be registered. Subsequent registrations overwrite the previous ones.
411+
///
412+
/// By default no callback is registered in which case the errors are written to stderr.
413+
///
414+
/// Each call to the error writer is sort of like writing a single line (the EOL character is omitted).
415+
/// Multiple calls to the error writer may occure for one failure.
416+
///
417+
/// If the hostfxr invokes functions in hostpolicy as part of its operation, the error writer
418+
/// will be propagated to hostpolicy for the duration of the call. This means that errors from
419+
/// both hostfxr and hostpolicy will be reporter through the same error writer.
420+
#[cfg_attr(feature = "doc-cfg", doc(cfg(feature = "netcore3_0")))]
421+
pub fn set_error_writer(&self, error_writer: Option<ErrorWriter>) {
422+
let new_raw_error_writer = error_writer
423+
.as_ref()
424+
.map(|_| error_writer_trampoline as hostfxr_sys::hostfxr_error_writer_fn);
425+
unsafe { self.lib.hostfxr_set_error_writer(new_raw_error_writer) };
426+
427+
CURRENT_ERROR_WRITER.with(|current_writer| {
428+
*current_writer.borrow_mut() = error_writer;
429+
});
430+
}
431+
}
432+
433+
type ErrorWriter = Box<dyn FnMut(&U16CStr)>;
434+
435+
thread_local! {
436+
static CURRENT_ERROR_WRITER: std::cell::RefCell<Option<ErrorWriter>> = std::cell::RefCell::new(None);
437+
}
438+
439+
extern "C" fn error_writer_trampoline(raw_error: *const u16) {
440+
CURRENT_ERROR_WRITER.with(|writer_holder| {
441+
if let Some(writer) = writer_holder.borrow_mut().as_mut() {
442+
let error_message = unsafe { U16CStr::from_ptr_str(raw_error) };
443+
writer(error_message);
444+
}
445+
});
398446
}

tests/error_writer.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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+
}

0 commit comments

Comments
 (0)