Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion metadata/src/declarations/parameter_declaration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub struct ParameterDeclaration {
token: CorTokenType,
parameter_type: PCCOR_SIGNATURE,
full_name: String,
attributes: u32,
}

impl ParameterDeclaration {
Expand All @@ -24,6 +25,7 @@ impl ParameterDeclaration {
assert_eq!(type_from_token(token), mdtParamDef.0);
assert_ne!(token.0, 0);

let mut attributes = 0u32;
let full_name = match metadata {
None => String::new(),
Some(metadata) => {
Expand All @@ -37,7 +39,7 @@ impl ParameterDeclaration {
0 as _,
Some(&mut full_name_data),
&mut length,
0 as _,
&mut attributes,
0 as _,
0 as _,
0 as _,
Expand All @@ -55,9 +57,16 @@ impl ParameterDeclaration {
token,
parameter_type: sig_type,
full_name,
attributes,
}
}

/// Marked `[out]` without being ByRef: a caller-allocated array the callee fills.
pub fn is_fill_array(&self) -> bool {
const PD_OUT: u32 = 0x2;
self.attributes & PD_OUT != 0 && !self.is_out()
}

pub fn is_out(&self) -> bool {
let mut parameter_type = self.parameter_type.clone();
cor_sig_uncompress_token(&mut parameter_type) == ELEMENT_TYPE_BYREF.0 as u32
Expand Down
30 changes: 30 additions & 0 deletions metadata/src/generic_instance_id_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,15 @@ impl GenericInstanceIdBuilder {
};
compute_winrt_piid(&signature)
}

/// The IID of `IReference<D>` for the non-generic delegate type `D` whose IID is
/// `delegate_iid`: the interface a delegate boxed as an `Object` answers.
pub fn ireference_of_delegate(delegate_iid: &GUID) -> GUID {
compute_winrt_piid(&format!(
"pinterface({{61c17706-2d65-11e0-9ae8-d48564015472}};delegate({}))",
format_guid(delegate_iid)
))
}
}

/// Recursively computes the canonical WinRT wire-format signature for a type
Expand Down Expand Up @@ -467,4 +476,25 @@ mod tests {
);
assert_eq!(actual, expected);
}

#[test]
fn ireference_of_delegate_matches_the_os() {
use windows::core::Interface;
use windows::Foundation::DeferralCompletedHandler;
let names = [
HSTRING::from("Windows.Foundation.IReference`1"),
HSTRING::from("Windows.Foundation.DeferralCompletedHandler"),
];
let elements: Vec<PCWSTR> = names.iter().map(|n| PCWSTR(n.as_ptr())).collect();
let locator = IRoMetaDataLocatorImpl;
let locator = IRoMetaDataLocator::new(&locator);
let mut expected = GUID::zeroed();
unsafe {
RoGetParameterizedTypeInstanceIID(&elements, &*locator, &mut expected, None).unwrap();
}
assert_eq!(
GenericInstanceIdBuilder::ireference_of_delegate(&DeferralCompletedHandler::IID),
expected,
);
}
}
141 changes: 141 additions & 0 deletions runtime/src/delegate_box.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
//! A JS delegate passed where WinRT expects an `Object` must arrive boxed as `IReference<D>`:
//! `UIElement.AddHandler` unboxes its handler that way. Each JS delegate embeds a `DelegateBox`
//! tear-off that answers `IInspectable` and `IReference<D>` and returns the delegate from
//! `get_Value`. Its IUnknown methods forward to the delegate, so COM identity is the delegate's.

use std::ffi::c_void;
use std::sync::OnceLock;

use metadata::generic_instance_id_builder::GenericInstanceIdBuilder;
use windows::core::{IInspectable, Interface, GUID, HRESULT};

const E_NOINTERFACE: HRESULT = HRESULT(0x8000_4002u32 as i32);
const E_POINTER: HRESULT = HRESULT(0x8000_4003u32 as i32);

#[repr(C)]
struct DelegateBoxVtbl {
query_interface:
unsafe extern "system" fn(*mut DelegateBox, *const GUID, *mut *mut c_void) -> HRESULT,
add_ref: unsafe extern "system" fn(*mut DelegateBox) -> u32,
release: unsafe extern "system" fn(*mut DelegateBox) -> u32,
get_iids: unsafe extern "system" fn(*mut DelegateBox, *mut u32, *mut *mut GUID) -> HRESULT,
get_runtime_class_name: unsafe extern "system" fn(*mut DelegateBox, *mut *mut c_void) -> HRESULT,
get_trust_level: unsafe extern "system" fn(*mut DelegateBox, *mut i32) -> HRESULT,
get_value: unsafe extern "system" fn(*mut DelegateBox, *mut *mut c_void) -> HRESULT,
}

static DELEGATE_BOX_VTBL: DelegateBoxVtbl = DelegateBoxVtbl {
query_interface: box_query_interface,
add_ref: box_add_ref,
release: box_release,
get_iids: box_get_iids,
get_runtime_class_name: box_get_runtime_class_name,
get_trust_level: box_get_trust_level,
get_value: box_get_value,
};

#[repr(C)]
pub(crate) struct DelegateBox {
vtable: *const DelegateBoxVtbl,
/// The delegate this box is part of; set once the delegate has its final address.
owner: *mut c_void,
delegate_iid: GUID,
reference_iid: OnceLock<GUID>,
}

impl DelegateBox {
pub(crate) fn new(delegate_iid: GUID) -> Self {
Self {
vtable: &DELEGATE_BOX_VTBL,
owner: std::ptr::null_mut(),
delegate_iid,
reference_iid: OnceLock::new(),
}
}

/// Ties the box to the delegate that contains it.
pub(crate) fn set_owner(&mut self, owner: *mut c_void) {
self.owner = owner;
}

/// The box's interface pointer when the owning delegate is asked for `iid` and the box
/// implements it. The caller AddRefs the delegate.
pub(crate) fn query(&self, iid: &GUID) -> Option<*mut c_void> {
let reference_iid = self
.reference_iid
.get_or_init(|| GenericInstanceIdBuilder::ireference_of_delegate(&self.delegate_iid));
(*iid == IInspectable::IID || iid == reference_iid)
.then(|| self as *const Self as *mut c_void)
}
}

/// The owning delegate's IUnknown vtable.
unsafe fn owner_vtable(this: *mut DelegateBox) -> &'static windows::core::IUnknown_Vtbl {
&**((*this).owner as *const *const windows::core::IUnknown_Vtbl)
}

