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
31 changes: 31 additions & 0 deletions runtime/src/interop_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2577,3 +2577,34 @@ fn js_delegate_without_value_types_keeps_the_shared_invoke() {
assert!(std::ptr::eq(unsafe { (*delegate).vtable }, &crate::JS_DELEGATE_VTBL));
unsafe { crate::js_delegate_release(delegate) };
}

/// `IReference<T>` parameters take a JS number or null, and `IReference<T>` results come
/// back as the number or null.
#[test]
fn ireference_parameters_and_results() {
run_js_assert(
"ireference_parameters_and_results",
r#"
const formatter = new Windows.Globalization.NumberFormatting.DecimalFormatter();
const parsed = formatter.ParseDouble('1.5');
if (parsed !== 1.5) throw new Error(`ParseDouble('1.5') returned ${parsed}`);
const invalid = formatter.ParseDouble('not a number');
if (invalid !== null) throw new Error(`ParseDouble of junk returned ${invalid}`);
const int = formatter.ParseInt('42');
if (int !== 42) throw new Error(`ParseInt('42') returned ${int}`);

const date = new Windows.ApplicationModel.Contacts.ContactDate();
if (date.Day !== null) throw new Error(`unset Day was ${date.Day}`);
date.Day = 12;
date.Year = -5;
if (date.Day !== 12) throw new Error(`Day was ${date.Day}`);
if (date.Year !== -5) throw new Error(`Year was ${date.Year}`);
date.Day = null;
if (date.Day !== null) throw new Error(`cleared Day was ${date.Day}`);
date.Year = undefined;
if (date.Year !== null) throw new Error(`cleared Year was ${date.Year}`);
date.Month = NSWinRT.interop.reference('UInt32', 7);
if (date.Month !== 7) throw new Error(`boxed Month was ${date.Month}`);
"#,
);
}
40 changes: 40 additions & 0 deletions runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -992,12 +992,44 @@ pub(crate) enum ReturnKind {
},
/// Return type is `Object`/IInspectable: concrete type only known at runtime.
DynamicObject,
/// `IReference<T>` return: `get_Value` is read into a `size`-byte buffer and converted as
/// `value`; a null reference is JS `null`.
Reference {
value: Box<ReturnKind>,
size: usize,
},
}

fn classify_reference_return(inner: &str) -> Option<ReturnKind> {
use crate::value::NativeType;
let value = classify_return(inner, false);
let size = match &value {
ReturnKind::Primitive(
NativeType::Void
| NativeType::Pointer
| NativeType::Buffer
| NativeType::Function
| NativeType::Struct(_),
) => return None,
ReturnKind::Primitive(nt) => nt.size(),
ReturnKind::Struct(_) => crate::helpers::struct_native_type_for_sig(inner)?.size(),
_ => return None,
};
Some(ReturnKind::Reference {
value: Box::new(value),
size,
})
}

