Skip to content

Commit

Permalink
Improving perf of unwrapping object (#479)
Browse files Browse the repository at this point in the history
* Avoiding using custom attributes via reflection to get native object and rather making use of IWinRTObject in NET 5 projection.

* Convert IServiceProvider to IDIC

* PR feedback.

* Add delegate unwrapping.
  • Loading branch information
manodasanW authored Oct 14, 2020
1 parent d6d4c2e commit 3bab361
Show file tree
Hide file tree
Showing 18 changed files with 286 additions and 74 deletions.
12 changes: 12 additions & 0 deletions TestComponentCSharp/Class.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,18 @@ namespace winrt::TestComponentCSharp::implementation
_stringPairChanged.remove(token);
}

TestComponentCSharp::ProvideUri Class::GetUriDelegate() noexcept
{
TestComponentCSharp::ProvideUri handler = [] { return Windows::Foundation::Uri(L"http://microsoft.com"); };
return handler;
}

void Class::AddUriHandler(TestComponentCSharp::IUriHandler uriHandler)
{
TestComponentCSharp::ProvideUri handler = [] { return Windows::Foundation::Uri(L"http://github.com"); };
uriHandler.AddUriHandler(handler);
}

BlittableStruct Class::BlittableStructProperty()
{
return _blittableStruct.blittable;
Expand Down
2 changes: 2 additions & 0 deletions TestComponentCSharp/Class.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ namespace winrt::TestComponentCSharp::implementation
void StringPropertyChanged(winrt::event_token const& token) noexcept;
void RaiseStringChanged();
void CallForString(TestComponentCSharp::ProvideString const& provideString);
void AddUriHandler(TestComponentCSharp::IUriHandler uriHandler);
hstring StringProperty2();
void StringProperty2(hstring const& value);
Windows::Foundation::Collections::IVector<hstring> StringsProperty();
Expand Down Expand Up @@ -163,6 +164,7 @@ namespace winrt::TestComponentCSharp::implementation
void CallForStringPair(TestComponentCSharp::ProvideStringPair const& provideStringPair);
winrt::event_token StringPairPropertyChanged(Windows::Foundation::EventHandler<Windows::Foundation::Collections::IKeyValuePair<hstring, hstring>> const& handler);
void StringPairPropertyChanged(winrt::event_token const& token) noexcept;
TestComponentCSharp::ProvideUri GetUriDelegate() noexcept;
BlittableStruct BlittableStructProperty();
void BlittableStructProperty(BlittableStruct const& value);
BlittableStruct GetBlittableStruct();
Expand Down
8 changes: 8 additions & 0 deletions TestComponentCSharp/TestComponentCSharp.idl
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ namespace TestComponentCSharp
//String DistinctProperty{ get; set; };
}

interface IUriHandler
{
void AddUriHandler(ProvideUri provideUri);
};

static runtimeclass ComImports
{
static Object MakeObject();
Expand Down Expand Up @@ -182,6 +187,9 @@ namespace TestComponentCSharp
void CallForStringPair(ProvideStringPair provideStringPair);
event Windows.Foundation.EventHandler<Windows.Foundation.Collections.IKeyValuePair<String, String> > StringPairPropertyChanged;

ProvideUri GetUriDelegate();
void AddUriHandler(IUriHandler uriHandler);

// Structs
BlittableStruct BlittableStructProperty;
BlittableStruct GetBlittableStruct();
Expand Down
26 changes: 26 additions & 0 deletions UnitTest/TestComponentCSharp_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,32 @@ public void TestEvents()
events_expected++;

Assert.Equal(events_received, events_expected);
}

class ManagedUriHandler : IUriHandler
{
public Class TestObject { get; private set; }

public ManagedUriHandler(Class testObject)
{
TestObject = testObject;
}

public void AddUriHandler(ProvideUri provideUri)
{
TestObject.CallForUri(provideUri);
Assert.Equal(new Uri("http://github.com"), TestObject.UriProperty);
}
}

