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
5 changes: 4 additions & 1 deletion runtime/src/generic_method_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,9 @@ impl GenericMethodCall {
let metadata = parameter.metadata().unwrap();

let signature = Signature::to_string(metadata, &type_);
if !crate::helpers::parameter_layout_supported(&signature) {
if !crate::helpers::parameter_layout_supported(&signature)
|| crate::helpers::ReferenceElement::of_signature(&signature).is_some()
{
init_error = Some(E_NOTIMPL);
}

Expand Down Expand Up @@ -268,6 +270,7 @@ impl GenericMethodCall {
if let Some(hr) = self.init_error {
return (hr, std::ptr::null_mut(), Vec::new());
}
let _boxed_args = crate::value::BoxedArgs::begin();
let number_of_abi_parameters = self.number_of_abi_parameters;
let mut arguments: Vec<NativeValue> = Vec::with_capacity(number_of_abi_parameters);
// Track parse-level types for each ABI argument slot.
Expand Down
291 changes: 288 additions & 3 deletions runtime/src/helpers.rs

Large diffs are not rendered by default.

24 changes: 18 additions & 6 deletions runtime/src/js/interop_array.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// interop.Array.create(type, count): a zero-filled array of a primitive WinRT element type,
// backed by JS memory a `T[]` parameter reads, or a callee fills, in place.
// interop.Array.create(type, count): an array for a WinRT `T[]` parameter to read, or a callee
// to fill, in place. Primitive element types get a zero-filled typed array backed by JS memory;
// strings, `Object` and WinRT types (by name or constructor) get a plain array of nulls.
(function () {
var interop = globalThis.interop || (globalThis.NSWinRT && globalThis.NSWinRT.interop);
if (!interop || (interop.Array && typeof interop.Array.create === 'function')) return;
Expand All @@ -13,16 +14,27 @@
Single: 'Float32Array', Double: 'Float64Array'
};

function isReferenceType(type, name) {
return name === 'String' || name === 'Object' || name.indexOf('.') > 0 ||
(typeof type === 'function' && !type.BYTES_PER_ELEMENT);
}

function create(type, count) {
var name = typeof type === 'function' && type.BYTES_PER_ELEMENT ? type.name : String(type);
if (typeof count !== 'number' || !(count >= 0) || count % 1 !== 0 || count > 4294967295) {
throw new RangeError('interop.Array.create: count must be an integer from 0 to 4294967295');
}
if (type != null && isReferenceType(type, name)) {
var array = new Array(count);
for (var i = 0; i < count; i++) array[i] = null;
return array;
}
var kind = Object.prototype.hasOwnProperty.call(kinds, name) ? kinds[name] : name;
var ctor = globalThis[kind];
if (!/Array$/.test(kind) || typeof ctor !== 'function' || !ctor.BYTES_PER_ELEMENT) {
throw new TypeError('interop.Array.create: unsupported element type ' + name +
'; expected one of ' + Object.keys(kinds).join(', '));
}
if (typeof count !== 'number' || !(count >= 0) || count % 1 !== 0 || count > 4294967295) {
throw new RangeError('interop.Array.create: count must be an integer from 0 to 4294967295');
'; expected one of ' + Object.keys(kinds).join(', ') +
', String, Object or a WinRT type');
}
return new ctor(count);
}
Expand Down
110 changes: 110 additions & 0 deletions runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,109 @@ pub(crate) enum ReturnKind {
value: Box<ReturnKind>,
size: usize,
},
/// A `T[]` WinRT allocates: `(length, data)` land in the return buffer, each `size`-byte
/// element converts as `element`, and the buffer is freed after conversion.
Array {
element: Box<ReturnKind>,
#[cfg_attr(not(feature = "napi_engine"), allow(dead_code))]
element_type: Arc<str>,
size: usize,
},
}

/// The [`ReturnKind::Array`] for an array of `element`, or `None` when its elements can't be
/// converted.
pub(crate) fn classify_array(element: &str) -> Option<ReturnKind> {
use crate::value::NativeType;
let element = crate::property_call::abi_type_arg(element);
let kind = classify_return(element, false);
let size = match &kind {
ReturnKind::Primitive(
NativeType::Void | NativeType::Pointer | NativeType::Buffer | NativeType::Function,
) => return None,
ReturnKind::Primitive(NativeType::String) => std::mem::size_of::<usize>(),
ReturnKind::Primitive(nt) => nt.size(),
ReturnKind::Object { .. }
| ReturnKind::InterfaceObject { .. }
| ReturnKind::DynamicObject => std::mem::size_of::<usize>(),
ReturnKind::Struct(declaration) => {
crate::property_call::struct_declaration_size(&*declaration.read())
}
ReturnKind::Guid => 16,
_ => return None,
};
(size > 0).then(|| ReturnKind::Array {
element: Box::new(kind),
element_type: Arc::from(element),
size,
})
}

/// Reads a received array's `(length, data)` from the callee-written slots and converts it.
///
/// # Safety
/// `length` and `data` point at the call's out slots, written by a successful call or zeroed.
#[cfg(feature = "classic")]
pub(crate) unsafe fn received_array_from_slots<'a>(
kind: &ReturnKind,
length: *const c_void,
data: *const c_void,
scope: &mut v8::PinScope<'a, '_>,
) -> Local<'a, v8::Value> {
let length = std::ptr::read_unaligned(length as *const u32);
let data = std::ptr::read_unaligned(data as *const *mut c_void);
received_array_value(kind, length, data, scope)
}

