Skip to content

Commit

Permalink
GetObjectReferenceForInterface improvements (#1046)
Browse files Browse the repository at this point in the history
* Add benchmarks for native events

* Add GetObjectReferenceForInterface which retrieves it with the correct vftable and fix bug with regards to agile reference

* Fix build and move function out of generic function.
  • Loading branch information
manodasanW authored Nov 16, 2021
1 parent c46e536 commit 212b230
Show file tree
Hide file tree
Showing 14 changed files with 245 additions and 148 deletions.
4 changes: 4 additions & 0 deletions src/Benchmarks/Benchmarks.manifest
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
name="BenchmarkComponent.ClassWithMarshalingRoutines"
threadingModel="both"
xmlns="urn:schemas-microsoft-com:winrt.v1" />
<activatableClass
name="BenchmarkComponent.EventOperations"
threadingModel="both"
xmlns="urn:schemas-microsoft-com:winrt.v1" />
</file>

</assembly>
76 changes: 76 additions & 0 deletions src/Benchmarks/EventPerf.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,32 @@

namespace Benchmarks
{
public class ManagedEvents : IEvents
{
public event EventHandler<double> DoublePropertyChanged;
public event EventHandler<int> IntPropertyChanged;

public void RaiseDoubleChanged()
{
DoublePropertyChanged.Invoke(this, 2.2);
}

public void RaiseIntChanged()
{
IntPropertyChanged.Invoke(this, 4);
}
}

[MemoryDiagnoser]
public class EventPerf
{
ClassWithMarshalingRoutines instance;
int z2;
ClassWithMarshalingRoutines instance2;
int z4;
ManagedEvents events;
EventOperations operations;


[GlobalSetup]
public void Setup()
Expand All @@ -28,6 +47,10 @@ public void Setup()
z4 = value * 3;
};
instance2.IntPropertyChanged += s2;

events = new ManagedEvents();
operations = new EventOperations(events);
operations.AddIntEvent();
}

[Benchmark]
Expand Down Expand Up @@ -99,5 +122,58 @@ public object AddAndRemoveIntEventOnNewEventSource()
return instance;
GC.KeepAlive(s);
}

[Benchmark]
public object NativeIntEventOverhead()
{
ManagedEvents events = new ManagedEvents();
EventOperations operations = new EventOperations(events);
return operations;
}

[Benchmark]
public object AddNativeIntEventToNewEventSource()
{
ManagedEvents events = new ManagedEvents();
EventOperations operations = new EventOperations(events);
operations.AddIntEvent();
return operations;
}

[Benchmark]
public object AddMultipleNativeEventsToNewEventSource()
{
ManagedEvents events = new ManagedEvents();
EventOperations operations = new EventOperations(events);
operations.AddIntEvent();
operations.AddDoubleEvent();
return operations;
}

[Benchmark]
public object AddAndInvokeNativeIntEventOnNewEventSource()
{
ManagedEvents events = new ManagedEvents();
EventOperations operations = new EventOperations(events);
operations.AddIntEvent();
operations.FireIntEvent();
return operations;
}

[Benchmark]
public void InvokeNativeIntEvent()
{
operations.FireIntEvent();
}

[Benchmark]
public object AddAndRemoveNativeIntEventOnNewEventSource()
{
ManagedEvents events = new ManagedEvents();
EventOperations operations = new EventOperations(events);
operations.AddIntEvent();
operations.RemoveIntEvent();
return operations;
}
}
}
5 changes: 3 additions & 2 deletions src/Tests/UnitTest/TestComponentCSharp_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2335,8 +2335,9 @@ public void CheckValue()

public void CallProxyObject()
{
// Call to the proxy object after the apartment is gone should throw.
Assert.ThrowsAny<System.Exception>(() => proxyObject2.Commands);
// Call to a proxy object which we internally use an agile reference
// to resolve after the apartment is gone should throw.
Assert.ThrowsAny<System.Exception>(() => proxyObject.Commands);
}

private Windows.UI.Popups.PopupMenu nonAgileObject, nonAgileObject2;
Expand Down
70 changes: 37 additions & 33 deletions src/WinRT.Runtime/ComWrappersSupport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,57 +59,61 @@ public static void MarshalDelegateInvoke<T>(IntPtr thisPtr, Action<T> invoke)
invoke(target_invoke);
}
}
}