[Fact]
public void TestDelegateUnwrapping()
{
var obj = TestObject.GetUriDelegate();
TestObject.CallForUri(obj);
Assert.Equal(new Uri("http://microsoft.com"), TestObject.UriProperty);

TestObject.AddUriHandler(new ManagedUriHandler(TestObject));
}

// TODO: when the public WinUI nuget supports IXamlServiceProvider, just use the projection
Expand Down
46 changes: 0 additions & 46 deletions WinRT.Runtime/ComWrappersSupport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ public static partial class ComWrappersSupport
{
private readonly static ConcurrentDictionary<string, Func<IInspectable, object>> TypedObjectFactoryCache = new ConcurrentDictionary<string, Func<IInspectable, object>>();
private readonly static ConditionalWeakTable<object, object> CCWTable = new ConditionalWeakTable<object, object>();
private readonly static ConcurrentDictionary<Type, MemberInfo> TypeObjectRefFieldCache = new ConcurrentDictionary<Type, MemberInfo>();

public static TReturn MarshalDelegateInvoke<TDelegate, TReturn>(IntPtr thisPtr, Func<TDelegate, TReturn> invoke)
where TDelegate : class, Delegate
Expand Down Expand Up @@ -57,51 +56,6 @@ public static void MarshalDelegateInvoke<T>(IntPtr thisPtr, Action<T> invoke)
}
}

public static bool TryUnwrapObject(object o, out IObjectReference objRef)
{
// The unwrapping here needs to be in exact type match in case the user
// has implemented a WinRT interface or inherited from a WinRT class
// in a .NET (non-projected) type.

if (o is Delegate del)
{
return TryUnwrapObject(del.Target, out objRef);
}

Type type = o.GetType();

var objRefField = TypeObjectRefFieldCache.GetOrAdd(type, (type) =>
{
ObjectReferenceWrapperAttribute objRefWrapper = type.GetCustomAttribute<ObjectReferenceWrapperAttribute>();
if (objRefWrapper is object)
{
return type.GetField(objRefWrapper.ObjectReferenceField, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
}
ProjectedRuntimeClassAttribute projectedClass = type.GetCustomAttribute<ProjectedRuntimeClassAttribute>();
if (projectedClass is object && projectedClass.DefaultInterfaceProperty != null)
{
return type.GetProperty(projectedClass.DefaultInterfaceProperty, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
}
return null;
});


if(objRefField is FieldInfo field)
{
objRef = (IObjectReference)field.GetValue(o);
return true;
}
else if(objRefField is PropertyInfo defaultProperty)
{
return TryUnwrapObject(defaultProperty.GetValue(o), out objRef);
}

objRef = null;
return false;
}

public static IObjectReference GetObjectReferenceForInterface(IntPtr externalComObject)
{
using var unknownRef = ObjectReference<IUnknownVftbl>.FromAbi(externalComObject);
Expand Down
25 changes: 23 additions & 2 deletions WinRT.Runtime/ComWrappersSupport.net5.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,29 @@ public static T CreateRcwForComObject<T>(IntPtr ptr)
ABI.System.Nullable<Type> nt => (T)(object) nt.Value,
_ => (T) rcw
};
}

}

public static bool TryUnwrapObject(object o, out IObjectReference objRef)
{
// The unwrapping here needs to be an exact type match in case the user
// has implemented a WinRT interface or inherited from a WinRT class
// in a .NET (non-projected) type.

if (o is Delegate del)
{
return TryUnwrapObject(del.Target, out objRef);
}

if (o is IWinRTObject winrtObj && winrtObj.HasUnwrappableNativeObject)
{
objRef = winrtObj.NativeObject;
return true;
}

objRef = null;
return false;
}

public static void RegisterObjectForInterface(object obj, IntPtr thisPtr) => TryRegisterObjectForInterface(obj, thisPtr);