pub(crate) fn classify_return(return_type: &str, is_void: bool) -> ReturnKind {
if is_void {
return ReturnKind::Void;
}
if let Some(kind) =
crate::helpers::ireference_inner_type(return_type).and_then(classify_reference_return)
{
return kind;
}
if return_type == "Guid" {
return ReturnKind::Guid;
}
Expand Down Expand Up @@ -1094,6 +1126,14 @@ pub(crate) fn return_value_from_kind<'a>(
}
}
}
ReturnKind::Reference { value, size } => {
match unsafe { crate::value::read_reference_value(result, *size) } {
Some(mut buf) => {
return_value_from_kind(value, buf.as_mut_ptr() as *mut c_void, None, scope)
}
None => v8::null(scope).into(),
}
}
ReturnKind::Primitive(nt) => match nt {
NativeType::Pointer => {
if result.is_null() {
Expand Down
39 changes: 26 additions & 13 deletions runtime/src/method_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@ pub(crate) enum PointerPlan {
/// Plain pointer parse: non-WinRT signature, unresolvable type, or a resolvable kind
/// that takes no special handling.
Plain,
/// `IReference<T>` parameter — box primitives via the typed Create* call (inner type name).
IReference(String),
/// `IReference<T>` parameter — box primitives via the typed Create* call (inner type name)
/// and pass the `IReference<T>` interface (IID).
IReference(String, GUID),
/// `Windows.UI.Xaml.Interop.TypeName` struct — synthesize {Name, Kind} from a class ctor.
TypeName,
/// Other struct parameter — serialize field-by-field (declaration pre-resolved).
Expand Down Expand Up @@ -78,7 +79,15 @@ impl PointerPlan {
typename_special: bool,
) -> Self {
if let Some(inner) = crate::helpers::ireference_inner_type(signature) {
return PointerPlan::IReference(inner.to_string());
let name = signature.trim();
let name = name.strip_prefix("ByRef ").unwrap_or(name);
let iid = GenericInstanceIdBuilder::generate_id_from_name(
&crate::property_call::substitute_type_vars(name, type_args),
);
return PointerPlan::IReference(
crate::property_call::substitute_type_vars(inner, type_args),
iid,
);
}
if !signature.contains('.') {
return PointerPlan::Plain;
Expand Down Expand Up @@ -993,11 +1002,13 @@ impl MethodCall {
PointerPlan::Plain => ffi_parse_pointer_arg(scope, value),
// IReference<T> parameters: box JS primitives with the correct Create* call
// so XAML receives the right typed IPropertyValue (e.g. IReference<Double>).
PointerPlan::IReference(inner) => {
if let Some(nv) = crate::value::box_as_ireference(scope, value, inner) {
Ok(nv)
} else {
ffi_parse_pointer_arg(scope, value)
PointerPlan::IReference(inner, iid) => {
match crate::value::box_as_ireference(scope, value, inner, iid) {
Some((nv, guard)) => {
queried_interfaces.extend(guard);
Ok(nv)
}
None => ffi_parse_pointer_arg(scope, value),
}
}
PointerPlan::TypeName => {
Expand Down Expand Up @@ -1625,12 +1636,14 @@ impl MethodCall {
// resolved once into the per-parameter plan when the static info was built.
match &self.si.param_plans[i] {
PointerPlan::Plain => nv::napi_parse_pointer(env, &value),
PointerPlan::IReference(inner) => {
PointerPlan::IReference(inner, iid) => {
// IReference<T>: box primitives with the correct typed Create* call.
if let Some(nvv) = nv::box_as_ireference(env, &value, inner) {
Ok(nvv)
} else {
nv::napi_parse_pointer(env, &value)
match nv::box_as_ireference(env, &value, inner, iid) {
Some((nvv, guard)) => {
queried_interfaces.extend(guard);
Ok(nvv)
}
None => nv::napi_parse_pointer(env, &value),
}
}
PointerPlan::TypeName => {
Expand Down
13 changes: 13 additions & 0 deletions runtime/src/napi_engine/invoke.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,19 @@ pub(crate) fn convert_call_result(
.map_err(|e| generic_error(e.to_string()))?;
return Ok(nv::as_unknown(env, proxy));
}
ReturnKind::Reference { value, size } => {
let inner = crate::helpers::ireference_inner_type(return_type).unwrap_or(return_type);
return match unsafe { crate::value::read_reference_value(result, *size) } {
Some(mut buf) => {
let ptr = buf.as_mut_ptr() as *mut c_void;
convert_call_result(env, value, false, inner, hr, ptr)
}
None => {
let n = env.get_null().map_err(|e| type_error(e.to_string()))?;
Ok(nv::as_unknown(env, n))
}
};
}
ReturnKind::Primitive(nt) => match nt {
NativeType::Pointer
| NativeType::Buffer
Expand Down
31 changes: 27 additions & 4 deletions runtime/src/napi_engine/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -851,10 +851,33 @@ pub fn box_as_typed_value(env: &Env, arg: &JsUnknown, type_name: &str) -> Option
}
}

/// Alias used by method_call / property_call for IReference<T> params.
#[inline]
pub fn box_as_ireference(env: &Env, arg: &JsUnknown, inner_type: &str) -> Option<NativeValue> {
box_as_typed_value(env, arg, inner_type)
/// Marshal a JS value for an `IReference<T>` parameter. See `crate::value::box_as_ireference`.
pub fn box_as_ireference(
env: &Env,
arg: &JsUnknown,
inner_type: &str,
iid: &GUID,
) -> Option<(NativeValue, Option<IUnknown>)> {
let vt = arg.get_type().ok()?;
if vt == ValueType::Null || vt == ValueType::Undefined {
return Some((
NativeValue {
pointer: std::ptr::null_mut(),
},
None,
));
}
let wrapped = if vt == ValueType::Object || vt == ValueType::Function {
let obj: JsObject = unsafe { arg.cast() };
try_get_external_handle(env, &obj).filter(|ptr| !ptr.is_null())
} else {
None
};
let boxed = match wrapped {
Some(ptr) => (*ManuallyDrop::new(unsafe { IUnknown::from_raw(ptr) })).clone(),
None => unsafe { IUnknown::from_raw(box_as_typed_value(env, arg, inner_type)?.pointer) },
};
Some(crate::value::query_reference(boxed, iid))
}

//
Expand Down
17 changes: 17 additions & 0 deletions runtime/src/ns_proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,11 @@ pub(crate) fn handle_named_property_getter(
.return_kind()
{
ReturnKind::Void => None,
kind @ ReturnKind::Reference { .. } => {
Some(crate::return_value_from_kind(
kind, result, None, scope,
))
}
ReturnKind::Guid => {
let obj = unsafe {
crate::guid_ptr_to_js_object(result, scope)
Expand Down Expand Up @@ -1023,6 +1028,9 @@ fn instance_method_dispatch(

let return_value_opt: Option<Local<v8::Value>> = match method.return_kind() {
ReturnKind::Void => None,
kind @ ReturnKind::Reference { .. } => {
Some(crate::return_value_from_kind(kind, result, None, scope))
}
ReturnKind::Guid => {
let obj = unsafe { crate::guid_ptr_to_js_object(result, scope) };
Some(obj.into())
Expand Down Expand Up @@ -1308,6 +1316,9 @@ pub(crate) fn handle_instance_property_getter(

let ret_val: Option<Local<v8::Value>> = match property_call.return_kind() {
ReturnKind::Void => None,
kind @ ReturnKind::Reference { .. } => {
Some(crate::return_value_from_kind(kind, result, None, scope))
}
ReturnKind::Guid => {
let obj = unsafe { crate::guid_ptr_to_js_object(result, scope) };
Some(obj.into())
Expand Down Expand Up @@ -2120,6 +2131,9 @@ pub(crate) fn create_ns_ctor_instance_object<'a>(
} else if !method.is_void() {
let ret_v: Option<Local<v8::Value>> = match method.return_kind() {
ReturnKind::Void => None,
kind @ ReturnKind::Reference { .. } => {
Some(crate::return_value_from_kind(kind, result, None, scope))
}
ReturnKind::Guid => {
let obj = unsafe { guid_ptr_to_js_object(result, scope) };
Some(obj.into())
Expand Down Expand Up @@ -2268,6 +2282,9 @@ pub(crate) fn create_ns_ctor_instance_object<'a>(
} else if !method.is_void() {
let ret_v: Option<Local<v8::Value>> = match method.return_kind() {
ReturnKind::Void => None,
kind @ ReturnKind::Reference { .. } => Some(
crate::return_value_from_kind(kind, result, None, scope),
),
ReturnKind::Guid => {
let obj = unsafe { guid_ptr_to_js_object(result, scope) };
Some(obj.into())
Expand Down
24 changes: 14 additions & 10 deletions runtime/src/property_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1259,11 +1259,13 @@ impl PropertyCall {
}
// IReference<T> parameters: box JS primitives with the correct Create* call
// so XAML receives the right typed IPropertyValue (e.g. IReference<Double>).
PointerPlan::IReference(inner) => {
if let Some(nv) = crate::value::box_as_ireference(scope, value, inner) {
Ok(nv)
} else {
ffi_parse_pointer_arg(scope, value)
PointerPlan::IReference(inner, iid) => {
match crate::value::box_as_ireference(scope, value, inner, iid) {
Some((nv, guard)) => {
queried_interfaces.extend(guard);
Ok(nv)
}
None => ffi_parse_pointer_arg(scope, value),
}
}
PointerPlan::Struct(declaration) => {
Expand Down Expand Up @@ -1651,11 +1653,13 @@ impl PropertyCall {
PointerPlan::Plain | PointerPlan::TypeName => {
nv::napi_parse_pointer(env, &value)
}
PointerPlan::IReference(inner) => {
if let Some(nvv) = nv::box_as_ireference(env, &value, inner) {
Ok(nvv)
} else {
nv::napi_parse_pointer(env, &value)
PointerPlan::IReference(inner, iid) => {
match nv::box_as_ireference(env, &value, inner, iid) {
Some((nvv, guard)) => {
queried_interfaces.extend(guard);
Ok(nvv)
}
None => nv::napi_parse_pointer(env, &value),
}
}
PointerPlan::Struct(declaration) => {
Expand Down
70 changes: 66 additions & 4 deletions runtime/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1084,15 +1084,77 @@ pub fn box_as_typed_value(
}
}

/// Keep the old name as an alias — used by method_call / property_call for IReference<T> params.
/// Marshal a JS value for an `IReference<T>` parameter: `null`/`undefined` is a null reference;
/// anything else is boxed as `inner_type` (or taken from its wrapper) and queried for `iid`.
/// The returned guard keeps the reference alive for the call.
#[cfg(feature = "classic")]
#[inline]
pub fn box_as_ireference(
scope: &mut v8::PinScope<'_, '_>,
arg: v8::Local<v8::Value>,
inner_type: &str,
) -> Option<NativeValue> {
box_as_typed_value(scope, arg, inner_type)
iid: &GUID,
) -> Option<(NativeValue, Option<IUnknown>)> {
if arg.is_null_or_undefined() {
return Some((
NativeValue {
pointer: std::ptr::null_mut(),
},
None,
));
}
let wrapped = if arg.is_object() {
arg.to_object(scope)
.and_then(|obj| try_get_external_handle(scope, obj))
.filter(|ptr| !ptr.is_null())
} else {
None
};
let boxed = match wrapped {
Some(ptr) => (*ManuallyDrop::new(unsafe { IUnknown::from_raw(ptr) })).clone(),
None => unsafe { IUnknown::from_raw(box_as_typed_value(scope, arg, inner_type)?.pointer) },
};
Some(query_reference(boxed, iid))
}

/// QI an owned boxed value for an `IReference<T>` IID, falling back to the value itself.
pub(crate) fn query_reference(
boxed: windows::core::IUnknown,
iid: &windows::core::GUID,
) -> (NativeValue, Option<windows::core::IUnknown>) {
use windows::core::Interface;
let mut queried: *mut c_void = std::ptr::null_mut();
if unsafe { boxed.query(iid, &mut queried) }.is_ok() && !queried.is_null() {
let queried = unsafe { windows::core::IUnknown::from_raw(queried) };
return (
NativeValue {
pointer: queried.as_raw(),
},
Some(queried),
);
}
(
NativeValue {
pointer: boxed.as_raw(),
},
Some(boxed),
)
}

/// Read an `IReference<T>` return through its `get_Value` into a buffer of at least `size`
/// bytes, releasing the reference. `None` for a null reference or a failed read.
pub(crate) unsafe fn read_reference_value(reference: *mut c_void, size: usize) -> Option<Vec<u64>> {
use windows::core::Interface;
if reference.is_null() {
return None;
}
let reference = windows::core::IUnknown::from_raw(reference);
type GetValue = unsafe extern "system" fn(*mut c_void, *mut c_void) -> windows::core::HRESULT;
let vtable = *(reference.as_raw() as *const *const usize);
let get_value: GetValue = std::mem::transmute(*vtable.add(6));
let mut buf = vec![0u64; size.div_ceil(8).max(2)];
get_value(reference.as_raw(), buf.as_mut_ptr() as *mut c_void)
.is_ok()
.then_some(buf)
}

/// Parse "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" into a GUID.
Expand Down
Loading