// If we are free threaded, we do not need to keep track of context.
// This can either be if the object implements IAgileObject or the free threaded marshaler.
internal unsafe static bool IsFreeThreaded(IObjectReference objRef)
{
if (objRef.TryAs(ABI.WinRT.Interop.IAgileObject.IID, out var agilePtr) >= 0)
{
Marshal.Release(agilePtr);
return true;
}
else if (objRef.TryAs<ABI.WinRT.Interop.IMarshal.Vftbl>(ABI.WinRT.Interop.IMarshal.IID, out var marshalRef) >= 0)
{
using (marshalRef)
{
Guid iid_IUnknown = IUnknownVftbl.IID;
Guid iid_unmarshalClass;
Marshal.ThrowExceptionForHR(marshalRef.Vftbl.GetUnmarshalClass_0(
marshalRef.ThisPtr, &iid_IUnknown, IntPtr.Zero, MSHCTX.InProc, IntPtr.Zero, MSHLFLAGS.Normal, &iid_unmarshalClass));
if (iid_unmarshalClass == ABI.WinRT.Interop.IMarshal.IID_InProcFreeThreadedMarshaler.Value)
{
return true;
}
}
}
return false;
}

public static IObjectReference GetObjectReferenceForInterface(IntPtr externalComObject)
{
return GetObjectReferenceForInterface<IUnknownVftbl>(externalComObject);
}

public static ObjectReference<T> GetObjectReferenceForInterface<T>(IntPtr externalComObject)
{
if (externalComObject == IntPtr.Zero)
{
return null;
}

var unknownRef = ObjectReference<IUnknownVftbl>.FromAbi(externalComObject);

if (IsFreeThreaded(unknownRef))
ObjectReference<T> objRef = ObjectReference<T>.FromAbi(externalComObject);
if (IsFreeThreaded(objRef))
{
return unknownRef;
return objRef;
}
else
{
using (unknownRef)
using (objRef)
{
return new ObjectReferenceWithContext<IUnknownVftbl>(
unknownRef.GetRef(),
return new ObjectReferenceWithContext<T>(
objRef.GetRef(),
Context.GetContextCallback(),
Context.GetContextToken());
}
}

// If we are free threaded, we do not need to keep track of context.
// This can either be if the object implements IAgileObject or the free threaded marshaler.
unsafe static bool IsFreeThreaded(IObjectReference unknownRef)
{
if (unknownRef.TryAs(ABI.WinRT.Interop.IAgileObject.IID, out var agilePtr) >= 0)
{
Marshal.Release(agilePtr);
return true;
}
else if (unknownRef.TryAs<ABI.WinRT.Interop.IMarshal.Vftbl>(ABI.WinRT.Interop.IMarshal.IID, out var marshalRef) >= 0)
{
using (marshalRef)
{
Guid iid_IUnknown = IUnknownVftbl.IID;
Guid iid_unmarshalClass;
Marshal.ThrowExceptionForHR(marshalRef.Vftbl.GetUnmarshalClass_0(
marshalRef.ThisPtr, &iid_IUnknown, IntPtr.Zero, MSHCTX.InProc, IntPtr.Zero, MSHLFLAGS.Normal, &iid_unmarshalClass));
if (iid_unmarshalClass == ABI.WinRT.Interop.IMarshal.IID_InProcFreeThreadedMarshaler.Value)
{
return true;
}
}
}
return false;
}
}

public static void RegisterProjectionAssembly(Assembly assembly) => TypeNameSupport.RegisterProjectionAssembly(assembly);
Expand Down
77 changes: 46 additions & 31 deletions src/WinRT.Runtime/ComWrappersSupport.net5.cs
Original file line number Diff line number Diff line change
Expand Up @@ -465,48 +465,63 @@ private static unsafe bool IsRuntimeImplementedRCW(Type objType)
return isRcw;
}

