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
12 changes: 8 additions & 4 deletions runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1193,6 +1193,10 @@ pub(crate) fn return_value_from_kind<'a>(
v8::null(scope).into()
} else {
let instance = unsafe { IUnknown::from_raw(result) };
// An object that already has a wrapper skips the runtime-class lookup below.
if let Some(local) = crate::wrapper_cache::get_for(scope, &instance) {
return local.into();
}
// Wrap by the runtime class: an interface such as IPropertySet declares no
// members itself and only requires the generic IMap/IIterable instances.
match crate::ns_proxy::resolve_runtime_class(&instance, type_name.as_ref()) {
Expand Down Expand Up @@ -4556,7 +4560,7 @@ fn create_ns_ctor_instance_object<'a>(
.cast::<IInspectable>()
.ok()
.and_then(|insp| insp.GetRuntimeClassName().ok())
.map(|cn| cn.to_string())
.map(|cn| cn.to_string_lossy())
.and_then(|n| MetadataReader::find_by_name(&n).map(|d| (n, d)))
.filter(|(_, d)| !matches!(d.read().kind(), DeclarationKind::Struct));
match resolved {
Expand Down Expand Up @@ -4875,7 +4879,7 @@ fn create_ns_ctor_instance_object<'a>(
.cast::<IInspectable>()
.ok()
.and_then(|insp| insp.GetRuntimeClassName().ok())
.map(|cn| cn.to_string())
.map(|cn| cn.to_string_lossy())
.and_then(|n| MetadataReader::find_by_name(&n).map(|d| (n, d)))
.filter(|(_, d)| !matches!(d.read().kind(), DeclarationKind::Struct));
match resolved {
Expand Down Expand Up @@ -5974,7 +5978,7 @@ unsafe fn raw_result_to_local<'s>(
let unknown = IUnknown::from_raw(result);
if let Ok(inspectable) = unknown.cast::<IInspectable>() {
if let Ok(class_name) = inspectable.GetRuntimeClassName() {
let name_str = class_name.to_string();
let name_str = class_name.to_string_lossy();
if let Some(decl) = MetadataReader::find_by_name(&name_str) {
let instance = unknown.clone();
return Some(
Expand Down Expand Up @@ -7986,7 +7990,7 @@ fn js_delegate_run(
}
let inspectable = owned.cast::<IInspectable>().ok()?;
let class_name = inspectable.GetRuntimeClassName().ok()?;
let name_str = class_name.to_string();
let name_str = class_name.to_string_lossy();
let decl = MetadataReader::find_by_name(&name_str)?;
Some(
create_ns_ctor_instance_object(
Expand Down
9 changes: 0 additions & 9 deletions runtime/src/method_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1390,16 +1390,7 @@ impl MethodCall {
crate::create_struct_object_from_raw(declaration, inner, scope)
.into()
} else {
// Attempt to inspect runtime identity via IInspectable
let instance = unsafe { IUnknown::from_raw(inner) };
match instance.clone().cast::<IInspectable>() {
Ok(ins) => {
let _ = ins.GetRuntimeClassName();
}
Err(e) => {
let _ = e;
}
}
crate::ns_proxy::create_ns_ctor_instance_object(
sig.as_str(),
None,
Expand Down
2 changes: 1 addition & 1 deletion runtime/src/napi_engine/ns_hostobject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ pub fn is_interface(declaration: &Decl) -> bool {
/// The metadata class named by `instance`'s GetRuntimeClassName.
pub fn runtime_class(instance: &IUnknown) -> Option<(String, Decl)> {
let inspectable = instance.cast::<IInspectable>().ok()?;
let name = unsafe { inspectable.GetRuntimeClassName() }.ok()?.to_string();
let name = unsafe { inspectable.GetRuntimeClassName() }.ok()?.to_string_lossy();
let declaration = MetadataReader::find_by_name(&name)?;
if !matches!(declaration.read().kind(), DeclarationKind::Class) {
return None;
Expand Down
38 changes: 34 additions & 4 deletions runtime/src/napi_engine/ns_proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@ struct InstanceCacheEntry {
/// The wrapper is the JS object of a JS subclass instance: returned for this identity whatever
/// class the caller expects (its runtime class, a managed type, isn't in the metadata).
any_type: bool,
/// The wrapper was built for the object's class, so it serves any interface-typed return
/// of the same object.
is_class: bool,
/// Serial of the InstanceState that owns this entry. On that state's Drop we remove the entry
/// only if the serial still matches — so a newer proxy for a reused COM address is never evicted.
serial: u64,
Expand Down Expand Up @@ -243,9 +246,24 @@ fn cached_instance(env: &Env, identity: usize, class_name: &str) -> Option<JsObj
})
}

/// The cached class wrapper for `identity`, whatever class name it was built for.
fn cached_class_instance(env: &Env, identity: usize) -> Option<JsObject> {
let type_name = NAPI_INSTANCE_CACHE.with(|c| {
c.borrow().get(&identity).filter(|e| e.is_class).map(|e| e.type_name.clone())
})?;
cached_instance(env, identity, &type_name)
}

/// Cache `proxy` under `identity` as a weak ref (refcount 0 — GC may reclaim; the entry is
/// re-created on the next wrap if so).
fn cache_instance(env: &Env, identity: usize, class_name: &str, serial: u64, proxy: &JsObject) {
fn cache_instance(
env: &Env,
identity: usize,
class_name: &str,
is_class: bool,
serial: u64,
proxy: &JsObject,
) {
flush_ref_graveyard();
let mut proxy_ref: napi::sys::napi_ref = std::ptr::null_mut();
let st = unsafe { napi::sys::napi_create_reference(env.raw(), proxy.raw(), 0, &mut proxy_ref) };
Expand All @@ -258,6 +276,7 @@ fn cache_instance(env: &Env, identity: usize, class_name: &str, serial: u64, pro
proxy_ref,
type_name: class_name.to_string(),
any_type: false,
is_class,
serial,
},
);
Expand Down Expand Up @@ -1203,6 +1222,16 @@ pub fn create_instance_proxy(
) -> napi::Result<JsObject> {
let identity = com_identity_key(&instance);

// An interface-typed return of an object that already has a class wrapper reuses it
// without asking for the runtime class again.
if let Some(id) = identity {
if crate::napi_engine::ns_hostobject::is_interface(&declaration) {
if let Some(existing) = cached_class_instance(env, id) {
return Ok(existing);
}
}
}

// Hybrid fast path: wrap non-collection *class* instances as host objects (shared prototype,
// no Proxy trap). The effective type is the class from the declaration when it is itself a
// host-eligible class (`new` path), or — for interface-typed returns like `IJsonValue` whose
Expand All @@ -1227,7 +1256,7 @@ pub fn create_instance_proxy(
env, &eff_name, instance, serial, identity,
)?;
if let Some(id) = identity {
cache_instance(env, id, &eff_name, serial, &obj);
cache_instance(env, id, &eff_name, true, serial, &obj);
}
return Ok(obj);
}
Expand All @@ -1253,6 +1282,7 @@ pub fn create_instance_proxy(
}
}
let serial = next_instance_serial();
let is_class = matches!(declaration.read().kind(), DeclarationKind::Class);
let iface = interface_context(&declaration);
let state = Rc::new(InstanceState {
class_name: class_name.to_string(),
Expand Down Expand Up @@ -1544,7 +1574,7 @@ pub fn create_instance_proxy(
let target = as_unknown(env, env.create_object()?);
let proxy = make_proxy(env, target, handler)?;
if let Some(id) = identity {
cache_instance(env, id, class_name, serial, &proxy);
cache_instance(env, id, class_name, is_class, serial, &proxy);
}
Ok(proxy)
}
Expand Down Expand Up @@ -1623,7 +1653,7 @@ pub fn try_wrap_inspectable_pointer(env: &Env, raw: *mut c_void) -> Option<JsObj
}
let inspectable = owned.cast::<IInspectable>().ok()?;
let class_name = unsafe { inspectable.GetRuntimeClassName() }.ok()?;
let name_str = class_name.to_string();
let name_str = class_name.to_string_lossy();
let declaration = MetadataReader::find_by_name(&name_str)?;
if declaration.read().kind() != DeclarationKind::Class {
return None;
Expand Down
33 changes: 25 additions & 8 deletions runtime/src/ns_proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,11 +465,14 @@ pub(crate) fn try_wrap_inspectable_pointer<'a>(
if let Some(v) = try_unbox_property_value(&instance, scope) {
return Some(v);
}
if let Some(local) = crate::wrapper_cache::get_for(scope, &instance) {
return Some(local.into());
}
let resolved = instance
.cast::<IInspectable>()
.ok()
.and_then(|insp| insp.GetRuntimeClassName().ok())
.map(|cn| cn.to_string())
.map(|cn| cn.to_string_lossy())
.and_then(|n| MetadataReader::find_by_name(&n).map(|d| (n, d)))
.filter(|(_, d)| !matches!(d.read().kind(), DeclarationKind::Struct));
match resolved {
Expand Down Expand Up @@ -552,11 +555,15 @@ pub(crate) fn set_ret_val_resolving_object(
) {
if return_sig == "Object" && !result.is_null() {
let instance = unsafe { IUnknown::from_raw(result) };
if let Some(local) = crate::wrapper_cache::get_for(scope, &instance) {
retval.set(local.into());
return;
}
let resolved = instance
.cast::<IInspectable>()
.ok()
.and_then(|insp| insp.GetRuntimeClassName().ok())
.map(|cn| cn.to_string())
.map(|cn| cn.to_string_lossy())
.and_then(|n| MetadataReader::find_by_name(&n).map(|d| (n, d)))
.filter(|(_, d)| !matches!(d.read().kind(), DeclarationKind::Struct));
match resolved {
Expand Down Expand Up @@ -782,13 +789,19 @@ pub(crate) fn handle_named_property_getter(
} else {
let instance =
unsafe { IUnknown::from_raw(result) };
if let Some(local) =
crate::wrapper_cache::get_for(scope, &instance)
{
retval.set(local.into());
return;
}
let resolved = instance
.cast::<IInspectable>()
.ok()
.and_then(|insp| {
insp.GetRuntimeClassName().ok()
})
.map(|cn| cn.to_string())
.map(|cn| cn.to_string_lossy())
.and_then(|n| {
MetadataReader::find_by_name(&n)
.map(|d| (n, d))
Expand Down Expand Up @@ -1098,13 +1111,15 @@ fn instance_method_dispatch(
ReturnKind::DynamicObject => {
if result.is_null() {
None
} else if let Some(local) = crate::wrapper_cache::take_cached(scope, result) {
Some(local.into())
} else {
let instance = unsafe { IUnknown::from_raw(result) };
let resolved = instance
.cast::<IInspectable>()
.ok()
.and_then(|insp| insp.GetRuntimeClassName().ok())
.map(|cn| cn.to_string())
.map(|cn| cn.to_string_lossy())
.and_then(|n| MetadataReader::find_by_name(&n).map(|d| (n, d)))
.filter(|(_, d)| !matches!(d.read().kind(), DeclarationKind::Struct));
match resolved {
Expand Down Expand Up @@ -1173,7 +1188,7 @@ fn find_runtime_class_property(
.ok()?
.GetRuntimeClassName()
.ok()
.map(|s| s.to_string())?;
.map(|s| s.to_string_lossy())?;

if runtime_name.is_empty() || runtime_name == static_type_name {
return None;
Expand Down Expand Up @@ -1386,13 +1401,15 @@ pub(crate) fn handle_instance_property_getter(
ReturnKind::InterfaceObject { decl, type_name } => {
if result.is_null() {
Some(v8::null(scope).into())
} else if let Some(local) = crate::wrapper_cache::take_cached(scope, result) {
Some(local.into())
} else {
let instance = unsafe { IUnknown::from_raw(result) };
let (resolved_name, resolved_decl) = instance
.cast::<IInspectable>()
.ok()
.and_then(|insp| insp.GetRuntimeClassName().ok())
.map(|cn| cn.to_string())
.map(|cn| cn.to_string_lossy())
.and_then(|n| MetadataReader::find_by_name(&n).map(|d| (n, d)))
.filter(|(_, d)| !matches!(d.read().kind(), DeclarationKind::Struct))
.unwrap_or_else(|| (type_name.to_string(), decl.clone()));
Expand Down Expand Up @@ -1867,7 +1884,7 @@ pub(crate) unsafe fn raw_result_to_local<'s>(
let unknown = IUnknown::from_raw(result);
if let Ok(inspectable) = unknown.cast::<IInspectable>() {
if let Ok(class_name) = inspectable.GetRuntimeClassName() {
let name_str = class_name.to_string();
let name_str = class_name.to_string_lossy();
if let Some(decl) = MetadataReader::find_by_name(&name_str) {
let instance = unknown.clone();
return Some(
Expand Down Expand Up @@ -2029,7 +2046,7 @@ pub(crate) fn resolve_runtime_class(
.cast::<IInspectable>()
.ok()
.and_then(|insp| insp.GetRuntimeClassName().ok())
.map(|cn| cn.to_string())
.map(|cn| cn.to_string_lossy())
.filter(|cn| !cn.is_empty() && cn != declared && !cn.contains('<'))
.and_then(|cn| MetadataReader::find_by_name(&cn).map(|d| (cn, d)))
.filter(|(_, d)| !matches!(d.read().kind(), DeclarationKind::Struct))
Expand Down
25 changes: 25 additions & 0 deletions runtime/src/wrapper_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,31 @@ pub(crate) fn get<'s>(scope: &v8::PinScope<'s, '_, ()>, key: usize) -> Option<v8
.map(|ptr| unsafe { std::mem::transmute::<NonNull<v8::Object>, v8::Local<'s, v8::Object>>(ptr) })
}

/// The live wrapper of the COM object behind `instance`, keyed by its IUnknown identity.
#[inline]
pub(crate) fn get_for<'s>(
scope: &v8::PinScope<'s, '_, ()>,
instance: &windows::core::IUnknown,
) -> Option<v8::Local<'s, v8::Object>> {
use windows::core::Interface;
let identity = instance.cast::<windows::core::IUnknown>().ok()?;
get(scope, identity.as_raw() as usize)
}

/// `get_for` on an owned (+1) return pointer: on a hit the reference is released, on a miss
/// the caller still owns it.
#[inline]
pub(crate) fn take_cached<'s>(
scope: &v8::PinScope<'s, '_, ()>,
owned: *mut c_void,
) -> Option<v8::Local<'s, v8::Object>> {
use windows::core::Interface;
let instance = std::mem::ManuallyDrop::new(unsafe { windows::core::IUnknown::from_raw(owned) });
let local = get_for(scope, &instance)?;
drop(std::mem::ManuallyDrop::into_inner(instance));
Some(local)
}

/// Cache `object` as the wrapper of COM identity `key` and hand it `ffi`. Returns the address
/// of the stored `DeclarationFFI` for internal field 0; it lives until V8 collects `object`.
pub(crate) fn insert(
Expand Down