/// Converts `length` elements at `data`, an array WinRT allocated, and frees it: primitives
/// become a typed array, anything else a JS array whose elements take ownership of the strings
/// and objects.
#[cfg(feature = "classic")]
pub(crate) unsafe fn received_array_value<'a>(
kind: &ReturnKind,
length: u32,
data: *mut c_void,
scope: &mut v8::PinScope<'a, '_>,
) -> Local<'a, v8::Value> {
let ReturnKind::Array { element, size, .. } = kind else {
return v8::undefined(scope).into();
};
let count = if data.is_null() { 0 } else { length as usize };
let primitive = match element.as_ref() {
ReturnKind::Primitive(nt) => crate::helpers::ArrayElement::from_native(nt),
_ => None,
};
let value = match primitive {
Some(primitive) => {
let bytes = if count == 0 {
Vec::new()
} else {
std::slice::from_raw_parts(data as *const u8, count * size).to_vec()
};
crate::value::typed_array_from_bytes(scope, primitive, bytes)
}
None => {
let array = v8::Array::new(scope, count as i32);
for index in 0..count {
let slot = (data as *mut u8).add(index * size) as *mut c_void;
let item = match element.as_ref() {
ReturnKind::Object { .. }
| ReturnKind::InterfaceObject { .. }
| ReturnKind::DynamicObject => {
std::ptr::read_unaligned(slot as *const *mut c_void)
}
_ => slot,
};
let item = return_value_from_kind(element, item, None, scope);
array.set_index(scope, index as u32, item);
}
array.into()
}
};
if !data.is_null() {
windows::Win32::System::Com::CoTaskMemFree(Some(data as *const c_void));
}
value
}

fn classify_reference_return(inner: &str) -> Option<ReturnKind> {
Expand Down Expand Up @@ -1130,6 +1233,9 @@ pub(crate) fn classify_return(return_type: &str, is_void: bool) -> ReturnKind {
if return_type == "Object" {
return ReturnKind::DynamicObject;
}
if let Some(element) = return_type.strip_suffix("[]") {
return classify_array(element).unwrap_or(ReturnKind::Void);
}
if return_type.contains('.') {
// A closed generic name ("IAsyncOperation`1<Foo>") resolves to the instance declaration,
// which carries the type arguments and the parameterized IID the wrapper needs; only
Expand Down Expand Up @@ -1189,6 +1295,10 @@ pub(crate) fn return_value_from_kind<'a>(
ReturnKind::Struct(declaration) => {
create_struct_object_from_raw(declaration.clone(), result, scope).into()
}
ReturnKind::Array { .. } => unsafe {
let data = (result as *const u8).add(8) as *const c_void;
received_array_from_slots(kind, result, data, scope)
},
ReturnKind::InterfaceObject { decl, type_name } => {
if result.is_null() {
v8::null(scope).into()
Expand Down
Loading