private static object CreateObject(IObjectReference objRef)
private static object CreateObject(IntPtr externalComObject)
{
if (objRef.TryAs<IInspectable.Vftbl>(IInspectable.IID, out var inspectableRef) == 0)
{
IInspectable inspectable = new IInspectable(inspectableRef);

if (ComWrappersSupport.CreateRCWType != null
&& ComWrappersSupport.CreateRCWType.IsSealed)
Guid inspectableIID = IInspectable.IID;
Guid weakReferenceIID = ABI.WinRT.Interop.IWeakReference.IID;
IntPtr ptr = IntPtr.Zero;

try
{
if (Marshal.QueryInterface(externalComObject, ref inspectableIID, out ptr) == 0)
{
var inspectableObjRef = ComWrappersSupport.GetObjectReferenceForInterface<IInspectable.Vftbl>(ptr);
ComWrappersHelper.Init(inspectableObjRef);

IInspectable inspectable = new IInspectable(inspectableObjRef);

if (ComWrappersSupport.CreateRCWType != null
&& ComWrappersSupport.CreateRCWType.IsSealed)
{
return ComWrappersSupport.GetTypedRcwFactory(ComWrappersSupport.CreateRCWType)(inspectable);
}

string runtimeClassName = ComWrappersSupport.GetRuntimeClassForTypeCreation(inspectable, ComWrappersSupport.CreateRCWType);
if (string.IsNullOrEmpty(runtimeClassName))
{
// If the external IInspectable has not implemented GetRuntimeClassName,
// we use the Inspectable wrapper directly.
return inspectable;
}

return ComWrappersSupport.GetTypedRcwFactory(runtimeClassName)(inspectable);
}
else if (Marshal.QueryInterface(externalComObject, ref weakReferenceIID, out ptr) == 0)
{
return ComWrappersSupport.GetTypedRcwFactory(ComWrappersSupport.CreateRCWType)(inspectable);
// IWeakReference is IUnknown-based, so implementations of it may not (and likely won't) implement
// IInspectable. As a result, we need to check for them explicitly.
var iunknownObjRef = ComWrappersSupport.GetObjectReferenceForInterface<IUnknownVftbl>(ptr);
ComWrappersHelper.Init(iunknownObjRef);

return new SingleInterfaceOptimizedObject(typeof(IWeakReference), iunknownObjRef, false);
}

string runtimeClassName = ComWrappersSupport.GetRuntimeClassForTypeCreation(inspectable, ComWrappersSupport.CreateRCWType);
if (string.IsNullOrEmpty(runtimeClassName))
{
// If the external IInspectable has not implemented GetRuntimeClassName,
// we use the Inspectable wrapper directly.
return inspectable;
else
{
// If the external COM object isn't IInspectable or IWeakReference, we can't handle it.
// If we're registered globally, we want to let the runtime fall back for IUnknown and IDispatch support.
// Return null so the runtime can fall back gracefully in IUnknown and IDispatch scenarios.
return null;
}

return ComWrappersSupport.GetTypedRcwFactory(runtimeClassName)(inspectable);
}
else if (objRef.TryAs<IUnknownVftbl>(ABI.WinRT.Interop.IWeakReference.IID, out var weakRef) == 0)
finally
{
// IWeakReference is IUnknown-based, so implementations of it may not (and likely won't) implement
// IInspectable. As a result, we need to check for them explicitly.

return new SingleInterfaceOptimizedObject(typeof(IWeakReference), weakRef, false);
Marshal.Release(ptr);
}

// If the external COM object isn't IInspectable or IWeakReference, we can't handle it.
// If we're registered globally, we want to let the runtime fall back for IUnknown and IDispatch support.
// Return null so the runtime can fall back gracefully in IUnknown and IDispatch scenarios.
return null;
}

protected override object CreateObject(IntPtr externalComObject, CreateObjectFlags flags)
{
IObjectReference objRef = ComWrappersSupport.GetObjectReferenceForInterface(externalComObject);
ComWrappersHelper.Init(objRef);

var obj = CreateObject(objRef);
var obj = CreateObject(externalComObject);
if (obj is IWinRTObject winrtObj && winrtObj.HasUnwrappableNativeObject && winrtObj.NativeObject != null)
{
// Handle the scenario where the CLR has already done an AddRefFromTrackerSource on the instance
Expand Down
3 changes: 2 additions & 1 deletion src/WinRT.Runtime/MatchingRefApiCompatBaseline.net5.0.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ Compat issues with assembly WinRT.Runtime:
TypesMustExist : Type 'ABI.WinRT.Interop.IWeakReferenceSourceMethods' does not exist in the reference but it does exist in the implementation.
TypesMustExist : Type 'System.Numerics.VectorExtensions' does not exist in the reference but it does exist in the implementation.
TypesMustExist : Type 'WinRT.ComWrappersHelper' does not exist in the reference but it does exist in the implementation.
MembersMustExist : Member 'public WinRT.ObjectReference<T> WinRT.ComWrappersSupport.GetObjectReferenceForInterface<T>(System.IntPtr)' does not exist in the reference but it does exist in the implementation.
MembersMustExist : Member 'public void WinRT.ComWrappersSupport.RegisterObjectForInterface(System.Object, System.IntPtr, System.Runtime.InteropServices.CreateObjectFlags)' does not exist in the reference but it does exist in the implementation.
MembersMustExist : Member 'protected void WinRT.IObjectReference.AddRef(System.Boolean)' does not exist in the reference but it does exist in the implementation.
MembersMustExist : Member 'public System.Int32 WinRT.IObjectReference.TryAs(System.Guid, System.IntPtr)' does not exist in the reference but it does exist in the implementation.
Expand All @@ -12,4 +13,4 @@ MembersMustExist : Member 'public System.IntPtr WinRT.MarshalString.GetAbi(WinRT
TypesMustExist : Type 'WinRT.MarshalString.Pinnable' does not exist in the reference but it does exist in the implementation.
TypesMustExist : Type 'WinRT.Interop.IWeakReference' does not exist in the reference but it does exist in the implementation.
TypesMustExist : Type 'WinRT.Interop.IWeakReferenceSource' does not exist in the reference but it does exist in the implementation.
Total Issues: 13
Total Issues: 15
18 changes: 14 additions & 4 deletions src/WinRT.Runtime/ObjectReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -467,13 +467,23 @@ void InitAgileReference()
});
}

internal ObjectReferenceWithContext(IntPtr thisPtr, IntPtr contextCallbackPtr, IntPtr contextToken, Lazy<AgileReference> agileReference, Guid iid)
internal ObjectReferenceWithContext(IntPtr thisPtr, IntPtr contextCallbackPtr, IntPtr contextToken, Guid iid)
: base(thisPtr)
{
_contextCallbackPtr = contextCallbackPtr;
_contextToken = contextToken;
_cachedContext = new Lazy<ConcurrentDictionary<IntPtr, ObjectReference<T>>>();
_agileReference = agileReference;
_cachedContext = new Lazy<ConcurrentDictionary<IntPtr, ObjectReference<T>>>();
_agileReference = new Lazy<AgileReference>(() => {
AgileReference agileReference = null;
Context.CallInContext(_contextCallbackPtr, _contextToken, InitAgileReference, null);
return agileReference;
void InitAgileReference()
{
agileReference = new AgileReference(this);
}
});

_iid = iid;
}

Expand Down Expand Up @@ -559,7 +569,7 @@ public override unsafe int TryAs<U>(Guid iid, out ObjectReference<U> objRef)
}
AddRefFromTrackerSource();