unsafe extern "system" fn box_query_interface(
this: *mut DelegateBox,
iid: *const GUID,
out: *mut *mut c_void,
) -> HRESULT {
if out.is_null() {
return E_POINTER;
}
if iid.is_null() {
*out = std::ptr::null_mut();
return E_NOINTERFACE;
}
(owner_vtable(this).QueryInterface)((*this).owner, iid, out)
}

unsafe extern "system" fn box_add_ref(this: *mut DelegateBox) -> u32 {
(owner_vtable(this).AddRef)((*this).owner)
}

unsafe extern "system" fn box_release(this: *mut DelegateBox) -> u32 {
(owner_vtable(this).Release)((*this).owner)
}

unsafe extern "system" fn box_get_iids(
_this: *mut DelegateBox,
count: *mut u32,
iids: *mut *mut GUID,
) -> HRESULT {
if count.is_null() || iids.is_null() {
return E_POINTER;
}
*count = 0;
*iids = std::ptr::null_mut();
HRESULT(0)
}

unsafe extern "system" fn box_get_runtime_class_name(
_this: *mut DelegateBox,
name: *mut *mut c_void,
) -> HRESULT {
if name.is_null() {
return E_POINTER;
}
// The empty HSTRING.
*name = std::ptr::null_mut();
HRESULT(0)
}

unsafe extern "system" fn box_get_trust_level(_this: *mut DelegateBox, level: *mut i32) -> HRESULT {
if level.is_null() {
return E_POINTER;
}
*level = 0;
HRESULT(0)
}

unsafe extern "system" fn box_get_value(this: *mut DelegateBox, value: *mut *mut c_void) -> HRESULT {
if value.is_null() {
return E_POINTER;
}
let owner = (*this).owner;
(owner_vtable(this).AddRef)(owner);
*value = owner;
HRESULT(0)
}
34 changes: 34 additions & 0 deletions runtime/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,43 @@ pub fn type_error(message: impl Into<Cow<'static, str>>) -> Error {
custom_error("TypeError", message)
}

/// The HRESULT a call reports when one of its JS arguments could not be marshaled.
pub(crate) const ARGUMENT_FAILURE: windows::core::HRESULT =
windows::core::HRESULT(0x8000_4005u32 as i32);

thread_local! {
static ARGUMENT_ERROR: std::cell::RefCell<Option<String>> =
const { std::cell::RefCell::new(None) };
}

/// Records why an argument could not be marshaled; the call then fails with
/// `ARGUMENT_FAILURE`, and `format_hresult_message` reports this text instead.
pub(crate) fn set_argument_error(message: impl Display) {
ARGUMENT_ERROR.with(|e| *e.borrow_mut() = Some(message.to_string()));
}

/// Drops a recorded argument error that no caller reported.
#[inline]
pub(crate) fn clear_argument_error() {
ARGUMENT_ERROR.with(|e| {
if e.borrow().is_some() {
*e.borrow_mut() = None;
}
});
}

pub(crate) fn take_argument_error() -> Option<String> {
ARGUMENT_ERROR.with(|e| e.borrow_mut().take())
}

/// Format an HRESULT into a human-readable message using the runtime's
/// provided message text when available. Falls back to a hex HRESULT string.
pub fn format_hresult_message(hr: windows::core::HRESULT) -> String {
if hr == ARGUMENT_FAILURE {
if let Some(message) = take_argument_error() {
return message;
}
}
let s = hr.message();
if s.is_empty() {
format!("HRESULT 0x{:08X}", hr.0 as u32)
Expand Down
2 changes: 1 addition & 1 deletion runtime/src/global_fns.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1931,7 +1931,7 @@ const HELPER_SOURCE: &str = concat!(r#"
globalThis.NSWinRT = globalThis.NSWinRT || {};
globalThis.NSWinRT.interop = globalThis.NSWinRT.interop || globalThis.interop;

"#, include_str!("js/dotnet_subclass.js"), r#"
"#, include_str!("js/interop_array.js"), "\n", include_str!("js/dotnet_subclass.js"), r#"

function asDelegate(handler) {
if (typeof handler === 'function') {
Expand Down
Loading