Skip to content

Commit 8757581

Browse files
authored
Remove Unwrap flag from UniqueComInterfaceMarshaller (#92599)
The Unwrap flag only has effect when UniqueInstance is not set. To avoid confusion from anyone referencing this code, we should remove it here. NativeAOT needed to move the Unwrap code to inside the !UniqueInstance block to match behavior of CoreCLR. This should only be noticeable when using ComWrappers to wrap an unwrap the same object in the same NativeAOT instance. In-Proc COM with different servers and clients won't hit this behavior.
1 parent 3138a80 commit 8757581

File tree

4 files changed

+46
-29
lines changed

4 files changed

+46
-29
lines changed

src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.NativeAot.cs

+27-27
Original file line numberDiff line numberDiff line change
@@ -948,33 +948,6 @@ private unsafe bool TryGetOrCreateObjectForComInstanceInternal(
948948
out IntPtr inner);
949949

950950
using ComHolder releaseIdentity = new ComHolder(identity);
951-
if (flags.HasFlag(CreateObjectFlags.Unwrap))
952-
{
953-
ComInterfaceDispatch* comInterfaceDispatch = TryGetComInterfaceDispatch(identity);
954-
if (comInterfaceDispatch != null)
955-
{
956-
// If we found a managed object wrapper in this ComWrappers instance
957-
// and it's has the same identity pointer as the one we're creating a NativeObjectWrapper for,
958-
// unwrap it. We don't AddRef the wrapper as we don't take a reference to it.
959-
//
960-
// A managed object can have multiple managed object wrappers, with a max of one per context.
961-
// Let's say we have a managed object A and ComWrappers instances C1 and C2. Let B1 and B2 be the
962-
// managed object wrappers for A created with C1 and C2 respectively.
963-
// If we are asked to create an EOC for B1 with the unwrap flag on the C2 ComWrappers instance,
964-
// we will create a new wrapper. In this scenario, we'll only unwrap B2.
965-
object unwrapped = ComInterfaceDispatch.GetInstance<object>(comInterfaceDispatch);
966-
if (_ccwTable.TryGetValue(unwrapped, out ManagedObjectWrapperHolder? unwrappedWrapperInThisContext))
967-
{
968-
// The unwrapped object has a CCW in this context. Compare with identity
969-
// so we can see if it's the CCW for the unwrapped object in this context.
970-
if (unwrappedWrapperInThisContext.ComIp == identity)
971-
{
972-
retValue = unwrapped;
973-
return true;
974-
}
975-
}
976-
}
977-
}
978951

979952
if (!flags.HasFlag(CreateObjectFlags.UniqueInstance))
980953
{
@@ -1018,6 +991,33 @@ private unsafe bool TryGetOrCreateObjectForComInstanceInternal(
1018991
return true;
1019992
}
1020993
}
994+
if (flags.HasFlag(CreateObjectFlags.Unwrap))
995+
{
996+
ComInterfaceDispatch* comInterfaceDispatch = TryGetComInterfaceDispatch(identity);
997+
if (comInterfaceDispatch != null)
998+
{
999+
// If we found a managed object wrapper in this ComWrappers instance
1000+
// and it's has the same identity pointer as the one we're creating a NativeObjectWrapper for,
1001+
// unwrap it. We don't AddRef the wrapper as we don't take a reference to it.
1002+
//
1003+
// A managed object can have multiple managed object wrappers, with a max of one per context.
1004+
// Let's say we have a managed object A and ComWrappers instances C1 and C2. Let B1 and B2 be the
1005+
// managed object wrappers for A created with C1 and C2 respectively.
1006+
// If we are asked to create an EOC for B1 with the unwrap flag on the C2 ComWrappers instance,
1007+
// we will create a new wrapper. In this scenario, we'll only unwrap B2.
1008+
object unwrapped = ComInterfaceDispatch.GetInstance<object>(comInterfaceDispatch);
1009+
if (_ccwTable.TryGetValue(unwrapped, out ManagedObjectWrapperHolder? unwrappedWrapperInThisContext))
1010+
{
1011+
// The unwrapped object has a CCW in this context. Compare with identity
1012+
// so we can see if it's the CCW for the unwrapped object in this context.
1013+
if (unwrappedWrapperInThisContext.ComIp == identity)
1014+
{
1015+
retValue = unwrapped;
1016+
return true;
1017+
}
1018+
}
1019+
}
1020+
}
10211021
}
10221022