objRef = new ObjectReferenceWithContext<U>(thatPtr, Context.GetContextCallback(), Context.GetContextToken(), _agileReference, iid)
objRef = new ObjectReferenceWithContext<U>(thatPtr, Context.GetContextCallback(), Context.GetContextToken(), iid)
{
IsAggregated = IsAggregated,
PreventReleaseOnDispose = IsAggregated,
Expand Down
4 changes: 2 additions & 2 deletions src/WinRT.Runtime/Projections/EventHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public static IntPtr GetAbi(IObjectReference value) =>

public static unsafe global::System.EventHandler<T> FromAbi(IntPtr nativeDelegate)
{
var abiDelegate = ComWrappersSupport.GetObjectReferenceForInterface(nativeDelegate)?.As<IDelegateVftbl>(PIID);
var abiDelegate = ComWrappersSupport.GetObjectReferenceForInterface<IDelegateVftbl>(nativeDelegate);
return abiDelegate is null ? null : (global::System.EventHandler<T>)ComWrappersSupport.TryRegisterObjectForInterface(new global::System.EventHandler<T>(new NativeDelegateWrapper(abiDelegate).Invoke), nativeDelegate);
}

Expand Down Expand Up @@ -180,7 +180,7 @@ public static IntPtr GetAbi(IObjectReference value) =>

public static unsafe global::System.EventHandler FromAbi(IntPtr nativeDelegate)
{
var abiDelegate = ComWrappersSupport.GetObjectReferenceForInterface(nativeDelegate)?.As<IDelegateVftbl>(GuidGenerator.GetIID(typeof(EventHandler)));
var abiDelegate = ComWrappersSupport.GetObjectReferenceForInterface<IDelegateVftbl>(nativeDelegate);
return abiDelegate is null ? null : (global::System.EventHandler)ComWrappersSupport.TryRegisterObjectForInterface(new global::System.EventHandler(new NativeDelegateWrapper(abiDelegate).Invoke), nativeDelegate);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public static unsafe IObjectReference CreateMarshaler(global::System.Collections

public static unsafe global::System.Collections.Specialized.NotifyCollectionChangedEventHandler FromAbi(IntPtr nativeDelegate)
{
var abiDelegate = ComWrappersSupport.GetObjectReferenceForInterface(nativeDelegate)?.As<IDelegateVftbl>(GuidGenerator.GetIID(typeof(NotifyCollectionChangedEventHandler)));
var abiDelegate = ComWrappersSupport.GetObjectReferenceForInterface<IDelegateVftbl>(nativeDelegate);
return abiDelegate is null ? null : (global::System.Collections.Specialized.NotifyCollectionChangedEventHandler)ComWrappersSupport.TryRegisterObjectForInterface(new global::System.Collections.Specialized.NotifyCollectionChangedEventHandler(new NativeDelegateWrapper(abiDelegate).Invoke), nativeDelegate);
}

Expand Down
Loading

0 comments on commit 212b230

Please sign in to comment.