public static object TryRegisterObjectForInterface(object obj, IntPtr thisPtr) => ComWrappers.GetOrRegisterObjectForComInstance(thisPtr, CreateObjectFlags.TrackerObject, obj);
Expand Down
45 changes: 42 additions & 3 deletions WinRT.Runtime/ComWrappersSupport.netstandard2.0.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ public static partial class ComWrappersSupport
{
private static ConditionalWeakTable<object, ComCallableWrapper> ComWrapperCache = new ConditionalWeakTable<object, ComCallableWrapper>();

private static ConcurrentDictionary<IntPtr, System.WeakReference<object>> RuntimeWrapperCache = new ConcurrentDictionary<IntPtr, System.WeakReference<object>>();
private static ConcurrentDictionary<IntPtr, System.WeakReference<object>> RuntimeWrapperCache = new ConcurrentDictionary<IntPtr, System.WeakReference<object>>();
private readonly static ConcurrentDictionary<Type, Func<object, IObjectReference>> TypeObjectRefFuncCache = new ConcurrentDictionary<Type, Func<object, IObjectReference>>();

internal static InspectableInfo GetInspectableInfo(IntPtr pThis) => UnmanagedObject.FindObject<ComCallableWrapper>(pThis).InspectableInfo;

Expand Down Expand Up @@ -75,8 +76,46 @@ public static T CreateRcwForComObject<T>(IntPtr ptr)
ABI.System.Nullable<Type> nt => (T)(object)nt.Value,
_ => (T)rcw
};
}

}