10231023
retValue = CreateObject(identity, flags);

src/libraries/System.Runtime.InteropServices/src/System/Runtime/InteropServices/Marshalling/UniqueComInterfaceMarshaller.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public static unsafe class UniqueComInterfaceMarshaller<T>
5555
{
5656
return default;
5757
}
58-
return (T)StrategyBasedComWrappers.DefaultMarshallingInstance.GetOrCreateObjectForComInstance((nint)unmanaged, CreateObjectFlags.Unwrap | CreateObjectFlags.UniqueInstance);
58+
return (T)StrategyBasedComWrappers.DefaultMarshallingInstance.GetOrCreateObjectForComInstance((nint)unmanaged, CreateObjectFlags.UniqueInstance);
5959
}
6060

6161

src/tests/Interop/COM/ComWrappers/API/Program.cs

+6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ namespace ComWrappersTests
99
using System.Diagnostics;
1010
using System.Runtime.CompilerServices;
1111
using System.Runtime.InteropServices;
12+
using System.Runtime.InteropServices.Marshalling;
1213

1314
using ComWrappersTests.Common;
1415
using TestLibrary;
@@ -188,6 +189,11 @@ public void ValidateComInterfaceCreationRoundTrip()
188189
var testObjUnwrapped = wrappers.GetOrCreateObjectForComInstance(comWrapper, CreateObjectFlags.Unwrap);
189190
Assert.Same(testObj, testObjUnwrapped);
190191

192+
// UniqueInstance and Unwrap should always be a new com object, never unwrapped
193+
var testObjUniqueUnwrapped = (ITestObjectWrapper)wrappers.GetOrCreateObjectForComInstance(comWrapper, CreateObjectFlags.Unwrap | CreateObjectFlags.UniqueInstance);
194+
Assert.NotSame(testObj, testObjUniqueUnwrapped);
195+
testObjUniqueUnwrapped.FinalRelease();
196+
191197
// Release the wrapper
192198
int count = Marshal.Release(comWrapper);
193199
Assert.Equal(0, count);

src/tests/Interop/COM/ComWrappers/Common.cs

+12-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
namespace ComWrappersTests.Common
55
{
66
using System;
7+
using System.Diagnostics;
78
using System.Threading;
89
using System.Runtime.InteropServices;
910

@@ -97,18 +98,28 @@ public class ITestObjectWrapper : ITest
9798
{
9899
private readonly ITestVtbl._SetValue _setValue;
99100
private readonly IntPtr _ptr;
101+
private bool _released;
100102

101103
public ITestObjectWrapper(IntPtr ptr)
102104
{
103105
_ptr = ptr;
104106
VtblPtr inst = Marshal.PtrToStructure<VtblPtr>(ptr);
105107
ITestVtbl _vtbl = Marshal.PtrToStructure<ITestVtbl>(inst.Vtbl);
106108
_setValue = Marshal.GetDelegateForFunctionPointer<ITestVtbl._SetValue>(_vtbl.SetValue);
109+
_released = false;
110+
}
111+
112+
public int FinalRelease()
113+
{
114+
Debug.Assert(!_released);
115+
int count = Marshal.Release(_ptr);
116+
_released = true;
117+
return count;
107118
}
108119

109120
~ITestObjectWrapper()
110121
{
111-
if (_ptr != IntPtr.Zero)
122+
if (_ptr != IntPtr.Zero && !_released)
112123
{
113124
Marshal.Release(_ptr);
114125
}

0 commit comments

Comments
 (0)