public static bool TryUnwrapObject(object o, out IObjectReference objRef)
{
// The unwrapping here needs to be an exact type match in case the user
// has implemented a WinRT interface or inherited from a WinRT class
// in a .NET (non-projected) type.

if (o is Delegate del)
{
return TryUnwrapObject(del.Target, out objRef);
}

var objRefFunc = TypeObjectRefFuncCache.GetOrAdd(o.GetType(), (type) =>
{
ObjectReferenceWrapperAttribute objRefWrapper = type.GetCustomAttribute<ObjectReferenceWrapperAttribute>();
if (objRefWrapper is object)
{
var field = type.GetField(objRefWrapper.ObjectReferenceField, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
return (o) => (IObjectReference) field.GetValue(o);
}
ProjectedRuntimeClassAttribute projectedClass = type.GetCustomAttribute<ProjectedRuntimeClassAttribute>();
if (projectedClass is object && projectedClass.DefaultInterfaceProperty != null)
{
var property = type.GetProperty(projectedClass.DefaultInterfaceProperty, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
return (o) =>
{
TryUnwrapObject(property.GetValue(o), out var objRef);
return objRef;
};
}
return null;
});

objRef = objRefFunc != null ? objRefFunc(o) : null;
return objRef != null;
}

public static void RegisterObjectForInterface(object obj, IntPtr thisPtr)
{
var referenceWrapper = new System.WeakReference<object>(obj);
Expand Down
3 changes: 2 additions & 1 deletion WinRT.Runtime/IInspectable.net5.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ namespace WinRT
{
public partial class IInspectable : IWinRTObject
{
IObjectReference IWinRTObject.NativeObject => _obj;
IObjectReference IWinRTObject.NativeObject => _obj;
bool IWinRTObject.HasUnwrappableNativeObject => true;

ConcurrentDictionary<RuntimeTypeHandle, IObjectReference> IWinRTObject.QueryInterfaceCache { get; } = new();
}
Expand Down
1 change: 1 addition & 0 deletions WinRT.Runtime/IWinRTObject.net5.cs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ RuntimeTypeHandle IDynamicInterfaceCastable.GetInterfaceImplementation(RuntimeTy
}

IObjectReference NativeObject { get; }
bool HasUnwrappableNativeObject { get; }

protected ConcurrentDictionary<RuntimeTypeHandle, IObjectReference> QueryInterfaceCache { get; }

Expand Down
20 changes: 20 additions & 0 deletions WinRT.Runtime/Marshalers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -993,6 +993,26 @@ public static unsafe void CopyManaged(T o, IntPtr dest, bool unwrapObject = true
public static unsafe void DisposeAbiArray(T box) => MarshalInterfaceHelper<T>.DisposeAbiArray(box);
}

static public class MarshalDelegate
{
public static IObjectReference CreateMarshaler(object o, Guid delegateIID, bool unwrapObject = true)
{
if (o is null)
{
return null;
}

if (unwrapObject && ComWrappersSupport.TryUnwrapObject(o, out var objRef))
{
return objRef.As<global::WinRT.Interop.IDelegateVftbl>(delegateIID);
}
using (var ccw = ComWrappersSupport.CreateCCWForObject(ComWrappersSupport.GetRuntimeClassCCWTypeIfAny(o)))
{
return ccw.As<global::WinRT.Interop.IDelegateVftbl>(delegateIID);
}
}
}

public class Marshaler<T>
{
static Marshaler()
Expand Down
17 changes: 14 additions & 3 deletions WinRT.Runtime/Projections/EventHandler.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
Expand Down Expand Up @@ -47,8 +48,12 @@ public static IntPtr GetAbi(IObjectReference value) =>
return (global::System.EventHandler<T>)ComWrappersSupport.TryRegisterObjectForInterface(new global::System.EventHandler<T>(new NativeDelegateWrapper(abiDelegate).Invoke), nativeDelegate);
}

[global::WinRT.ObjectReferenceWrapper(nameof(_nativeDelegate))]
[global::WinRT.ObjectReferenceWrapper(nameof(_nativeDelegate))]
#if NETSTANDARD2_0
private class NativeDelegateWrapper
#else
private class NativeDelegateWrapper : IWinRTObject
#endif
{
private readonly ObjectReference<global::WinRT.Interop.IDelegateVftbl> _nativeDelegate;
private readonly AgileReference _agileReference = default;
Expand All @@ -64,8 +69,14 @@ public NativeDelegateWrapper(ObjectReference<global::WinRT.Interop.IDelegateVftb
{
objRef.Dispose();
}
}

}

#if !NETSTANDARD2_0
IObjectReference IWinRTObject.NativeObject => _nativeDelegate;
bool IWinRTObject.HasUnwrappableNativeObject => true;
ConcurrentDictionary<RuntimeTypeHandle, IObjectReference> IWinRTObject.QueryInterfaceCache { get; } = new();
#endif

public void Invoke(object sender, T args)
{
using var agileDelegate = _agileReference?.Get()?.As<global::WinRT.Interop.IDelegateVftbl>(GuidGenerator.GetIID(typeof(EventHandler<T>)));
Expand Down
9 changes: 7 additions & 2 deletions WinRT.Runtime/Projections/ICommand.net5.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
Expand Down Expand Up @@ -47,7 +48,7 @@ public static IntPtr GetAbi(IObjectReference value) =>
}

[global::WinRT.ObjectReferenceWrapper(nameof(_nativeDelegate))]
private class NativeDelegateWrapper
private class NativeDelegateWrapper : IWinRTObject
{
private readonly ObjectReference<global::WinRT.Interop.IDelegateVftbl> _nativeDelegate;
private readonly AgileReference _agileReference = default;
Expand All @@ -63,7 +64,11 @@ public NativeDelegateWrapper(ObjectReference<global::WinRT.Interop.IDelegateVftb
{
objRef.Dispose();
}
}
}

IObjectReference IWinRTObject.NativeObject => _nativeDelegate;
bool IWinRTObject.HasUnwrappableNativeObject => true;
ConcurrentDictionary<RuntimeTypeHandle, IObjectReference> IWinRTObject.QueryInterfaceCache { get; } = new();

public void Invoke(object sender, EventArgs args)
{
Expand Down
Loading

0 comments on commit 3bab361

Please sign in to comment.