diff --git a/src/coreclr/clr.featuredefines.props b/src/coreclr/clr.featuredefines.props
index 7ae2f2f7c8b94..b4074674e5744 100644
--- a/src/coreclr/clr.featuredefines.props
+++ b/src/coreclr/clr.featuredefines.props
@@ -28,6 +28,7 @@
true
true
true
+ true
true
true
true
@@ -54,6 +55,7 @@
$(DefineConstants);FEATURE_STUBS_AS_IL
$(DefineConstants);FEATURE_CLASSIC_COMINTEROP
$(DefineConstants);FEATURE_COLLECTIBLE_ALC
+ $(DefineConstants);FEATURE_COMWRAPPERS
$(DefineConstants);FEATURE_COMINTEROP
$(DefineConstants);FEATURE_COMINTEROP_APARTMENT_SUPPORT
$(DefineConstants);FEATURE_COMINTEROP_UNMANAGED_ACTIVATION
diff --git a/src/coreclr/clrdefinitions.cmake b/src/coreclr/clrdefinitions.cmake
index 474788010d779..a58e3af0d7604 100644
--- a/src/coreclr/clrdefinitions.cmake
+++ b/src/coreclr/clrdefinitions.cmake
@@ -98,6 +98,7 @@ add_compile_definitions($<$>>:F
add_definitions(-DFEATURE_COLLECTIBLE_TYPES)
if(CLR_CMAKE_TARGET_WIN32)
+ add_definitions(-DFEATURE_COMWRAPPERS)
add_definitions(-DFEATURE_CLASSIC_COMINTEROP)
add_definitions(-DFEATURE_COMINTEROP)
add_definitions(-DFEATURE_COMINTEROP_APARTMENT_SUPPORT)
diff --git a/src/coreclr/src/CMakeLists.txt b/src/coreclr/src/CMakeLists.txt
index 6a753253f2044..b6584b2de7830 100644
--- a/src/coreclr/src/CMakeLists.txt
+++ b/src/coreclr/src/CMakeLists.txt
@@ -74,6 +74,7 @@ add_subdirectory(tools)
add_subdirectory(unwinder)
add_subdirectory(ildasm)
add_subdirectory(ilasm)
+add_subdirectory(interop)
if(CLR_CMAKE_HOST_UNIX)
add_subdirectory(palrt)
diff --git a/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj b/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj
index 881d4601cd6ec..7e215d1ad72a8 100644
--- a/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj
+++ b/src/coreclr/src/System.Private.CoreLib/System.Private.CoreLib.csproj
@@ -303,6 +303,9 @@
+
+
+
Common\System\Runtime\InteropServices\IDispatch.cs
diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs
index 117e7e26dc6ea..705e611d7e164 100644
--- a/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs
+++ b/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.CoreCLR.cs
@@ -264,6 +264,28 @@ internal static unsafe bool ObjectHasComponentSize(object obj)
return (MethodTable *)Unsafe.Add(ref Unsafe.As(ref obj.GetRawData()), -1);
}
+
+ ///
+ /// Allocate memory that is associated with the and
+ /// will be freed if and when the is unloaded.
+ ///
+ /// Type associated with the allocated memory.
+ /// Amount of memory in bytes to allocate.
+ /// The allocated memory
+ public static IntPtr AllocateTypeAssociatedMemory(Type type, int size)
+ {
+ RuntimeType? rt = type as RuntimeType;
+ if (rt == null)
+ throw new ArgumentException(SR.Arg_MustBeType, nameof(type));
+
+ if (size < 0)
+ throw new ArgumentOutOfRangeException(nameof(size));
+
+ return AllocateTypeAssociatedMemoryInternal(new QCallTypeHandle(ref rt), (uint)size);
+ }
+
+ [DllImport(RuntimeHelpers.QCall)]
+ private static extern IntPtr AllocateTypeAssociatedMemoryInternal(QCallTypeHandle type, uint size);
}
// Helper class to assist with unsafe pinning of arbitrary objects.
diff --git a/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs b/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs
new file mode 100644
index 0000000000000..f5d0e06b016a3
--- /dev/null
+++ b/src/coreclr/src/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs
@@ -0,0 +1,252 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections;
+using System.Threading;
+using System.Runtime.CompilerServices;
+using Internal.Runtime.CompilerServices;
+
+namespace System.Runtime.InteropServices
+{
+ ///
+ /// Enumeration of flags for .
+ ///
+ [Flags]
+ public enum CreateComInterfaceFlags
+ {
+ None = 0,
+
+ ///
+ /// The caller will provide an IUnknown Vtable.
+ ///
+ ///
+ /// This is useful in scenarios when the caller has no need to rely on an IUnknown instance
+ /// that is used when running managed code is not possible (i.e. during a GC). In traditional
+ /// COM scenarios this is common, but scenarios involving Reference Tracker hosting
+ /// calling of the IUnknown API during a GC is possible.
+ ///
+ CallerDefinedIUnknown = 1,
+
+ ///
+ /// Flag used to indicate the COM interface should implement IReferenceTrackerTarget.
+ /// When this flag is passed, the resulting COM interface will have an internal implementation of IUnknown
+ /// and as such none should be supplied by the caller.
+ ///
+ TrackerSupport = 2,
+ }
+
+ ///
+ /// Enumeration of flags for .
+ ///
+ [Flags]
+ public enum CreateObjectFlags
+ {
+ None = 0,
+
+ ///
+ /// Indicate if the supplied external COM object implements the IReferenceTracker.
+ ///
+ TrackerObject = 1,
+
+ ///
+ /// Ignore any internal caching and always create a unique instance.
+ ///
+ UniqueInstance = 2,
+ }
+
+ ///
+ /// Class for managing wrappers of COM IUnknown types.
+ ///
+ [CLSCompliant(false)]
+ public abstract partial class ComWrappers
+ {
+ ///
+ /// Interface type and pointer to targeted VTable.
+ ///
+ public struct ComInterfaceEntry
+ {
+ ///
+ /// Interface IID.
+ ///
+ public Guid IID;
+
+ ///
+ /// Memory must have the same lifetime as the memory returned from the call to .
+ ///
+ public IntPtr Vtable;
+ }
+
+ ///
+ /// ABI for function dispatch of a COM interface.
+ ///
+ public struct ComInterfaceDispatch
+ {
+ public IntPtr Vtable;
+
+ ///
+ /// Given a from a generated Vtable, convert to the target type.
+ ///
+ /// Desired type.
+ /// Pointer supplied to Vtable function entry.
+ /// Instance of type associated with dispatched function call.
+ public static unsafe T GetInstance(ComInterfaceDispatch* dispatchPtr) where T : class
+ {
+ // See the dispatch section in the runtime for details on the masking below.
+ const long DispatchThisPtrMask = ~0xfL;
+ var comInstance = *(ComInterfaceInstance**)(((long)dispatchPtr) & DispatchThisPtrMask);
+
+ return Unsafe.As(GCHandle.InternalGet(comInstance->GcHandle));
+ }
+
+ private struct ComInterfaceInstance
+ {
+ public IntPtr GcHandle;
+ }
+ }
+
+ ///
+ /// Globally registered instance of the ComWrappers class.
+ ///
+ private static ComWrappers? s_globalInstance;
+
+ ///
+ /// Create a COM representation of the supplied object that can be passed to a non-managed environment.
+ ///
+ /// The managed object to expose outside the .NET runtime.
+ /// Flags used to configure the generated interface.
+ /// The generated COM interface that can be passed outside the .NET runtime.
+ public IntPtr GetOrCreateComInterfaceForObject(object instance, CreateComInterfaceFlags flags)
+ {
+ if (instance == null)
+ throw new ArgumentNullException(nameof(instance));
+
+ ComWrappers impl = this;
+ return GetOrCreateComInterfaceForObjectInternal(ObjectHandleOnStack.Create(ref impl), ObjectHandleOnStack.Create(ref instance), flags);
+ }
+
+ [DllImport(RuntimeHelpers.QCall)]
+ private static extern IntPtr GetOrCreateComInterfaceForObjectInternal(ObjectHandleOnStack comWrappersImpl, ObjectHandleOnStack instance, CreateComInterfaceFlags flags);
+
+ ///
+ /// Compute the desired Vtable for respecting the values of .
+ ///
+ /// Target of the returned Vtables.
+ /// Flags used to compute Vtables.
+ /// The number of elements contained in the returned memory.
+ /// pointer containing memory for all COM interface entries.
+ ///
+ /// All memory returned from this function must either be unmanaged memory, pinned managed memory, or have been
+ /// allocated with the API.
+ ///
+ /// If the interface entries cannot be created and null
is returned, the call to will throw a .
+ ///
+ protected unsafe abstract ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count);
+
+ // Call to execute the abstract instance function
+ internal static unsafe void* CallComputeVtables(ComWrappers? comWrappersImpl, object obj, CreateComInterfaceFlags flags, out int count)
+ => (comWrappersImpl ?? s_globalInstance!).ComputeVtables(obj, flags, out count);
+
+ ///
+ /// Get the currently registered managed object or creates a new managed object and registers it.
+ ///
+ /// Object to import for usage into the .NET runtime.
+ /// Flags used to describe the external object.
+ /// Returns a managed object associated with the supplied external COM object.
+ public object GetOrCreateObjectForComInstance(IntPtr externalComObject, CreateObjectFlags flags)
+ {
+ return GetOrCreateObjectForComInstanceInternal(externalComObject, flags, null);
+ }
+
+ ///
+ /// Create a managed object for the object pointed at by respecting the values of .
+ ///
+ /// Object to import for usage into the .NET runtime.
+ /// Flags used to describe the external object.
+ /// Returns a managed object associated with the supplied external COM object.
+ ///
+ /// If the object cannot be created and null
is returned, the call to will throw a .
+ ///
+ protected abstract object? CreateObject(IntPtr externalComObject, CreateObjectFlags flags);
+
+ // Call to execute the abstract instance function
+ internal static object? CallCreateObject(ComWrappers? comWrappersImpl, IntPtr externalComObject, CreateObjectFlags flags)
+ => (comWrappersImpl ?? s_globalInstance!).CreateObject(externalComObject, flags);
+
+ ///
+ /// Get the currently registered managed object or uses the supplied managed object and registers it.
+ ///
+ /// Object to import for usage into the .NET runtime.
+ /// Flags used to describe the external object.
+ /// The to be used as the wrapper for the external object
+ /// Returns a managed object associated with the supplied external COM object.
+ ///
+ /// If the instance already has an associated external object a will be thrown.
+ ///
+ public object GetOrRegisterObjectForComInstance(IntPtr externalComObject, CreateObjectFlags flags, object wrapper)
+ {
+ if (wrapper == null)
+ throw new ArgumentNullException(nameof(externalComObject));
+
+ return GetOrCreateObjectForComInstanceInternal(externalComObject, flags, wrapper);
+ }
+
+ private object GetOrCreateObjectForComInstanceInternal(IntPtr externalComObject, CreateObjectFlags flags, object? wrapperMaybe)
+ {
+ if (externalComObject == IntPtr.Zero)
+ throw new ArgumentNullException(nameof(externalComObject));
+
+ ComWrappers impl = this;
+ object? wrapperMaybeLocal = wrapperMaybe;
+ object? retValue = null;
+ GetOrCreateObjectForComInstanceInternal(ObjectHandleOnStack.Create(ref impl), externalComObject, flags, ObjectHandleOnStack.Create(ref wrapperMaybeLocal), ObjectHandleOnStack.Create(ref retValue));
+
+ return retValue!;
+ }
+
+ [DllImport(RuntimeHelpers.QCall)]
+ private static extern void GetOrCreateObjectForComInstanceInternal(ObjectHandleOnStack comWrappersImpl, IntPtr externalComObject, CreateObjectFlags flags, ObjectHandleOnStack wrapper, ObjectHandleOnStack retValue);
+
+ ///
+ /// Called when a request is made for a collection of objects to be released outside of normal object or COM interface lifetime.
+ ///
+ /// Collection of objects to release.
+ protected abstract void ReleaseObjects(IEnumerable objects);
+
+ // Call to execute the virtual instance function
+ internal static void CallReleaseObjects(ComWrappers? comWrappersImpl, IEnumerable objects)
+ => (comWrappersImpl ?? s_globalInstance!).ReleaseObjects(objects);
+
+ ///
+ /// Register this class's implementation to be used as the single global instance.
+ ///
+ ///
+ /// This function can only be called a single time. Subsequent calls to this function will result
+ /// in a being thrown.
+ ///
+ /// Scenarios where the global instance may be used are:
+ /// * Object tracking via the and flags.
+ /// * Usage of COM related Marshal APIs.
+ ///
+ public void RegisterAsGlobalInstance()
+ {
+ if (null != Interlocked.CompareExchange(ref s_globalInstance, this, null))
+ {
+ throw new InvalidOperationException(SR.InvalidOperation_ResetGlobalComWrappersInstance);
+ }
+ }
+
+ ///
+ /// Get the runtime provided IUnknown implementation.
+ ///
+ /// Function pointer to QueryInterface.
+ /// Function pointer to AddRef.
+ /// Function pointer to Release.
+ protected static void GetIUnknownImpl(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease)
+ => GetIUnknownImplInternal(out fpQueryInterface, out fpAddRef, out fpRelease);
+
+ [DllImport(RuntimeHelpers.QCall)]
+ private static extern void GetIUnknownImplInternal(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease);
+ }
+}
\ No newline at end of file
diff --git a/src/coreclr/src/dlls/mscoree/coreclr/CMakeLists.txt b/src/coreclr/src/dlls/mscoree/coreclr/CMakeLists.txt
index 2fabad169942f..e86e91f6a69e8 100644
--- a/src/coreclr/src/dlls/mscoree/coreclr/CMakeLists.txt
+++ b/src/coreclr/src/dlls/mscoree/coreclr/CMakeLists.txt
@@ -104,7 +104,9 @@ set(CORECLR_LIBRARIES
gcinfo # Condition="'$(TargetCpu)'=='amd64' or '$(TargetCpu)' == 'arm' or '$(TargetCpu)' == 'arm64'"
ildbsymlib
utilcode
+ v3binder
libraries-native
+ interop
)
if(CLR_CMAKE_TARGET_WIN32)
diff --git a/src/coreclr/src/inc/CrstTypes.def b/src/coreclr/src/inc/CrstTypes.def
index cf53f5bdd24ad..5bbf1b2d5198e 100644
--- a/src/coreclr/src/inc/CrstTypes.def
+++ b/src/coreclr/src/inc/CrstTypes.def
@@ -478,6 +478,9 @@ End
Crst RCWCleanupList
End
+Crst ExternalObjectContextCache
+End
+
Crst ReDacl
End
diff --git a/src/coreclr/src/inc/crsttypes.h b/src/coreclr/src/inc/crsttypes.h
index 4bc0665602dbf..3638826f769d3 100644
--- a/src/coreclr/src/inc/crsttypes.h
+++ b/src/coreclr/src/inc/crsttypes.h
@@ -63,113 +63,114 @@ enum CrstType
CrstException = 44,
CrstExecuteManLock = 45,
CrstExecuteManRangeLock = 46,
- CrstFCall = 47,
- CrstFriendAccessCache = 48,
- CrstFuncPtrStubs = 49,
- CrstFusionAppCtx = 50,
- CrstGCCover = 51,
- CrstGCMemoryPressure = 52,
- CrstGlobalStrLiteralMap = 53,
- CrstHandleTable = 54,
- CrstHostAssemblyMap = 55,
- CrstHostAssemblyMapAdd = 56,
- CrstIbcProfile = 57,
- CrstIJWFixupData = 58,
- CrstIJWHash = 59,
- CrstILStubGen = 60,
- CrstInlineTrackingMap = 61,
- CrstInstMethodHashTable = 62,
- CrstInterfaceVTableMap = 63,
- CrstInterop = 64,
- CrstInteropData = 65,
- CrstIOThreadpoolWorker = 66,
- CrstIsJMCMethod = 67,
- CrstISymUnmanagedReader = 68,
- CrstJit = 69,
- CrstJitGenericHandleCache = 70,
- CrstJitInlineTrackingMap = 71,
- CrstJitPerf = 72,
- CrstJumpStubCache = 73,
- CrstLeafLock = 74,
- CrstListLock = 75,
- CrstLoaderAllocator = 76,
- CrstLoaderAllocatorReferences = 77,
- CrstLoaderHeap = 78,
- CrstMda = 79,
- CrstMetadataTracker = 80,
- CrstMethodDescBackpatchInfoTracker = 81,
- CrstModIntPairList = 82,
- CrstModule = 83,
- CrstModuleFixup = 84,
- CrstModuleLookupTable = 85,
- CrstMulticoreJitHash = 86,
- CrstMulticoreJitManager = 87,
- CrstMUThunkHash = 88,
- CrstNativeBinderInit = 89,
- CrstNativeImageCache = 90,
- CrstNativeImageEagerFixups = 91,
- CrstNls = 92,
- CrstNotifyGdb = 93,
- CrstObjectList = 94,
- CrstOnEventManager = 95,
- CrstPatchEntryPoint = 96,
- CrstPEImage = 97,
- CrstPEImagePDBStream = 98,
- CrstPendingTypeLoadEntry = 99,
- CrstPinHandle = 100,
- CrstPinnedByrefValidation = 101,
- CrstProfilerGCRefDataFreeList = 102,
- CrstProfilingAPIStatus = 103,
- CrstPublisherCertificate = 104,
- CrstRCWCache = 105,
- CrstRCWCleanupList = 106,
- CrstRCWRefCache = 107,
- CrstReadyToRunEntryPointToMethodDescMap = 108,
- CrstReDacl = 109,
- CrstReflection = 110,
- CrstReJITGlobalRequest = 111,
- CrstRemoting = 112,
- CrstRetThunkCache = 113,
- CrstRWLock = 114,
- CrstSavedExceptionInfo = 115,
- CrstSaveModuleProfileData = 116,
- CrstSecurityStackwalkCache = 117,
- CrstSharedAssemblyCreate = 118,
- CrstSigConvert = 119,
- CrstSingleUseLock = 120,
- CrstSpecialStatics = 121,
- CrstSqmManager = 122,
- CrstStackSampler = 123,
- CrstStressLog = 124,
- CrstStrongName = 125,
- CrstStubCache = 126,
- CrstStubDispatchCache = 127,
- CrstStubUnwindInfoHeapSegments = 128,
- CrstSyncBlockCache = 129,
- CrstSyncHashLock = 130,
- CrstSystemBaseDomain = 131,
- CrstSystemDomain = 132,
- CrstSystemDomainDelayedUnloadList = 133,
- CrstThreadIdDispenser = 134,
- CrstThreadpoolEventCache = 135,
- CrstThreadpoolTimerQueue = 136,
- CrstThreadpoolWaitThreads = 137,
- CrstThreadpoolWorker = 138,
- CrstThreadStaticDataHashTable = 139,
- CrstThreadStore = 140,
- CrstTieredCompilation = 141,
- CrstTPMethodTable = 142,
- CrstTypeEquivalenceMap = 143,
- CrstTypeIDMap = 144,
- CrstUMEntryThunkCache = 145,
- CrstUMThunkHash = 146,
- CrstUniqueStack = 147,
- CrstUnresolvedClassLock = 148,
- CrstUnwindInfoTableLock = 149,
- CrstVSDIndirectionCellLock = 150,
- CrstWinRTFactoryCache = 151,
- CrstWrapperTemplate = 152,
- kNumberOfCrstTypes = 153
+ CrstExternalObjectContextCache = 47,
+ CrstFCall = 48,
+ CrstFriendAccessCache = 49,
+ CrstFuncPtrStubs = 50,
+ CrstFusionAppCtx = 51,
+ CrstGCCover = 52,
+ CrstGCMemoryPressure = 53,
+ CrstGlobalStrLiteralMap = 54,
+ CrstHandleTable = 55,
+ CrstHostAssemblyMap = 56,
+ CrstHostAssemblyMapAdd = 57,
+ CrstIbcProfile = 58,
+ CrstIJWFixupData = 59,
+ CrstIJWHash = 60,
+ CrstILStubGen = 61,
+ CrstInlineTrackingMap = 62,
+ CrstInstMethodHashTable = 63,
+ CrstInterfaceVTableMap = 64,
+ CrstInterop = 65,
+ CrstInteropData = 66,
+ CrstIOThreadpoolWorker = 67,
+ CrstIsJMCMethod = 68,
+ CrstISymUnmanagedReader = 69,
+ CrstJit = 70,
+ CrstJitGenericHandleCache = 71,
+ CrstJitInlineTrackingMap = 72,
+ CrstJitPerf = 73,
+ CrstJumpStubCache = 74,
+ CrstLeafLock = 75,
+ CrstListLock = 76,
+ CrstLoaderAllocator = 77,
+ CrstLoaderAllocatorReferences = 78,
+ CrstLoaderHeap = 79,
+ CrstMda = 80,
+ CrstMetadataTracker = 81,
+ CrstMethodDescBackpatchInfoTracker = 82,
+ CrstModIntPairList = 83,
+ CrstModule = 84,
+ CrstModuleFixup = 85,
+ CrstModuleLookupTable = 86,
+ CrstMulticoreJitHash = 87,
+ CrstMulticoreJitManager = 88,
+ CrstMUThunkHash = 89,
+ CrstNativeBinderInit = 90,
+ CrstNativeImageCache = 91,
+ CrstNativeImageEagerFixups = 92,
+ CrstNls = 93,
+ CrstNotifyGdb = 94,
+ CrstObjectList = 95,
+ CrstOnEventManager = 96,
+ CrstPatchEntryPoint = 97,
+ CrstPEImage = 98,
+ CrstPEImagePDBStream = 99,
+ CrstPendingTypeLoadEntry = 100,
+ CrstPinHandle = 101,
+ CrstPinnedByrefValidation = 102,
+ CrstProfilerGCRefDataFreeList = 103,
+ CrstProfilingAPIStatus = 104,
+ CrstPublisherCertificate = 105,
+ CrstRCWCache = 106,
+ CrstRCWCleanupList = 107,
+ CrstRCWRefCache = 108,
+ CrstReadyToRunEntryPointToMethodDescMap = 109,
+ CrstReDacl = 110,
+ CrstReflection = 111,
+ CrstReJITGlobalRequest = 112,
+ CrstRemoting = 113,
+ CrstRetThunkCache = 114,
+ CrstRWLock = 115,
+ CrstSavedExceptionInfo = 116,
+ CrstSaveModuleProfileData = 117,
+ CrstSecurityStackwalkCache = 118,
+ CrstSharedAssemblyCreate = 119,
+ CrstSigConvert = 120,
+ CrstSingleUseLock = 121,
+ CrstSpecialStatics = 122,
+ CrstSqmManager = 123,
+ CrstStackSampler = 124,
+ CrstStressLog = 125,
+ CrstStrongName = 126,
+ CrstStubCache = 127,
+ CrstStubDispatchCache = 128,
+ CrstStubUnwindInfoHeapSegments = 129,
+ CrstSyncBlockCache = 130,
+ CrstSyncHashLock = 131,
+ CrstSystemBaseDomain = 132,
+ CrstSystemDomain = 133,
+ CrstSystemDomainDelayedUnloadList = 134,
+ CrstThreadIdDispenser = 135,
+ CrstThreadpoolEventCache = 136,
+ CrstThreadpoolTimerQueue = 137,
+ CrstThreadpoolWaitThreads = 138,
+ CrstThreadpoolWorker = 139,
+ CrstThreadStaticDataHashTable = 140,
+ CrstThreadStore = 141,
+ CrstTieredCompilation = 142,
+ CrstTPMethodTable = 143,
+ CrstTypeEquivalenceMap = 144,
+ CrstTypeIDMap = 145,
+ CrstUMEntryThunkCache = 146,
+ CrstUMThunkHash = 147,
+ CrstUniqueStack = 148,
+ CrstUnresolvedClassLock = 149,
+ CrstUnwindInfoTableLock = 150,
+ CrstVSDIndirectionCellLock = 151,
+ CrstWinRTFactoryCache = 152,
+ CrstWrapperTemplate = 153,
+ kNumberOfCrstTypes = 154
};
#endif // __CRST_TYPES_INCLUDED
@@ -227,6 +228,7 @@ int g_rgCrstLevelMap[] =
0, // CrstException
7, // CrstExecuteManLock
0, // CrstExecuteManRangeLock
+ 0, // CrstExternalObjectContextCache
3, // CrstFCall
7, // CrstFriendAccessCache
7, // CrstFuncPtrStubs
@@ -385,6 +387,7 @@ LPCSTR g_rgCrstNameMap[] =
"CrstException",
"CrstExecuteManLock",
"CrstExecuteManRangeLock",
+ "CrstExternalObjectContextCache",
"CrstFCall",
"CrstFriendAccessCache",
"CrstFuncPtrStubs",
diff --git a/src/coreclr/src/interop/CMakeLists.txt b/src/coreclr/src/interop/CMakeLists.txt
new file mode 100644
index 0000000000000..d7eaa1b04ae63
--- /dev/null
+++ b/src/coreclr/src/interop/CMakeLists.txt
@@ -0,0 +1,36 @@
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+include_directories(BEFORE inc)
+
+set(INTEROP_COMMON_SOURCES
+ interoplib.cpp
+)
+
+set(INTEROP_COMMON_HEADERS
+ inc/interoplibimports.h
+ inc/interoplib.h
+ platform.h
+)
+
+set(INTEROP_SOURCES
+ ${INTEROP_COMMON_SOURCES}
+)
+
+set(INTEROP_HEADERS
+ ${INTEROP_COMMON_HEADERS}
+)
+
+if (WIN32)
+ list(APPEND INTEROP_SOURCES
+ ${INTEROP_HEADERS}
+ comwrappers.cpp
+ comwrappers.hpp
+ trackerobjectmanager.cpp
+ referencetrackertypes.hpp)
+endif(WIN32)
+
+convert_to_absolute_path(INTEROP_SOURCES ${INTEROP_SOURCES})
+
+add_library_clr(interop
+ STATIC
+ ${INTEROP_SOURCES}
+)
diff --git a/src/coreclr/src/interop/comwrappers.cpp b/src/coreclr/src/interop/comwrappers.cpp
new file mode 100644
index 0000000000000..d7fa94bab1337
--- /dev/null
+++ b/src/coreclr/src/interop/comwrappers.cpp
@@ -0,0 +1,717 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#include "comwrappers.hpp"
+#include
+
+#include // placement new
+
+using OBJECTHANDLE = InteropLib::OBJECTHANDLE;
+using AllocScenario = InteropLibImports::AllocScenario;
+
+namespace ABI
+{
+ //---------------------------------------------------------------------------------
+ // Dispatch section of the ManagedObjectWrapper (MOW)
+ //
+ // Within the dispatch section, the ManagedObjectWrapper itself is inserted at a defined
+ // aligned location. This allows the simple masking of the any ComInterfaceDispatch* to get
+ // access to the ManagedObjectWrapper by masking the lower N bits. Below is a sketch of how
+ // the dispatch section would appear in a 32-bit process for a 16 bit alignment.
+ //
+ // 16 byte aligned Vtable
+ // +-----------+
+ // | MOW this |
+ // +-----------+ +-----+
+ // COM IP-->| VTable ptr|----------------------------->|slot1|
+ // +-----------+ +-----+ +-----+
+ // | VTable ptr|---------->|slot1| |slot2|
+ // +-----------+ +-----+ + +
+ // | VTable ptr| | ....| | ... |
+ // +-----------+ + + + +
+ // | MOW this | |slotN| |slotN|
+ // + + +-----+ +-----+
+ // | .... |
+ // +-----------+
+ //
+ // A 16 byte alignment permits a ratio of 3:1 COM vtables to ManagedObjectWrapper 'this'
+ // pointers in a 32-bit process, but in a 64-bit process the mapping is only 1:1.
+ // See the dispatch section building API below for an example of how indexing works.
+ //--------------------------------------------------------------------------------
+
+ struct ComInterfaceDispatch
+ {
+ const void* vtable;
+ };
+ ABI_ASSERT(sizeof(ComInterfaceDispatch) == sizeof(void*));
+
+ const size_t DispatchAlignmentThisPtr = 16; // Should be a power of 2.
+ const intptr_t DispatchThisPtrMask = ~(DispatchAlignmentThisPtr - 1);
+ ABI_ASSERT(sizeof(void*) < DispatchAlignmentThisPtr);
+
+ const intptr_t AlignmentThisPtrMaxPadding = DispatchAlignmentThisPtr - sizeof(void*);
+ const size_t EntriesPerThisPtr = (DispatchAlignmentThisPtr / sizeof(void*)) - 1;
+
+ // Check if the instance can dispatch according to the ABI.
+ bool IsAbleToDispatch(_In_ ComInterfaceDispatch* disp)
+ {
+ return (reinterpret_cast(disp) & DispatchThisPtrMask) != 0;
+ }
+
+ // Given the number of dispatch entries, compute the needed number of 'this' pointer entries.
+ constexpr size_t ComputeThisPtrForDispatchSection(_In_ size_t dispatchCount)
+ {
+ return (dispatchCount / ABI::EntriesPerThisPtr) + ((dispatchCount % ABI::EntriesPerThisPtr) == 0 ? 0 : 1);
+ }
+
+ // Given a pointer and a padding allowance, attempt to find an offset into
+ // the memory that is properly aligned for the dispatch section.
+ char* AlignDispatchSection(_In_ char* section, _In_ intptr_t extraPadding)
+ {
+ _ASSERTE(section != nullptr);
+
+ // If the dispatch section is not properly aligned by default, we
+ // utilize the padding to make sure the dispatch section is aligned.
+ while ((reinterpret_cast(section) % ABI::DispatchAlignmentThisPtr) != 0)
+ {
+ // Check if there is padding to attempt an alignment.
+ if (extraPadding <= 0)
+ return nullptr;
+
+ extraPadding -= sizeof(void*);
+
+#ifdef _DEBUG
+ // Poison unused portions of the section.
+ ::memset(section, 0xff, sizeof(void*));
+#endif
+
+ section += sizeof(void*);
+ }
+
+ return section;
+ }
+
+ struct ComInterfaceEntry
+ {
+ GUID IID;
+ const void* Vtable;
+ };
+
+ struct EntrySet
+ {
+ const ComInterfaceEntry* start;
+ int32_t count;
+ };
+
+ // Populate the dispatch section with the entry sets
+ ComInterfaceDispatch* PopulateDispatchSection(
+ _In_ void* thisPtr,
+ _In_ void* dispatchSection,
+ _In_ size_t entrySetCount,
+ _In_ const EntrySet* entrySets)
+ {
+ // Define dispatch section iterator.
+ const void** currDisp = reinterpret_cast(dispatchSection);
+
+ // Keep rolling count of dispatch entries.
+ int32_t dispCount = 0;
+
+ // Iterate over all interface entry sets.
+ const EntrySet* curr = entrySets;
+ const EntrySet* end = entrySets + entrySetCount;
+ for (; curr != end; ++curr)
+ {
+ const ComInterfaceEntry* currEntry = curr->start;
+ int32_t entryCount = curr->count;
+
+ // Update dispatch section with 'this' pointer and vtables.
+ for (int32_t i = 0; i < entryCount; ++i, ++dispCount, ++currEntry)
+ {
+ // Insert the 'this' pointer at the appropriate locations
+ // e.g.:
+ // 32-bit | 64-bit
+ // (0 * 4) % 16 = 0 | (0 * 8) % 16 = 0
+ // (1 * 4) % 16 = 4 | (1 * 8) % 16 = 8
+ // (2 * 4) % 16 = 8 | (2 * 8) % 16 = 0
+ // (3 * 4) % 16 = 12 | (3 * 8) % 16 = 8
+ // (4 * 4) % 16 = 0 | (4 * 8) % 16 = 0
+ // (5 * 4) % 16 = 4 | (5 * 8) % 16 = 8
+ //
+ if (((dispCount * sizeof(void*)) % ABI::DispatchAlignmentThisPtr) == 0)
+ {
+ *currDisp++ = thisPtr;
+ ++dispCount;
+ }
+
+ // Fill in the dispatch entry
+ *currDisp++ = currEntry->Vtable;
+ }
+ }
+
+ return reinterpret_cast(dispatchSection);
+ }
+
+ // Given the entry index, compute the dispatch index.
+ ComInterfaceDispatch* IndexIntoDispatchSection(_In_ int32_t i, _In_ ComInterfaceDispatch* dispatches)
+ {
+ // Convert the supplied zero based index into what it represents as a count.
+ const size_t count = static_cast(i) + 1;
+
+ // Based on the supplied count, compute how many previous 'this' pointers would be
+ // required in the dispatch section and add that to the supplied index to get the
+ // index into the dispatch section.
+ const size_t idx = ComputeThisPtrForDispatchSection(count) + i;
+
+ ComInterfaceDispatch* disp = dispatches + idx;
+ _ASSERTE(IsAbleToDispatch(disp));
+ return disp;
+ }
+
+ // Given a dispatcher instance, return the associated ManagedObjectWrapper.
+ ManagedObjectWrapper* ToManagedObjectWrapper(_In_ ComInterfaceDispatch* disp)
+ {
+ _ASSERTE(disp != nullptr && disp->vtable != nullptr);
+
+ intptr_t wrapperMaybe = reinterpret_cast(disp) & DispatchThisPtrMask;
+ return *reinterpret_cast(wrapperMaybe);
+ }
+}
+
+namespace
+{
+ HRESULT STDMETHODCALLTYPE ManagedObjectWrapper_QueryInterface(
+ _In_ ABI::ComInterfaceDispatch* disp,
+ /* [in] */ REFIID riid,
+ /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject)
+ {
+ ManagedObjectWrapper* wrapper = ABI::ToManagedObjectWrapper(disp);
+ return wrapper->QueryInterface(riid, ppvObject);
+ }
+
+ ULONG STDMETHODCALLTYPE ManagedObjectWrapper_AddRef(_In_ ABI::ComInterfaceDispatch* disp)
+ {
+ ManagedObjectWrapper* wrapper = ABI::ToManagedObjectWrapper(disp);
+ return wrapper->AddRef();
+ }
+
+ ULONG STDMETHODCALLTYPE ManagedObjectWrapper_Release(_In_ ABI::ComInterfaceDispatch* disp)
+ {
+ ManagedObjectWrapper* wrapper = ABI::ToManagedObjectWrapper(disp);
+ return wrapper->Release();
+ }
+
+ // Hard-coded ManagedObjectWrapper IUnknown vtable.
+ const struct
+ {
+ decltype(&ManagedObjectWrapper_QueryInterface) QueryInterface;
+ decltype(&ManagedObjectWrapper_AddRef) AddRef;
+ decltype(&ManagedObjectWrapper_Release) Release;
+ } ManagedObjectWrapper_IUnknownImpl {
+ &ManagedObjectWrapper_QueryInterface,
+ &ManagedObjectWrapper_AddRef,
+ &ManagedObjectWrapper_Release
+ };
+
+ static_assert(sizeof(ManagedObjectWrapper_IUnknownImpl) == (3 * sizeof(void*)), "Unexpected vtable size");
+}
+
+namespace
+{
+ const int32_t TrackerRefShift = 32;
+ const ULONGLONG TrackerRefCounter = ULONGLONG{ 1 } << TrackerRefShift;
+ const ULONGLONG ComRefCounter = ULONGLONG{ 1 };
+ const ULONGLONG TrackerRefZero = 0x0000000080000000;
+ const ULONGLONG TrackerRefCountMask = 0xffffffff00000000;
+ const ULONGLONG ComRefCountMask = 0x000000007fffffff;
+ const ULONGLONG RefCountMask = 0xffffffff7fffffff;
+
+ constexpr ULONG GetTrackerCount(_In_ ULONGLONG c)
+ {
+ return static_cast((c & TrackerRefCountMask) >> TrackerRefShift);
+ }
+
+ constexpr ULONG GetComCount(_In_ ULONGLONG c)
+ {
+ return static_cast(c & ComRefCountMask);
+ }
+
+ ULONG STDMETHODCALLTYPE TrackerTarget_AddRefFromReferenceTracker(_In_ ABI::ComInterfaceDispatch* disp)
+ {
+ _ASSERTE(disp != nullptr && disp->vtable != nullptr);
+
+ ManagedObjectWrapper* wrapper = ABI::ToManagedObjectWrapper(disp);
+ _ASSERTE(wrapper->IsSet(CreateComInterfaceFlagsEx::TrackerSupport));
+
+ return wrapper->AddRefFromReferenceTracker();
+ }
+
+ ULONG STDMETHODCALLTYPE TrackerTarget_ReleaseFromReferenceTracker(_In_ ABI::ComInterfaceDispatch* disp)
+ {
+ _ASSERTE(disp != nullptr && disp->vtable != nullptr);
+
+ ManagedObjectWrapper* wrapper = ABI::ToManagedObjectWrapper(disp);
+ _ASSERTE(wrapper->IsSet(CreateComInterfaceFlagsEx::TrackerSupport));
+
+ return wrapper->ReleaseFromReferenceTracker();
+ }
+
+ HRESULT STDMETHODCALLTYPE TrackerTarget_Peg(_In_ ABI::ComInterfaceDispatch* disp)
+ {
+ _ASSERTE(disp != nullptr && disp->vtable != nullptr);
+
+ ManagedObjectWrapper* wrapper = ABI::ToManagedObjectWrapper(disp);
+ _ASSERTE(wrapper->IsSet(CreateComInterfaceFlagsEx::TrackerSupport));
+
+ return wrapper->Peg();
+ }
+
+ HRESULT STDMETHODCALLTYPE TrackerTarget_Unpeg(_In_ ABI::ComInterfaceDispatch* disp)
+ {
+ _ASSERTE(disp != nullptr && disp->vtable != nullptr);
+
+ ManagedObjectWrapper* wrapper = ABI::ToManagedObjectWrapper(disp);
+ _ASSERTE(wrapper->IsSet(CreateComInterfaceFlagsEx::TrackerSupport));
+
+ return wrapper->Unpeg();
+ }
+
+ // Hard-coded IReferenceTrackerTarget vtable
+ const struct
+ {
+ decltype(ManagedObjectWrapper_IUnknownImpl) IUnknownImpl;
+ decltype(&TrackerTarget_AddRefFromReferenceTracker) AddRefFromReferenceTracker;
+ decltype(&TrackerTarget_ReleaseFromReferenceTracker) ReleaseFromReferenceTracker;
+ decltype(&TrackerTarget_Peg) Peg;
+ decltype(&TrackerTarget_Unpeg) Unpeg;
+ } ManagedObjectWrapper_IReferenceTrackerTargetImpl {
+ ManagedObjectWrapper_IUnknownImpl,
+ &TrackerTarget_AddRefFromReferenceTracker,
+ &TrackerTarget_ReleaseFromReferenceTracker,
+ &TrackerTarget_Peg,
+ &TrackerTarget_Unpeg
+ };
+
+ static_assert(sizeof(ManagedObjectWrapper_IReferenceTrackerTargetImpl) == (7 * sizeof(void*)), "Unexpected vtable size");
+}
+
+void ManagedObjectWrapper::GetIUnknownImpl(
+ _Out_ void** fpQueryInterface,
+ _Out_ void** fpAddRef,
+ _Out_ void** fpRelease)
+{
+ _ASSERTE(fpQueryInterface != nullptr
+ && fpAddRef != nullptr
+ && fpRelease != nullptr);
+
+ *fpQueryInterface = ManagedObjectWrapper_IUnknownImpl.QueryInterface;
+ *fpAddRef = ManagedObjectWrapper_IUnknownImpl.AddRef;
+ *fpRelease = ManagedObjectWrapper_IUnknownImpl.Release;
+}
+
+ManagedObjectWrapper* ManagedObjectWrapper::MapFromIUnknown(_In_ IUnknown* pUnk)
+{
+ _ASSERTE(pUnk != nullptr);
+
+ // If the first Vtable entry is part of the ManagedObjectWrapper IUnknown impl,
+ // we know how to interpret the IUnknown.
+ void** vtable = *reinterpret_cast(pUnk);
+ if (*vtable != ManagedObjectWrapper_IUnknownImpl.QueryInterface)
+ return nullptr;
+
+ ABI::ComInterfaceDispatch* disp = reinterpret_cast(pUnk);
+ return ABI::ToManagedObjectWrapper(disp);
+}
+
+HRESULT ManagedObjectWrapper::Create(
+ _In_ InteropLib::Com::CreateComInterfaceFlags flagsRaw,
+ _In_ OBJECTHANDLE objectHandle,
+ _In_ int32_t userDefinedCount,
+ _In_ ABI::ComInterfaceEntry* userDefined,
+ _Outptr_ ManagedObjectWrapper** mow)
+{
+ _ASSERTE(objectHandle != nullptr && mow != nullptr);
+
+ auto flags = static_cast(flagsRaw);
+ _ASSERTE((flags & CreateComInterfaceFlagsEx::InternalMask) == CreateComInterfaceFlagsEx::None);
+
+ // Maximum number of runtime supplied vtables.
+ ABI::ComInterfaceEntry runtimeDefinedLocal[4];
+ int32_t runtimeDefinedCount = 0;
+
+ // Check if the caller will provide the IUnknown table.
+ if ((flags & CreateComInterfaceFlagsEx::CallerDefinedIUnknown) == CreateComInterfaceFlagsEx::None)
+ {
+ ABI::ComInterfaceEntry& curr = runtimeDefinedLocal[runtimeDefinedCount++];
+ curr.IID = __uuidof(IUnknown);
+ curr.Vtable = &ManagedObjectWrapper_IUnknownImpl;
+ }
+
+ // Check if the caller wants tracker support.
+ if ((flags & CreateComInterfaceFlagsEx::TrackerSupport) == CreateComInterfaceFlagsEx::TrackerSupport)
+ {
+ ABI::ComInterfaceEntry& curr = runtimeDefinedLocal[runtimeDefinedCount++];
+ curr.IID = __uuidof(IReferenceTrackerTarget);
+ curr.Vtable = &ManagedObjectWrapper_IReferenceTrackerTargetImpl;
+ }
+
+ _ASSERTE(runtimeDefinedCount <= ARRAYSIZE(runtimeDefinedLocal));
+
+ // Compute size for ManagedObjectWrapper instance.
+ const size_t totalRuntimeDefinedSize = runtimeDefinedCount * sizeof(ABI::ComInterfaceEntry);
+ const size_t totalDefinedCount = static_cast(runtimeDefinedCount) + userDefinedCount;
+
+ // Compute the total entry size of dispatch section.
+ const size_t totalDispatchSectionCount = ABI::ComputeThisPtrForDispatchSection(totalDefinedCount) + totalDefinedCount;
+ const size_t totalDispatchSectionSize = totalDispatchSectionCount * sizeof(void*);
+
+ // Allocate memory for the ManagedObjectWrapper.
+ char* wrapperMem = (char*)InteropLibImports::MemAlloc(sizeof(ManagedObjectWrapper) + totalRuntimeDefinedSize + totalDispatchSectionSize + ABI::AlignmentThisPtrMaxPadding, AllocScenario::ManagedObjectWrapper);
+ if (wrapperMem == nullptr)
+ return E_OUTOFMEMORY;
+
+ // Compute Runtime defined offset.
+ char* runtimeDefinedOffset = wrapperMem + sizeof(ManagedObjectWrapper);
+
+ // Copy in runtime supplied COM interface entries.
+ ABI::ComInterfaceEntry* runtimeDefined = nullptr;
+ if (0 < runtimeDefinedCount)
+ {
+ ::memcpy(runtimeDefinedOffset, runtimeDefinedLocal, totalRuntimeDefinedSize);
+ runtimeDefined = reinterpret_cast(runtimeDefinedOffset);
+ }
+
+ // Compute the dispatch section offset and ensure it is aligned.
+ char* dispatchSectionOffset = runtimeDefinedOffset + totalRuntimeDefinedSize;
+ dispatchSectionOffset = ABI::AlignDispatchSection(dispatchSectionOffset, ABI::AlignmentThisPtrMaxPadding);
+ if (dispatchSectionOffset == nullptr)
+ return E_UNEXPECTED;
+
+ // Define the sets for the tables to insert
+ const ABI::EntrySet AllEntries[] =
+ {
+ { runtimeDefined, runtimeDefinedCount },
+ { userDefined, userDefinedCount }
+ };
+
+ ABI::ComInterfaceDispatch* dispSection = ABI::PopulateDispatchSection(wrapperMem, dispatchSectionOffset, ARRAYSIZE(AllEntries), AllEntries);
+
+ ManagedObjectWrapper* wrapper = new (wrapperMem) ManagedObjectWrapper
+ {
+ flags,
+ objectHandle,
+ runtimeDefinedCount,
+ runtimeDefined,
+ userDefinedCount,
+ userDefined,
+ dispSection
+ };
+
+ *mow = wrapper;
+ return S_OK;
+}
+
+void ManagedObjectWrapper::Destroy(_In_ ManagedObjectWrapper* wrapper)
+{
+ _ASSERTE(wrapper != nullptr);
+
+ // Manually trigger the destructor since placement
+ // new was used to allocate the object.
+ wrapper->~ManagedObjectWrapper();
+ InteropLibImports::MemFree(wrapper, AllocScenario::ManagedObjectWrapper);
+}
+
+ManagedObjectWrapper::ManagedObjectWrapper(
+ _In_ CreateComInterfaceFlagsEx flags,
+ _In_ OBJECTHANDLE objectHandle,
+ _In_ int32_t runtimeDefinedCount,
+ _In_ const ABI::ComInterfaceEntry* runtimeDefined,
+ _In_ int32_t userDefinedCount,
+ _In_ const ABI::ComInterfaceEntry* userDefined,
+ _In_ ABI::ComInterfaceDispatch* dispatches)
+ : Target{ nullptr }
+ , _runtimeDefinedCount{ runtimeDefinedCount }
+ , _userDefinedCount{ userDefinedCount }
+ , _runtimeDefined{ runtimeDefined }
+ , _userDefined{ userDefined }
+ , _dispatches{ dispatches }
+ , _refCount{ 1 }
+ , _flags{ flags }
+{
+ bool wasSet = TrySetObjectHandle(objectHandle);
+ _ASSERTE(wasSet);
+}
+
+ManagedObjectWrapper::~ManagedObjectWrapper()
+{
+ // If the target isn't null, then a managed object
+ // is going to leak.
+ _ASSERTE(Target == nullptr);
+}
+
+ULONGLONG ManagedObjectWrapper::UniversalRelease(_In_ ULONGLONG dec)
+{
+ OBJECTHANDLE local = Target;
+
+ LONGLONG refCount;
+ if (dec == ComRefCounter)
+ {
+ _ASSERTE(dec == 1);
+ refCount = ::InterlockedDecrement64(&_refCount);
+ }
+ else
+ {
+ _ASSERTE(dec == TrackerRefCounter);
+ LONGLONG prev;
+ do
+ {
+ prev = _refCount;
+ refCount = prev - dec;
+ } while (::InterlockedCompareExchange64(&_refCount, refCount, prev) != prev);
+ }
+
+ // It is possible that a target wasn't set during an
+ // attempt to reactive the wrapper.
+ if ((RefCountMask & refCount) == 0 && local != nullptr)
+ {
+ _ASSERTE(!IsSet(CreateComInterfaceFlagsEx::IsPegged));
+ _ASSERTE(refCount == TrackerRefZero || refCount == 0);
+
+ // Attempt to reset the target if its current value is the same.
+ // It is possible the wrapper is in the middle of being reactivated.
+ (void)TrySetObjectHandle(nullptr, local);
+
+ // Tell the runtime to delete the managed object instance handle.
+ InteropLibImports::DeleteObjectInstanceHandle(local);
+ }
+
+ return refCount;
+}
+
+void* ManagedObjectWrapper::As(_In_ REFIID riid)
+{
+ // Find target interface and return dispatcher or null if not found.
+ for (int32_t i = 0; i < _runtimeDefinedCount; ++i)
+ {
+ if (IsEqualGUID(_runtimeDefined[i].IID, riid))
+ {
+ return ABI::IndexIntoDispatchSection(i, _dispatches);
+ }
+ }
+
+ for (int32_t i = 0; i < _userDefinedCount; ++i)
+ {
+ if (IsEqualGUID(_userDefined[i].IID, riid))
+ {
+ return ABI::IndexIntoDispatchSection(i + _runtimeDefinedCount, _dispatches);
+ }
+ }
+
+ return nullptr;
+}
+
+bool ManagedObjectWrapper::TrySetObjectHandle(_In_ OBJECTHANDLE objectHandle, _In_ OBJECTHANDLE current)
+{
+ return (::InterlockedCompareExchangePointer(&Target, objectHandle, current) == current);
+}
+
+bool ManagedObjectWrapper::IsSet(_In_ CreateComInterfaceFlagsEx flag) const
+{
+ return (_flags & flag) != CreateComInterfaceFlagsEx::None;
+}
+
+void ManagedObjectWrapper::SetFlag(_In_ CreateComInterfaceFlagsEx flag)
+{
+ LONG setMask = (LONG)flag;
+ ::InterlockedOr((LONG*)&_flags, setMask);
+}
+
+void ManagedObjectWrapper::ResetFlag(_In_ CreateComInterfaceFlagsEx flag)
+{
+ LONG resetMask = (LONG)~flag;
+ ::InterlockedAnd((LONG*)&_flags, resetMask);
+}
+
+ULONG ManagedObjectWrapper::IsActiveAddRef()
+{
+ ULONG count = GetComCount(::InterlockedIncrement64(&_refCount));
+ if (count == 1)
+ {
+ // Ensure the current target is null.
+ ::InterlockedExchangePointer(&Target, nullptr);
+ }
+
+ return count;
+}
+
+ULONG ManagedObjectWrapper::AddRefFromReferenceTracker()
+{
+ LONGLONG prev;
+ LONGLONG curr;
+ do
+ {
+ prev = _refCount;
+ curr = prev + TrackerRefCounter;
+ } while (::InterlockedCompareExchange64(&_refCount, curr, prev) != prev);
+
+ return GetTrackerCount(curr);
+}
+
+ULONG ManagedObjectWrapper::ReleaseFromReferenceTracker()
+{
+ return GetTrackerCount(UniversalRelease(TrackerRefCounter));
+}
+
+HRESULT ManagedObjectWrapper::Peg()
+{
+ SetFlag(CreateComInterfaceFlagsEx::IsPegged);
+ return S_OK;
+}
+
+HRESULT ManagedObjectWrapper::Unpeg()
+{
+ ResetFlag(CreateComInterfaceFlagsEx::IsPegged);
+ return S_OK;
+}
+
+HRESULT ManagedObjectWrapper::QueryInterface(
+ /* [in] */ REFIID riid,
+ /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject)
+{
+ if (ppvObject == nullptr)
+ return E_POINTER;
+
+ // Find target interface
+ *ppvObject = As(riid);
+ if (*ppvObject == nullptr)
+ return E_NOINTERFACE;
+
+ (void)AddRef();
+ return S_OK;
+}
+
+ULONG ManagedObjectWrapper::AddRef(void)
+{
+ return GetComCount(::InterlockedIncrement64(&_refCount));
+}
+
+ULONG ManagedObjectWrapper::Release(void)
+{
+ return GetComCount(UniversalRelease(ComRefCounter));
+}
+
+namespace
+{
+ const size_t LiveContextSentinel = 0x0a110ced;
+ const size_t DeadContextSentinel = 0xdeaddead;
+}
+
+NativeObjectWrapperContext* NativeObjectWrapperContext::MapFromRuntimeContext(_In_ void* cxtMaybe)
+{
+ _ASSERTE(cxtMaybe != nullptr);
+
+ // Convert the supplied context
+ char* cxtRaw = reinterpret_cast(cxtMaybe);
+ cxtRaw -= sizeof(NativeObjectWrapperContext);
+ NativeObjectWrapperContext* cxt = reinterpret_cast(cxtRaw);
+
+#ifdef _DEBUG
+ _ASSERTE(cxt->_sentinel == LiveContextSentinel);
+#endif
+
+ return cxt;
+}
+
+HRESULT NativeObjectWrapperContext::Create(
+ _In_ IUnknown* external,
+ _In_ InteropLib::Com::CreateObjectFlags flags,
+ _In_ size_t runtimeContextSize,
+ _Outptr_ NativeObjectWrapperContext** context)
+{
+ _ASSERTE(external != nullptr && context != nullptr);
+
+ HRESULT hr;
+
+ ComHolder trackerObject;
+ if (flags & InteropLib::Com::CreateObjectFlags_TrackerObject)
+ {
+ hr = external->QueryInterface(&trackerObject);
+ if (SUCCEEDED(hr))
+ RETURN_IF_FAILED(TrackerObjectManager::OnIReferenceTrackerFound(trackerObject));
+ }
+
+ // Allocate memory for the RCW
+ char* cxtMem = (char*)InteropLibImports::MemAlloc(sizeof(NativeObjectWrapperContext) + runtimeContextSize, AllocScenario::NativeObjectWrapper);
+ if (cxtMem == nullptr)
+ return E_OUTOFMEMORY;
+
+ void* runtimeContext = cxtMem + sizeof(NativeObjectWrapperContext);
+
+ // Contract specifically requires zeroing out runtime context.
+ ::memset(runtimeContext, 0, runtimeContextSize);
+
+ NativeObjectWrapperContext* contextLocal = new (cxtMem) NativeObjectWrapperContext{ runtimeContext, trackerObject };
+
+ if (trackerObject != nullptr)
+ {
+ // Inform the tracker object manager
+ _ASSERTE(flags & InteropLib::Com::CreateObjectFlags_TrackerObject);
+ hr = TrackerObjectManager::AfterWrapperCreated(trackerObject);
+ if (FAILED(hr))
+ {
+ Destroy(contextLocal);
+ return hr;
+ }
+ }
+
+ *context = contextLocal;
+ return S_OK;
+}
+
+void NativeObjectWrapperContext::Destroy(_In_ NativeObjectWrapperContext* wrapper)
+{
+ _ASSERTE(wrapper != nullptr);
+
+ // Manually trigger the destructor since placement
+ // new was used to allocate the object.
+ wrapper->~NativeObjectWrapperContext();
+ InteropLibImports::MemFree(wrapper, AllocScenario::NativeObjectWrapper);
+}
+
+NativeObjectWrapperContext::NativeObjectWrapperContext(_In_ void* runtimeContext, _In_opt_ IReferenceTracker* trackerObject)
+ : _trackerObject{ trackerObject }
+ , _runtimeContext{ runtimeContext }
+ , _isValidTracker{ (trackerObject != nullptr ? TRUE : FALSE) }
+#ifdef _DEBUG
+ , _sentinel{ LiveContextSentinel }
+#endif
+{
+ if (_isValidTracker == TRUE)
+ (void)_trackerObject->AddRef();
+}
+
+NativeObjectWrapperContext::~NativeObjectWrapperContext()
+{
+ DisconnectTracker();
+
+#ifdef _DEBUG
+ _sentinel = DeadContextSentinel;
+#endif
+}
+
+void* NativeObjectWrapperContext::GetRuntimeContext() const noexcept
+{
+ return _runtimeContext;
+}
+
+IReferenceTracker* NativeObjectWrapperContext::GetReferenceTracker() const noexcept
+{
+ return ((_isValidTracker == TRUE) ? _trackerObject : nullptr);
+}
+
+void NativeObjectWrapperContext::DisconnectTracker() noexcept
+{
+ // Attempt to disconnect from the tracker.
+ if (TRUE == ::InterlockedCompareExchange((LONG*)&_isValidTracker, FALSE, TRUE))
+ (void)_trackerObject->Release();
+}
diff --git a/src/coreclr/src/interop/comwrappers.hpp b/src/coreclr/src/interop/comwrappers.hpp
new file mode 100644
index 0000000000000..d2337746e39b8
--- /dev/null
+++ b/src/coreclr/src/interop/comwrappers.hpp
@@ -0,0 +1,249 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#ifndef _INTEROP_COMWRAPPERS_H_
+#define _INTEROP_COMWRAPPERS_H_
+
+#include "platform.h"
+#include
+#include "referencetrackertypes.hpp"
+
+enum class CreateComInterfaceFlagsEx : int32_t
+{
+ None = InteropLib::Com::CreateComInterfaceFlags_None,
+ CallerDefinedIUnknown = InteropLib::Com::CreateComInterfaceFlags_CallerDefinedIUnknown,
+ TrackerSupport = InteropLib::Com::CreateComInterfaceFlags_TrackerSupport,
+
+ // Highest bit is reserved for internal usage
+ IsPegged = 1 << 31,
+
+ InternalMask = IsPegged,
+};
+
+DEFINE_ENUM_FLAG_OPERATORS(CreateComInterfaceFlagsEx);
+
+// Forward declarations
+namespace ABI
+{
+ struct ComInterfaceDispatch;
+ struct ComInterfaceEntry;
+}
+
+// Class for wrapping a managed object and projecting it in a non-managed environment
+class ManagedObjectWrapper
+{
+public:
+ Volatile Target;
+
+private:
+ const int32_t _runtimeDefinedCount;
+ const int32_t _userDefinedCount;
+ const ABI::ComInterfaceEntry* _runtimeDefined;
+ const ABI::ComInterfaceEntry* _userDefined;
+ ABI::ComInterfaceDispatch* _dispatches;
+
+ LONGLONG _refCount;
+ Volatile _flags;
+
+public: // static
+ // Get the implementation for IUnknown.
+ static void GetIUnknownImpl(
+ _Out_ void** fpQueryInterface,
+ _Out_ void** fpAddRef,
+ _Out_ void** fpRelease);
+
+ // Convert the IUnknown if the instance is a ManagedObjectWrapper
+ // into a ManagedObjectWrapper, otherwise null.
+ static ManagedObjectWrapper* MapFromIUnknown(_In_ IUnknown* pUnk);
+
+ // Create a ManagedObjectWrapper instance
+ static HRESULT Create(
+ _In_ InteropLib::Com::CreateComInterfaceFlags flags,
+ _In_ InteropLib::OBJECTHANDLE objectHandle,
+ _In_ int32_t userDefinedCount,
+ _In_ ABI::ComInterfaceEntry* userDefined,
+ _Outptr_ ManagedObjectWrapper** mow);
+
+ // Destroy the instance
+ static void Destroy(_In_ ManagedObjectWrapper* wrapper);
+
+private:
+ ManagedObjectWrapper(
+ _In_ CreateComInterfaceFlagsEx flags,
+ _In_ InteropLib::OBJECTHANDLE objectHandle,
+ _In_ int32_t runtimeDefinedCount,
+ _In_ const ABI::ComInterfaceEntry* runtimeDefined,
+ _In_ int32_t userDefinedCount,
+ _In_ const ABI::ComInterfaceEntry* userDefined,
+ _In_ ABI::ComInterfaceDispatch* dispatches);
+
+ ~ManagedObjectWrapper();
+
+ // Represents a single implementation of how to release
+ // the wrapper. Supplied with a decrementing value.
+ ULONGLONG UniversalRelease(_In_ ULONGLONG dec);
+
+public:
+ void* As(_In_ REFIID riid);
+ // Attempt to set the target object handle based on an assumed current value.
+ bool TrySetObjectHandle(_In_ InteropLib::OBJECTHANDLE objectHandle, _In_ InteropLib::OBJECTHANDLE current = nullptr);
+ bool IsSet(_In_ CreateComInterfaceFlagsEx flag) const;
+ void SetFlag(_In_ CreateComInterfaceFlagsEx flag);
+ void ResetFlag(_In_ CreateComInterfaceFlagsEx flag);
+
+ // Used while validating wrapper is active.
+ ULONG IsActiveAddRef();
+
+public: // IReferenceTrackerTarget
+ ULONG AddRefFromReferenceTracker();
+ ULONG ReleaseFromReferenceTracker();
+ HRESULT Peg();
+ HRESULT Unpeg();
+
+public: // Lifetime
+ HRESULT QueryInterface(
+ /* [in] */ REFIID riid,
+ /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR * __RPC_FAR * ppvObject);
+ ULONG AddRef(void);
+ ULONG Release(void);
+};
+
+// ABI contract. This below offset is assumed in managed code.
+ABI_ASSERT(offsetof(ManagedObjectWrapper, Target) == 0);
+
+// Class for connecting a native COM object to a managed object instance
+class NativeObjectWrapperContext
+{
+#ifdef _DEBUG
+ size_t _sentinel;
+#endif
+
+ IReferenceTracker* _trackerObject;
+ void* _runtimeContext;
+ Volatile _isValidTracker;
+
+public: // static
+ // Convert a context pointer into a NativeObjectWrapperContext.
+ static NativeObjectWrapperContext* MapFromRuntimeContext(_In_ void* cxt);
+
+ // Create a NativeObjectWrapperContext instance
+ static HRESULT NativeObjectWrapperContext::Create(
+ _In_ IUnknown* external,
+ _In_ InteropLib::Com::CreateObjectFlags flags,
+ _In_ size_t runtimeContextSize,
+ _Outptr_ NativeObjectWrapperContext** context);
+
+ // Destroy the instance
+ static void Destroy(_In_ NativeObjectWrapperContext* wrapper);
+
+private:
+ NativeObjectWrapperContext(_In_ void* runtimeContext, _In_opt_ IReferenceTracker* trackerObject);
+ ~NativeObjectWrapperContext();
+
+public:
+ // Get the associated runtime context for this context.
+ void* GetRuntimeContext() const noexcept;
+
+ // Get the IReferenceTracker instance.
+ IReferenceTracker* GetReferenceTracker() const noexcept;
+
+ // Disconnect reference tracker instance.
+ void DisconnectTracker() noexcept;
+};
+
+// Manage native object wrappers that support IReferenceTracker.
+class TrackerObjectManager
+{
+public:
+ // Called when an IReferenceTracker instance is found.
+ static HRESULT OnIReferenceTrackerFound(_In_ IReferenceTracker* obj);
+
+ // Called after wrapper has been created.
+ static HRESULT AfterWrapperCreated(_In_ IReferenceTracker* obj);
+
+ // Called before wrapper is about to be destroyed (the same lifetime as short weak handle).
+ static HRESULT BeforeWrapperDestroyed(_In_ IReferenceTracker* obj);
+
+public:
+ // Begin the reference tracking process for external objects.
+ static HRESULT BeginReferenceTracking(InteropLibImports::RuntimeCallContext* cxt);
+
+ // End the reference tracking process for external object.
+ static HRESULT EndReferenceTracking();
+};
+
+// Class used to hold COM objects (i.e. IUnknown base class)
+// This class mimics the semantics of ATL::CComPtr (https://docs.microsoft.com/cpp/atl/reference/ccomptr-class).
+template
+struct ComHolder
+{
+ T* p;
+
+ ComHolder()
+ : p{ nullptr }
+ { }
+
+ ComHolder(_In_ const ComHolder&) = delete;
+ ComHolder& operator=(_In_ const ComHolder&) = delete;
+
+ ComHolder(_Inout_ ComHolder&& other)
+ : p{ other.Detach() }
+ { }
+
+ ComHolder& operator=(_Inout_ ComHolder&& other)
+ {
+ Attach(other.Detach());
+ return (*this);
+ }
+
+ ComHolder(_In_ T* i)
+ : p{ i }
+ {
+ _ASSERTE(p != nullptr);
+ (void)p->AddRef();
+ }
+
+ ~ComHolder()
+ {
+ Release();
+ }
+
+ T** operator&()
+ {
+ return &p;
+ }
+
+ T* operator->()
+ {
+ return p;
+ }
+
+ operator T*()
+ {
+ return p;
+ }
+
+ void Attach(_In_opt_ T* i) noexcept
+ {
+ Release();
+ p = i;
+ }
+
+ T* Detach() noexcept
+ {
+ T* tmp = p;
+ p = nullptr;
+ return tmp;
+ }
+
+ void Release() noexcept
+ {
+ if (p != nullptr)
+ {
+ (void)p->Release();
+ p = nullptr;
+ }
+ }
+};
+#endif // _INTEROP_COMWRAPPERS_H_
diff --git a/src/coreclr/src/interop/inc/interoplib.h b/src/coreclr/src/interop/inc/interoplib.h
new file mode 100644
index 0000000000000..df59b8b90a584
--- /dev/null
+++ b/src/coreclr/src/interop/inc/interoplib.h
@@ -0,0 +1,98 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#ifndef _INTEROP_INC_INTEROPLIB_H_
+#define _INTEROP_INC_INTEROPLIB_H_
+
+namespace InteropLibImports
+{
+ // Forward declaration of Runtime calling context.
+ // This class is used by the consuming runtime to pass through details
+ // that may be required during a subsequent callback from the InteropLib.
+ // InteropLib never directly modifies or inspects supplied instances.
+ struct RuntimeCallContext;
+}
+
+namespace InteropLib
+{
+ using OBJECTHANDLE = void*;
+
+ namespace Com
+ {
+ // See CreateComInterfaceFlags in ComWrappers.cs
+ enum CreateComInterfaceFlags
+ {
+ CreateComInterfaceFlags_None = 0,
+ CreateComInterfaceFlags_CallerDefinedIUnknown = 1,
+ CreateComInterfaceFlags_TrackerSupport = 2,
+ };
+
+ // Create an IUnknown instance that represents the supplied managed object instance.
+ HRESULT CreateWrapperForObject(
+ _In_ OBJECTHANDLE instance,
+ _In_ INT32 vtableCount,
+ _In_ void* vtables,
+ _In_ enum CreateComInterfaceFlags flags,
+ _Outptr_ IUnknown** wrapper) noexcept;
+
+ // Destroy the supplied wrapper
+ void DestroyWrapperForObject(_In_ void* wrapper) noexcept;
+
+ // Check if a wrapper is active.
+ HRESULT IsActiveWrapper(_In_ IUnknown* wrapper) noexcept;
+
+ // Reactivate the supplied wrapper.
+ HRESULT ReactivateWrapper(_In_ IUnknown* wrapper, _In_ InteropLib::OBJECTHANDLE handle) noexcept;
+
+ struct ExternalWrapperResult
+ {
+ // The returned context memory is guaranteed to be initialized to zero.
+ void* Context;
+
+ // See https://docs.microsoft.com/windows/win32/api/windows.ui.xaml.hosting.referencetracker/
+ // for details.
+ bool FromTrackerRuntime;
+ };
+
+ // See CreateObjectFlags in ComWrappers.cs
+ enum CreateObjectFlags
+ {
+ CreateObjectFlags_None = 0,
+ CreateObjectFlags_TrackerObject = 1,
+ CreateObjectFlags_UniqueInstance = 2,
+ };
+
+ // Allocate a wrapper context for an external object.
+ // The runtime supplies the external object, flags, and a memory
+ // request in order to bring the object into the runtime.
+ HRESULT CreateWrapperForExternal(
+ _In_ IUnknown* external,
+ _In_ enum CreateObjectFlags flags,
+ _In_ size_t contextSize,
+ _Out_ ExternalWrapperResult* result) noexcept;
+
+ // Destroy the supplied wrapper.
+ void DestroyWrapperForExternal(_In_ void* context) noexcept;
+
+ // Separate the supplied wrapper from the tracker runtime.
+ void SeparateWrapperFromTrackerRuntime(_In_ void* context) noexcept;
+
+ // Get internal interop IUnknown dispatch pointers.
+ void GetIUnknownImpl(
+ _Out_ void** fpQueryInterface,
+ _Out_ void** fpAddRef,
+ _Out_ void** fpRelease) noexcept;
+
+ // Begin the reference tracking process on external COM objects.
+ // This should only be called during a runtime's GC phase.
+ HRESULT BeginExternalObjectReferenceTracking(_In_ InteropLibImports::RuntimeCallContext* cxt) noexcept;
+
+ // End the reference tracking process.
+ // This should only be called during a runtime's GC phase.
+ HRESULT EndExternalObjectReferenceTracking() noexcept;
+ }
+}
+
+#endif // _INTEROP_INC_INTEROPLIB_H_
+
diff --git a/src/coreclr/src/interop/inc/interoplibimports.h b/src/coreclr/src/interop/inc/interoplibimports.h
new file mode 100644
index 0000000000000..3217b78a35f33
--- /dev/null
+++ b/src/coreclr/src/interop/inc/interoplibimports.h
@@ -0,0 +1,78 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#ifndef _INTEROP_INC_INTEROPLIBIMPORTS_H_
+#define _INTEROP_INC_INTEROPLIBIMPORTS_H_
+
+#include "interoplib.h"
+
+namespace InteropLibImports
+{
+ enum class AllocScenario
+ {
+ ManagedObjectWrapper,
+ NativeObjectWrapper,
+ };
+
+ // Allocate the given amount of memory.
+ void* MemAlloc(_In_ size_t sizeInBytes, _In_ AllocScenario scenario) noexcept;
+
+ // Free the previously allocated memory.
+ void MemFree(_In_ void* mem, _In_ AllocScenario scenario) noexcept;
+
+ // Add memory pressure to the runtime's GC calculations.
+ HRESULT AddMemoryPressureForExternal(_In_ UINT64 memoryInBytes) noexcept;
+
+ // Remove memory pressure from the runtime's GC calculations.
+ HRESULT RemoveMemoryPressureForExternal(_In_ UINT64 memoryInBytes) noexcept;
+
+ enum class GcRequest
+ {
+ Default,
+ FullBlocking // This is an expensive GC request, akin to a Gen2/"stop the world" GC.
+ };
+
+ // Request a GC from the runtime.
+ HRESULT RequestGarbageCollectionForExternal(_In_ GcRequest req) noexcept;
+
+ // Wait for the runtime's finalizer to clean up objects.
+ HRESULT WaitForRuntimeFinalizerForExternal() noexcept;
+
+ // Release objects associated with the current thread.
+ HRESULT ReleaseExternalObjectsFromCurrentThread() noexcept;
+
+ // Delete Object instance handle.
+ void DeleteObjectInstanceHandle(_In_ InteropLib::OBJECTHANDLE handle) noexcept;
+
+ // Get the current global pegging state.
+ bool GetGlobalPeggingState() noexcept;
+
+ // Set the current global pegging state.
+ void SetGlobalPeggingState(_In_ bool state) noexcept;
+
+ // Get next External Object Context from the Runtime calling context.
+ // S_OK - Context is valid.
+ // S_FALSE - Iterator has reached end and context out parameter is set to NULL.
+ HRESULT IteratorNext(
+ _In_ RuntimeCallContext* runtimeContext,
+ _Outptr_result_maybenull_ void** extObjContext) noexcept;
+
+ // Tell the runtime a reference path between the External Object Context and
+ // OBJECTHANDLE was found.
+ HRESULT FoundReferencePath(
+ _In_ RuntimeCallContext* runtimeContext,
+ _In_ void* extObjContext,
+ _In_ InteropLib::OBJECTHANDLE handle) noexcept;
+
+ // Get or create an IReferenceTrackerTarget instance for the supplied
+ // external object.
+ HRESULT GetOrCreateTrackerTargetForExternal(
+ _In_ IUnknown* externalComObject,
+ _In_ InteropLib::Com::CreateObjectFlags externalObjectFlags,
+ _In_ InteropLib::Com::CreateComInterfaceFlags trackerTargetFlags,
+ _Outptr_ void** trackerTarget) noexcept;
+}
+
+#endif // _INTEROP_INC_INTEROPLIBIMPORTS_H_
+
diff --git a/src/coreclr/src/interop/interoplib.cpp b/src/coreclr/src/interop/interoplib.cpp
new file mode 100644
index 0000000000000..8283024ad5d4a
--- /dev/null
+++ b/src/coreclr/src/interop/interoplib.cpp
@@ -0,0 +1,161 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#include "platform.h"
+#include
+#include
+
+#ifdef FEATURE_COMWRAPPERS
+#include "comwrappers.hpp"
+#endif // FEATURE_COMWRAPPERS
+
+using OBJECTHANDLE = InteropLib::OBJECTHANDLE;
+using RuntimeCallContext = InteropLibImports::RuntimeCallContext;
+
+namespace InteropLib
+{
+#ifdef FEATURE_COMWRAPPERS
+ // Exposed COM related API
+ namespace Com
+ {
+ HRESULT CreateWrapperForObject(
+ _In_ OBJECTHANDLE instance,
+ _In_ INT32 vtableCount,
+ _In_ void* vtablesRaw,
+ _In_ enum CreateComInterfaceFlags flags,
+ _Outptr_ IUnknown** wrapper) noexcept
+ {
+ _ASSERTE(instance != nullptr && wrapper != nullptr);
+
+ // Validate the supplied vtable data is valid with a
+ // reasonable count.
+ if ((vtablesRaw == nullptr && vtableCount != 0) || vtableCount < 0)
+ return E_INVALIDARG;
+
+ HRESULT hr;
+
+ // Convert input to appropriate types.
+ auto vtables = static_cast(vtablesRaw);
+
+ ManagedObjectWrapper* mow;
+ RETURN_IF_FAILED(ManagedObjectWrapper::Create(flags, instance, vtableCount, vtables, &mow));
+
+ *wrapper = static_cast(mow->As(IID_IUnknown));
+ return S_OK;
+ }
+
+ void DestroyWrapperForObject(_In_ void* wrapperMaybe) noexcept
+ {
+ ManagedObjectWrapper* wrapper = ManagedObjectWrapper::MapFromIUnknown(static_cast(wrapperMaybe));
+
+ // A caller should not be destroying a wrapper without knowing if the wrapper is valid.
+ _ASSERTE(wrapper != nullptr);
+
+ ManagedObjectWrapper::Destroy(wrapper);
+ }
+
+ HRESULT IsActiveWrapper(_In_ IUnknown* wrapperMaybe) noexcept
+ {
+ ManagedObjectWrapper* wrapper = ManagedObjectWrapper::MapFromIUnknown(wrapperMaybe);
+ if (wrapper == nullptr)
+ return E_INVALIDARG;
+
+ ULONG count = wrapper->IsActiveAddRef();
+ if (count == 1 || wrapper->Target == nullptr)
+ {
+ // The wrapper isn't active.
+ (void)wrapper->Release();
+ return S_FALSE;
+ }
+
+ return S_OK;
+ }
+
+ HRESULT ReactivateWrapper(_In_ IUnknown* wrapperMaybe, _In_ OBJECTHANDLE handle) noexcept
+ {
+ ManagedObjectWrapper* wrapper = ManagedObjectWrapper::MapFromIUnknown(wrapperMaybe);
+ if (wrapper == nullptr || handle == nullptr)
+ return E_INVALIDARG;
+
+ // Take an AddRef() as an indication of ownership.
+ (void)wrapper->AddRef();
+
+ // If setting this object handle fails, then the race
+ // was lost and we will cleanup the handle.
+ if (!wrapper->TrySetObjectHandle(handle))
+ InteropLibImports::DeleteObjectInstanceHandle(handle);
+
+ return S_OK;
+ }
+
+ HRESULT CreateWrapperForExternal(
+ _In_ IUnknown* external,
+ _In_ enum CreateObjectFlags flags,
+ _In_ size_t contextSize,
+ _Out_ ExternalWrapperResult* result) noexcept
+ {
+ _ASSERTE(external != nullptr && result != nullptr);
+
+ HRESULT hr;
+
+ NativeObjectWrapperContext* wrapperContext;
+ RETURN_IF_FAILED(NativeObjectWrapperContext::Create(external, flags, contextSize, &wrapperContext));
+
+ result->Context = wrapperContext->GetRuntimeContext();
+ result->FromTrackerRuntime = (wrapperContext->GetReferenceTracker() != nullptr);
+ return S_OK;
+ }
+
+ void DestroyWrapperForExternal(_In_ void* contextMaybe) noexcept
+ {
+ NativeObjectWrapperContext* context = NativeObjectWrapperContext::MapFromRuntimeContext(contextMaybe);
+
+ // A caller should not be destroying a context without knowing if the context is valid.
+ _ASSERTE(context != nullptr);
+
+ // Check if the tracker object manager should be informed prior to being destroyed.
+ IReferenceTracker* trackerMaybe = context->GetReferenceTracker();
+ if (trackerMaybe != nullptr)
+ {
+ // We only call this during a GC so ignore the failure as
+ // there is no way we can handle it at this point.
+ HRESULT hr = TrackerObjectManager::BeforeWrapperDestroyed(trackerMaybe);
+ _ASSERTE(SUCCEEDED(hr));
+ (void)hr;
+ }
+
+ NativeObjectWrapperContext::Destroy(context);
+ }
+
+ void SeparateWrapperFromTrackerRuntime(_In_ void* contextMaybe) noexcept
+ {
+ NativeObjectWrapperContext* context = NativeObjectWrapperContext::MapFromRuntimeContext(contextMaybe);
+
+ // A caller should not be separating a context without knowing if the context is valid.
+ _ASSERTE(context != nullptr);
+
+ context->DisconnectTracker();
+ }
+
+ void GetIUnknownImpl(
+ _Out_ void** fpQueryInterface,
+ _Out_ void** fpAddRef,
+ _Out_ void** fpRelease) noexcept
+ {
+ ManagedObjectWrapper::GetIUnknownImpl(fpQueryInterface, fpAddRef, fpRelease);
+ }
+
+ HRESULT BeginExternalObjectReferenceTracking(_In_ RuntimeCallContext* cxt) noexcept
+ {
+ return TrackerObjectManager::BeginReferenceTracking(cxt);
+ }
+
+ HRESULT EndExternalObjectReferenceTracking() noexcept
+ {
+ return TrackerObjectManager::EndReferenceTracking();
+ }
+ }
+
+#endif // FEATURE_COMWRAPPERS
+}
diff --git a/src/coreclr/src/interop/platform.h b/src/coreclr/src/interop/platform.h
new file mode 100644
index 0000000000000..b21ec99cd8ca8
--- /dev/null
+++ b/src/coreclr/src/interop/platform.h
@@ -0,0 +1,31 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#ifndef _INTEROP_PLATFORM_H_
+#define _INTEROP_PLATFORM_H_
+
+#include
+#include
+#include
+
+#ifndef _ASSERTE
+#define _ASSERTE(x) assert((x))
+#endif
+
+#ifdef _WIN32
+#include
+#include // COM interfaces
+
+// Common macro for working in COM
+#define RETURN_IF_FAILED(exp) { hr = exp; if (FAILED(hr)) { _ASSERTE(false && #exp); return hr; } }
+#define RETURN_VOID_IF_FAILED(exp) { hr = exp; if (FAILED(hr)) { _ASSERTE(false && #exp); return; } }
+
+#endif // _WIN32
+
+#define ABI_ASSERT(abi_definition) static_assert((abi_definition), "ABI is being invalidated.")
+
+// Runtime headers
+#include
+
+#endif // _INTEROP_PLATFORM_H_
\ No newline at end of file
diff --git a/src/coreclr/src/interop/referencetrackertypes.hpp b/src/coreclr/src/interop/referencetrackertypes.hpp
new file mode 100644
index 0000000000000..b9802a48fa113
--- /dev/null
+++ b/src/coreclr/src/interop/referencetrackertypes.hpp
@@ -0,0 +1,59 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#ifndef _INTEROP_REFERENCETRACKERTYPES_H_
+#define _INTEROP_REFERENCETRACKERTYPES_H_
+
+#include
+
+// Documentation found at https://docs.microsoft.com/windows/win32/api/windows.ui.xaml.hosting.referencetracker/
+
+class DECLSPEC_UUID("64bd43f8-bfee-4ec4-b7eb-2935158dae21") IReferenceTrackerTarget : public IUnknown
+{
+public:
+ STDMETHOD_(ULONG, AddRefFromReferenceTracker)() = 0;
+ STDMETHOD_(ULONG, ReleaseFromReferenceTracker)() = 0;
+ STDMETHOD(Peg)() = 0;
+ STDMETHOD(Unpeg)() = 0;
+};
+
+class DECLSPEC_UUID("29a71c6a-3c42-4416-a39d-e2825a07a773") IReferenceTrackerHost : public IUnknown
+{
+public:
+ STDMETHOD(DisconnectUnusedReferenceSources)(_In_ DWORD dwFlags) = 0;
+ STDMETHOD(ReleaseDisconnectedReferenceSources)() = 0;
+ STDMETHOD(NotifyEndOfReferenceTrackingOnThread)() = 0;
+ STDMETHOD(GetTrackerTarget)(_In_ IUnknown* obj, _Outptr_ IReferenceTrackerTarget** ppNewReference) = 0;
+ STDMETHOD(AddMemoryPressure)(_In_ UINT64 bytesAllocated) = 0;
+ STDMETHOD(RemoveMemoryPressure)(_In_ UINT64 bytesAllocated) = 0;
+};
+
+class DECLSPEC_UUID("3cf184b4-7ccb-4dda-8455-7e6ce99a3298") IReferenceTrackerManager : public IUnknown
+{
+public:
+ STDMETHOD(ReferenceTrackingStarted)() = 0;
+ STDMETHOD(FindTrackerTargetsCompleted)(_In_ BOOL bWalkFailed) = 0;
+ STDMETHOD(ReferenceTrackingCompleted)() = 0;
+ STDMETHOD(SetReferenceTrackerHost)(_In_ IReferenceTrackerHost *pCLRServices) = 0;
+};
+
+class DECLSPEC_UUID("04b3486c-4687-4229-8d14-505ab584dd88") IFindReferenceTargetsCallback : public IUnknown
+{
+public:
+ STDMETHOD(FoundTrackerTarget)(_In_ IReferenceTrackerTarget* target) = 0;
+};
+
+class DECLSPEC_UUID("11d3b13a-180e-4789-a8be-7712882893e6") IReferenceTracker : public IUnknown
+{
+public:
+ STDMETHOD(ConnectFromTrackerSource)() = 0;
+ STDMETHOD(DisconnectFromTrackerSource)() = 0;
+ STDMETHOD(FindTrackerTargets)(_In_ IFindReferenceTargetsCallback *pCallback) = 0;
+ STDMETHOD(GetReferenceTrackerManager)(_Outptr_ IReferenceTrackerManager **ppTrackerManager) = 0;
+ STDMETHOD(AddRefFromTrackerSource)() = 0;
+ STDMETHOD(ReleaseFromTrackerSource)() = 0;
+ STDMETHOD(PegFromTrackerSource)() = 0;
+};
+
+#endif // _INTEROP_REFERENCETRACKERTYPES_H_
diff --git a/src/coreclr/src/interop/trackerobjectmanager.cpp b/src/coreclr/src/interop/trackerobjectmanager.cpp
new file mode 100644
index 0000000000000..7d1cb15722197
--- /dev/null
+++ b/src/coreclr/src/interop/trackerobjectmanager.cpp
@@ -0,0 +1,372 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#include "comwrappers.hpp"
+#include
+
+using OBJECTHANDLE = InteropLib::OBJECTHANDLE;
+using RuntimeCallContext = InteropLibImports::RuntimeCallContext;
+
+namespace
+{
+ const IID IID_IReferenceTrackerHost = __uuidof(IReferenceTrackerHost);
+ const IID IID_IReferenceTrackerTarget = __uuidof(IReferenceTrackerTarget);
+ const IID IID_IReferenceTracker = __uuidof(IReferenceTracker);
+ const IID IID_IReferenceTrackerManager = __uuidof(IReferenceTrackerManager);
+ const IID IID_IFindReferenceTargetsCallback = __uuidof(IFindReferenceTargetsCallback);
+
+ // In order to minimize the impact of a constructor running on module load,
+ // the HostServices class should have no instance fields.
+ class HostServices : public IReferenceTrackerHost
+ {
+ public: // IReferenceTrackerHost
+ STDMETHOD(DisconnectUnusedReferenceSources)(_In_ DWORD dwFlags);
+ STDMETHOD(ReleaseDisconnectedReferenceSources)();
+ STDMETHOD(NotifyEndOfReferenceTrackingOnThread)();
+ STDMETHOD(GetTrackerTarget)(_In_ IUnknown* obj, _Outptr_ IReferenceTrackerTarget** ppNewReference);
+ STDMETHOD(AddMemoryPressure)(_In_ UINT64 bytesAllocated);
+ STDMETHOD(RemoveMemoryPressure)(_In_ UINT64 bytesAllocated);
+
+ public: // IUnknown
+ // Lifetime maintained by stack - we don't care about ref counts
+ STDMETHOD_(ULONG, AddRef)() { return 1; }
+ STDMETHOD_(ULONG, Release)() { return 1; }
+
+ STDMETHOD(QueryInterface)(
+ /* [in] */ REFIID riid,
+ /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject)
+ {
+ if (ppvObject == nullptr)
+ return E_POINTER;
+
+ if (IsEqualIID(riid, IID_IReferenceTrackerHost))
+ {
+ *ppvObject = static_cast(this);
+ }
+ else if (IsEqualIID(riid, IID_IUnknown))
+ {
+ *ppvObject = static_cast(this);
+ }
+ else
+ {
+ *ppvObject = nullptr;
+ return E_NOINTERFACE;
+ }
+
+ (void)AddRef();
+ return S_OK;
+ }
+ };
+
+ // Global instance of host services.
+ HostServices g_HostServicesInstance;
+
+ // Defined in windows.ui.xaml.hosting.referencetracker.h.
+ enum XAML_REFERENCETRACKER_DISCONNECT
+ {
+ // Indicates the disconnect is during a suspend and a GC can be trigger.
+ XAML_REFERENCETRACKER_DISCONNECT_SUSPEND = 0x00000001
+ };
+
+ STDMETHODIMP HostServices::DisconnectUnusedReferenceSources(_In_ DWORD flags)
+ {
+ InteropLibImports::GcRequest type = InteropLibImports::GcRequest::Default;
+
+ // Request a "stop the world" GC when a suspend is occurring.
+ if (flags & XAML_REFERENCETRACKER_DISCONNECT_SUSPEND)
+ type = InteropLibImports::GcRequest::FullBlocking;
+
+ return InteropLibImports::RequestGarbageCollectionForExternal(type);
+ }
+
+ STDMETHODIMP HostServices::ReleaseDisconnectedReferenceSources()
+ {
+ return InteropLibImports::WaitForRuntimeFinalizerForExternal();
+ }
+
+ STDMETHODIMP HostServices::NotifyEndOfReferenceTrackingOnThread()
+ {
+ return InteropLibImports::ReleaseExternalObjectsFromCurrentThread();
+ }
+
+ // Creates a proxy object (managed object wrapper) that points to the given IUnknown.
+ // The proxy represents the following:
+ // 1. Has a managed reference pointing to the external object
+ // and therefore forms a cycle that can be resolved by GC.
+ // 2. Forwards data binding requests.
+ //
+ // For example:
+ //
+ // Grid <---- NoCW Grid <-------- NoCW
+ // | ^ | ^
+ // | | Becomes | |
+ // v | v |
+ // Rectangle Rectangle ----->Proxy
+ //
+ // Arguments
+ // obj - An IUnknown* where a NoCW points to (Grid, in this case)
+ // Notes:
+ // 1. We can either create a new NoCW or get back an old one from the cache.
+ // 2. This obj could be a regular tracker runtime object for data binding.
+ // ppNewReference - The IReferenceTrackerTarget* for the proxy created
+ // The tracker runtime will call IReferenceTrackerTarget to establish a reference.
+ //
+ STDMETHODIMP HostServices::GetTrackerTarget(_In_ IUnknown* obj, _Outptr_ IReferenceTrackerTarget** ppNewReference)
+ {
+ if (obj == nullptr || ppNewReference == nullptr)
+ return E_INVALIDARG;
+
+ HRESULT hr;
+
+ // QI for IUnknown to get the identity unknown
+ ComHolder identity;
+ RETURN_IF_FAILED(obj->QueryInterface(&identity));
+
+ // Get or create an existing implementation for this external.
+ ComHolder target;
+ RETURN_IF_FAILED(InteropLibImports::GetOrCreateTrackerTargetForExternal(
+ identity,
+ InteropLib::Com::CreateObjectFlags_TrackerObject,
+ InteropLib::Com::CreateComInterfaceFlags_TrackerSupport,
+ (void**)&target));
+
+ return target->QueryInterface(IID_IReferenceTrackerTarget, (void**)ppNewReference);
+ }
+
+ STDMETHODIMP HostServices::AddMemoryPressure(_In_ UINT64 bytesAllocated)
+ {
+ return InteropLibImports::AddMemoryPressureForExternal(bytesAllocated);
+ }
+
+ STDMETHODIMP HostServices::RemoveMemoryPressure(_In_ UINT64 bytesAllocated)
+ {
+ return InteropLibImports::RemoveMemoryPressureForExternal(bytesAllocated);
+ }
+
+ VolatilePtr s_TrackerManager; // The one and only Tracker Manager instance
+ Volatile s_HasTrackingStarted = FALSE;
+
+ // Indicates if walking the external objects is needed.
+ // (i.e. Have any IReferenceTracker instances been found?)
+ bool ShouldWalkExternalObjects()
+ {
+ return (s_TrackerManager != nullptr);
+ }
+
+ // Callback implementation of IFindReferenceTargetsCallback
+ class FindDependentWrappersCallback : public IFindReferenceTargetsCallback
+ {
+ NativeObjectWrapperContext* _nowCxt;
+ RuntimeCallContext* _runtimeCallCxt;
+
+ public:
+ FindDependentWrappersCallback(_In_ NativeObjectWrapperContext* nowCxt, _In_ RuntimeCallContext* runtimeCallCxt)
+ : _nowCxt{ nowCxt }
+ , _runtimeCallCxt{ runtimeCallCxt }
+ {
+ _ASSERTE(_nowCxt != nullptr && runtimeCallCxt != nullptr);
+ }
+
+ STDMETHOD(FoundTrackerTarget)(_In_ IReferenceTrackerTarget* target)
+ {
+ HRESULT hr;
+
+ if (target == nullptr)
+ return E_POINTER;
+
+ ManagedObjectWrapper* mow = ManagedObjectWrapper::MapFromIUnknown(target);
+
+ // Not a target we implemented.
+ if (mow == nullptr)
+ return S_OK;
+
+ // Notify the runtime a reference path was found.
+ RETURN_IF_FAILED(InteropLibImports::FoundReferencePath(
+ _runtimeCallCxt,
+ _nowCxt->GetRuntimeContext(),
+ mow->Target));
+
+ return S_OK;
+ }
+
+ // Lifetime maintained by stack - we don't care about ref counts
+ STDMETHOD_(ULONG, AddRef)() { return 1; }
+ STDMETHOD_(ULONG, Release)() { return 1; }
+
+ STDMETHOD(QueryInterface)(
+ /* [in] */ REFIID riid,
+ /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject)
+ {
+ if (ppvObject == nullptr)
+ return E_POINTER;
+
+ if (IsEqualIID(riid, IID_IFindReferenceTargetsCallback))
+ {
+ *ppvObject = static_cast(this);
+ }
+ else if (IsEqualIID(riid, IID_IUnknown))
+ {
+ *ppvObject = static_cast(this);
+ }
+ else
+ {
+ *ppvObject = nullptr;
+ return E_NOINTERFACE;
+ }
+
+ (void)AddRef();
+ return S_OK;
+ }
+ };
+
+ HRESULT WalkExternalTrackerObjects(_In_ RuntimeCallContext* cxt)
+ {
+ _ASSERTE(cxt != nullptr);
+
+ BOOL walkFailed = FALSE;
+ HRESULT hr;
+
+ void* extObjContext = nullptr;
+ while (S_OK == (hr = InteropLibImports::IteratorNext(cxt, &extObjContext)))
+ {
+ _ASSERTE(extObjContext != nullptr);
+
+ NativeObjectWrapperContext* nowc = NativeObjectWrapperContext::MapFromRuntimeContext(extObjContext);
+
+ // Check if the object is a tracker object.
+ IReferenceTracker* trackerMaybe = nowc->GetReferenceTracker();
+ if (trackerMaybe == nullptr)
+ continue;
+
+ // Ask the tracker instance to find all reference targets.
+ FindDependentWrappersCallback cb{ nowc, cxt };
+ hr = trackerMaybe->FindTrackerTargets(&cb);
+ if (FAILED(hr))
+ break;
+ }
+
+ if (FAILED(hr))
+ {
+ // Remember the fact that we've failed and stop walking
+ walkFailed = TRUE;
+ InteropLibImports::SetGlobalPeggingState(true);
+ }
+
+ _ASSERTE(s_TrackerManager != nullptr);
+ (void)s_TrackerManager->FindTrackerTargetsCompleted(walkFailed);
+
+ return hr;
+ }
+}
+
+HRESULT TrackerObjectManager::OnIReferenceTrackerFound(_In_ IReferenceTracker* obj)
+{
+ _ASSERTE(obj != nullptr);
+ if (s_TrackerManager != nullptr)
+ return S_OK;
+
+ // Retrieve IReferenceTrackerManager
+ HRESULT hr;
+ ComHolder trackerManager;
+ RETURN_IF_FAILED(obj->GetReferenceTrackerManager(&trackerManager));
+
+ ComHolder hostServices;
+ RETURN_IF_FAILED(g_HostServicesInstance.QueryInterface(IID_IReferenceTrackerHost, (void**)&hostServices));
+
+ // Attempt to set the tracker instance.
+ if (::InterlockedCompareExchangePointer((void**)&s_TrackerManager, trackerManager.p, nullptr) == nullptr)
+ {
+ (void)trackerManager.Detach(); // Ownership has been transfered
+ RETURN_IF_FAILED(s_TrackerManager->SetReferenceTrackerHost(hostServices));
+ }
+
+ return S_OK;
+}
+
+HRESULT TrackerObjectManager::AfterWrapperCreated(_In_ IReferenceTracker* obj)
+{
+ _ASSERTE(obj != nullptr);
+
+ HRESULT hr;
+
+ // Notify tracker runtime that we've created a new wrapper for this object.
+ // To avoid surprises, we should notify them before we fire the first AddRefFromTrackerSource.
+ RETURN_IF_FAILED(obj->ConnectFromTrackerSource());
+
+ // Send out AddRefFromTrackerSource callbacks to notify tracker runtime we've done AddRef()
+ // for certain interfaces. We should do this *after* we made a AddRef() because we should never
+ // be in a state where report refs > actual refs
+ RETURN_IF_FAILED(obj->AddRefFromTrackerSource());
+
+ return S_OK;
+}
+
+HRESULT TrackerObjectManager::BeforeWrapperDestroyed(_In_ IReferenceTracker* obj)
+{
+ _ASSERTE(obj != nullptr);
+
+ HRESULT hr;
+
+ // Notify tracker runtime that we are about to destroy a wrapper
+ // (same timing as short weak handle) for this object.
+ // They need this information to disconnect weak refs and stop firing events,
+ // so that they can avoid resurrecting the object.
+ RETURN_IF_FAILED(obj->DisconnectFromTrackerSource());
+
+ return S_OK;
+}
+
+HRESULT TrackerObjectManager::BeginReferenceTracking(_In_ RuntimeCallContext* cxt)
+{
+ _ASSERTE(cxt != nullptr);
+
+ if (!ShouldWalkExternalObjects())
+ return S_FALSE;
+
+ HRESULT hr;
+
+ _ASSERTE(s_HasTrackingStarted == FALSE);
+ _ASSERTE(InteropLibImports::GetGlobalPeggingState());
+
+ s_HasTrackingStarted = TRUE;
+
+ // From this point, the tracker runtime decides whether a target
+ // should be pegged or not as the global pegging flag is now off.
+ InteropLibImports::SetGlobalPeggingState(false);
+
+ // Let the tracker runtime know we are about to walk external objects so that
+ // they can lock their reference cache. Note that the tracker runtime doesn't need to
+ // unpeg all external objects at this point and they can do the pegging/unpegging.
+ // in FindTrackerTargetsCompleted.
+ _ASSERTE(s_TrackerManager != nullptr);
+ RETURN_IF_FAILED(s_TrackerManager->ReferenceTrackingStarted());
+
+ // Time to walk the external objects
+ RETURN_IF_FAILED(WalkExternalTrackerObjects(cxt));
+
+ return S_OK;
+}
+
+HRESULT TrackerObjectManager::EndReferenceTracking()
+{
+ if (s_HasTrackingStarted != TRUE
+ || !ShouldWalkExternalObjects())
+ return S_FALSE;
+
+ HRESULT hr;
+
+ // Let the tracker runtime know the external object walk is done and they need to:
+ // 1. Unpeg all managed object wrappers (mow) if the (mow) needs to be unpegged
+ // (i.e. when the (mow) is only reachable by other external tracker objects).
+ // 2. Peg all mows if the mow needs to be pegged (i.e. when the above condition is not true)
+ // 3. Unlock reference cache when they are done.
+ _ASSERTE(s_TrackerManager != nullptr);
+ hr = s_TrackerManager->ReferenceTrackingCompleted();
+ _ASSERTE(SUCCEEDED(hr));
+
+ InteropLibImports::SetGlobalPeggingState(true);
+ s_HasTrackingStarted = FALSE;
+
+ return hr;
+}
diff --git a/src/coreclr/src/vm/CMakeLists.txt b/src/coreclr/src/vm/CMakeLists.txt
index 43b9bc3451961..30471d4071637 100644
--- a/src/coreclr/src/vm/CMakeLists.txt
+++ b/src/coreclr/src/vm/CMakeLists.txt
@@ -3,6 +3,7 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
# Needed due to the cmunged files being in the binary folders, the set(CMAKE_INCLUDE_CURRENT_DIR ON) is not enough
include_directories(BEFORE ${CMAKE_CURRENT_SOURCE_DIR})
include_directories(${ARCH_SOURCES_DIR})
+include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../interop/inc)
add_definitions(-DUNICODE)
add_definitions(-D_UNICODE)
@@ -593,6 +594,7 @@ list(APPEND VM_SOURCES_WKS
dispparammarshaler.cpp
dwreport.cpp
eventreporter.cpp
+ interoplibinterface.cpp
mngstdinterfaces.cpp
notifyexternals.cpp
olecontexthelpers.cpp
@@ -621,6 +623,7 @@ list(APPEND VM_HEADERS_WKS
dispparammarshaler.h
dwreport.h
eventreporter.h
+ interoplibinterface.h
mngstdinterfaces.h
notifyexternals.h
olecontexthelpers.h
diff --git a/src/coreclr/src/vm/ceemain.cpp b/src/coreclr/src/vm/ceemain.cpp
index 2790349ee9383..23b31b6e07e1d 100644
--- a/src/coreclr/src/vm/ceemain.cpp
+++ b/src/coreclr/src/vm/ceemain.cpp
@@ -186,7 +186,7 @@
#include "runtimecallablewrapper.h"
#include "notifyexternals.h"
#include "mngstdinterfaces.h"
-#include "rcwwalker.h"
+#include "interoplibinterface.h"
#endif // FEATURE_COMINTEROP
#ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT
diff --git a/src/coreclr/src/vm/ecalllist.h b/src/coreclr/src/vm/ecalllist.h
index 5a20f27458767..1ae20b1ff1368 100644
--- a/src/coreclr/src/vm/ecalllist.h
+++ b/src/coreclr/src/vm/ecalllist.h
@@ -921,6 +921,7 @@ FCFuncStart(gRuntimeHelpers)
FCFuncElement("EnsureSufficientExecutionStack", ReflectionInvocation::EnsureSufficientExecutionStack)
FCFuncElement("TryEnsureSufficientExecutionStack", ReflectionInvocation::TryEnsureSufficientExecutionStack)
FCFuncElement("GetUninitializedObjectInternal", ReflectionSerialization::GetUninitializedObject)
+ QCFuncElement("AllocateTypeAssociatedMemoryInternal", RuntimeTypeHandle::AllocateTypeAssociatedMemory)
FCFuncEnd()
FCFuncStart(gContextSynchronizationFuncs)
@@ -992,6 +993,14 @@ FCFuncEnd()
#endif // FEATURE_COMINTEROP
+#ifdef FEATURE_COMWRAPPERS
+FCFuncStart(gComWrappersFuncs)
+ QCFuncElement("GetIUnknownImplInternal", ComWrappersNative::GetIUnknownImpl)
+ QCFuncElement("GetOrCreateComInterfaceForObjectInternal", ComWrappersNative::GetOrCreateComInterfaceForObject)
+ QCFuncElement("GetOrCreateObjectForComInstanceInternal", ComWrappersNative::GetOrCreateObjectForComInstance)
+FCFuncEnd()
+#endif // FEATURE_COMWRAPPERS
+
FCFuncStart(gMngdRefCustomMarshalerFuncs)
FCFuncElement("CreateMarshaler", MngdRefCustomMarshaler::CreateMarshaler)
FCFuncElement("ConvertContentsToNative", MngdRefCustomMarshaler::ConvertContentsToNative)
@@ -1209,6 +1218,9 @@ FCClassElement("AssemblyName", "System.Reflection", gAssemblyNameFuncs)
FCClassElement("Buffer", "System", gBufferFuncs)
FCClassElement("CLRConfig", "System", gClrConfig)
FCClassElement("CastHelpers", "System.Runtime.CompilerServices", gCastHelpers)
+#ifdef FEATURE_COMINTEROP
+FCClassElement("ComWrappers", "System.Runtime.InteropServices", gComWrappersFuncs)
+#endif // FEATURE_COMINTEROP
FCClassElement("CompatibilitySwitch", "System.Runtime.Versioning", gCompatibilitySwitchFuncs)
FCClassElement("CustomAttribute", "System.Reflection", gCOMCustomAttributeFuncs)
FCClassElement("CustomAttributeEncodedArgument", "System.Reflection", gCustomAttributeEncodedArgument)
diff --git a/src/coreclr/src/vm/gcenv.ee.cpp b/src/coreclr/src/vm/gcenv.ee.cpp
index fe0fc30575d23..8352bb66f3755 100644
--- a/src/coreclr/src/vm/gcenv.ee.cpp
+++ b/src/coreclr/src/vm/gcenv.ee.cpp
@@ -214,17 +214,7 @@ void GCToEEInterface::GcStartWork (int condemned, int max_gen)
#endif
#ifdef FEATURE_COMINTEROP
- //
- // Let GC detect managed/native cycles with input from jupiter
- // Jupiter will
- // 1. Report reference from RCW to CCW based on native reference in Jupiter
- // 2. Identify the subset of CCWs that needs to be rooted
- //
- // We'll build the references from RCW to CCW using
- // 1. Preallocated arrays
- // 2. Dependent handles
- //
- RCWWalker::OnGCStarted(condemned);
+ Interop::OnGCStarted(condemned);
#endif // FEATURE_COMINTEROP
if (condemned == max_gen)
@@ -243,10 +233,7 @@ void GCToEEInterface::GcDone(int condemned)
CONTRACTL_END;
#ifdef FEATURE_COMINTEROP
- //
- // Tell Jupiter GC has finished
- //
- RCWWalker::OnGCFinished(condemned);
+ Interop::OnGCFinished(condemned);
#endif // FEATURE_COMINTEROP
}
diff --git a/src/coreclr/src/vm/gcenv.ee.standalone.cpp b/src/coreclr/src/vm/gcenv.ee.standalone.cpp
index 85f6a698d2cde..5d6a58dd776a0 100644
--- a/src/coreclr/src/vm/gcenv.ee.standalone.cpp
+++ b/src/coreclr/src/vm/gcenv.ee.standalone.cpp
@@ -10,7 +10,7 @@
#ifdef FEATURE_COMINTEROP
#include "runtimecallablewrapper.h"
-#include "rcwwalker.h"
+#include "interoplibinterface.h"
#include "comcallablewrapper.h"
#endif // FEATURE_COMINTEROP
diff --git a/src/coreclr/src/vm/gcenv.ee.static.cpp b/src/coreclr/src/vm/gcenv.ee.static.cpp
index e04fd58ae6e8a..697fed5b56980 100644
--- a/src/coreclr/src/vm/gcenv.ee.static.cpp
+++ b/src/coreclr/src/vm/gcenv.ee.static.cpp
@@ -10,7 +10,7 @@
#ifdef FEATURE_COMINTEROP
#include "runtimecallablewrapper.h"
-#include "rcwwalker.h"
+#include "interoplibinterface.h"
#include "comcallablewrapper.h"
#endif // FEATURE_COMINTEROP
diff --git a/src/coreclr/src/vm/interoplibinterface.cpp b/src/coreclr/src/vm/interoplibinterface.cpp
new file mode 100644
index 0000000000000..70e5f14f2c7df
--- /dev/null
+++ b/src/coreclr/src/vm/interoplibinterface.cpp
@@ -0,0 +1,1299 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// Runtime headers
+#include "common.h"
+#include "rcwrefcache.h"
+#include "rcwwalker.h"
+#include "olecontexthelpers.h"
+#include "finalizerthread.h"
+
+// Interop library header
+#include
+
+#include "interoplibinterface.h"
+
+using CreateObjectFlags = InteropLib::Com::CreateObjectFlags;
+using CreateComInterfaceFlags = InteropLib::Com::CreateComInterfaceFlags;
+
+namespace
+{
+ // This class is used to track the external object within the runtime.
+ struct ExternalObjectContext
+ {
+ static const DWORD InvalidSyncBlockIndex;
+
+ void* Identity;
+ void* ThreadContext;
+ DWORD SyncBlockIndex;
+
+ enum
+ {
+ Flags_None = 0,
+ Flags_Collected = 1,
+ Flags_ReferenceTracker = 2,
+ Flags_InCache = 4,
+ };
+ DWORD Flags;
+
+ static void Construct(
+ _Out_ ExternalObjectContext* cxt,
+ _In_ IUnknown* identity,
+ _In_opt_ void* threadContext,
+ _In_ DWORD syncBlockIndex,
+ _In_ DWORD flags)
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ PRECONDITION(cxt != NULL);
+ PRECONDITION(threadContext != NULL);
+ PRECONDITION(syncBlockIndex != InvalidSyncBlockIndex);
+ }
+ CONTRACTL_END;
+
+ cxt->Identity = (void*)identity;
+ cxt->ThreadContext = threadContext;
+ cxt->SyncBlockIndex = syncBlockIndex;
+ cxt->Flags = flags;
+ }
+
+ bool IsSet(_In_ DWORD f) const
+ {
+ return ((Flags & f) == f);
+ }
+
+ bool IsActive() const
+ {
+ return !IsSet(Flags_Collected)
+ && (SyncBlockIndex != InvalidSyncBlockIndex);
+ }
+
+ void MarkCollected()
+ {
+ _ASSERTE(GCHeapUtilities::IsGCInProgress());
+ SyncBlockIndex = InvalidSyncBlockIndex;
+ Flags |= Flags_Collected;
+ }
+
+ OBJECTREF GetObjectRef()
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_COOPERATIVE;
+ }
+ CONTRACTL_END;
+
+ _ASSERTE(IsActive());
+ return ObjectToOBJECTREF(g_pSyncTable[SyncBlockIndex].m_Object);
+ }
+ };
+
+ const DWORD ExternalObjectContext::InvalidSyncBlockIndex = 0; // See syncblk.h
+
+ static_assert((sizeof(ExternalObjectContext) % sizeof(void*)) == 0, "Keep context pointer size aligned");
+
+ // Holder for a External Wrapper Result
+ struct ExternalWrapperResultHolder
+ {
+ InteropLib::Com::ExternalWrapperResult Result;
+ ExternalWrapperResultHolder()
+ : Result{}
+ { }
+ ~ExternalWrapperResultHolder()
+ {
+ if (Result.Context != NULL)
+ InteropLib::Com::DestroyWrapperForExternal(Result.Context);
+ }
+ InteropLib::Com::ExternalWrapperResult* operator&()
+ {
+ return &Result;
+ }
+ ExternalObjectContext* GetContext()
+ {
+ return static_cast(Result.Context);
+ }
+ ExternalObjectContext* DetachContext()
+ {
+ ExternalObjectContext* t = GetContext();
+ Result.Context = NULL;
+ return t;
+ }
+ };
+
+ using ExtObjCxtRefCache = RCWRefCache;
+
+ class ExtObjCxtCache
+ {
+ static Volatile g_Instance;
+
+ public: // static
+ static ExtObjCxtCache* GetInstanceNoThrow() noexcept
+ {
+ CONTRACT(ExtObjCxtCache*)
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
+ }
+ CONTRACT_END;
+
+ RETURN g_Instance;
+ }
+
+ static ExtObjCxtCache* GetInstance()
+ {
+ CONTRACT(ExtObjCxtCache*)
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ POSTCONDITION(RETVAL != NULL);
+ }
+ CONTRACT_END;
+
+ if (g_Instance.Load() == NULL)
+ {
+ ExtObjCxtCache* instMaybe = new ExtObjCxtCache();
+
+ // Attempt to set the global instance.
+ if (NULL != FastInterlockCompareExchangePointer(&g_Instance, instMaybe, NULL))
+ delete instMaybe;
+ }
+
+ RETURN g_Instance;
+ }
+
+ public: // Inner class definitions
+ class Traits : public DefaultSHashTraits
+ {
+ public:
+ using key_t = void*;
+ static const key_t GetKey(_In_ element_t e) { LIMITED_METHOD_CONTRACT; return (key_t)e->Identity; }
+ static count_t Hash(_In_ key_t key) { LIMITED_METHOD_CONTRACT; return (count_t)key; }
+ static bool Equals(_In_ key_t lhs, _In_ key_t rhs) { LIMITED_METHOD_CONTRACT; return (lhs == rhs); }
+ };
+
+ // Alias some useful types
+ using Element = SHash::element_t;
+ using Iterator = SHash::Iterator;
+
+ class LockHolder : public CrstHolder
+ {
+ public:
+ LockHolder(_In_ ExtObjCxtCache *cache)
+ : CrstHolder(&cache->_lock)
+ {
+ // This cache must be locked in Cooperative mode
+ // since releases of wrappers can occur during a GC.
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_COOPERATIVE;
+ }
+ CONTRACTL_END;
+ }
+ };
+
+ private:
+ friend struct InteropLibImports::RuntimeCallContext;
+ SHash _hashMap;
+ Crst _lock;
+ ExtObjCxtRefCache* _refCache;
+
+ ExtObjCxtCache()
+ : _lock(CrstExternalObjectContextCache, CRST_UNSAFE_COOPGC)
+ , _refCache(GetAppDomain()->GetRCWRefCache())
+ { }
+ ~ExtObjCxtCache() = default;
+
+ public:
+#if _DEBUG
+ bool IsLockHeld()
+ {
+ WRAPPER_NO_CONTRACT;
+ return (_lock.OwnedByCurrentThread() != FALSE);
+ }
+#endif // _DEBUG
+
+ // Get the associated reference cache with this external object cache.
+ ExtObjCxtRefCache* GetRefCache()
+ {
+ WRAPPER_NO_CONTRACT;
+ return _refCache;
+ }
+
+ // Create a managed IEnumerable instance for this collection.
+ // The collection should respect the supplied arguments.
+ // withFlags - If Flag_None, then ignore. Otherwise objects must have these flags.
+ // threadContext - The object must be associated with the supplied thread context.
+ //
+ // [TODO] Performance improvement should be made here to provide a custom IEnumerable
+ // instead of a managed array.
+ OBJECTREF CreateManagedEnumerable(_In_ DWORD withFlags, _In_opt_ void* threadContext)
+ {
+ CONTRACT(OBJECTREF)
+ {
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ PRECONDITION(!IsLockHeld());
+ POSTCONDITION(RETVAL != NULL);
+ }
+ CONTRACT_END;
+
+ DWORD objCount;
+ DWORD objCountMax;
+
+ struct
+ {
+ PTRARRAYREF arrRef;
+ PTRARRAYREF arrRefTmp;
+ } gc;
+ ::ZeroMemory(&gc, sizeof(gc));
+ GCPROTECT_BEGIN(gc);
+
+ {
+ LockHolder lock(this);
+ objCountMax = _hashMap.GetCount();
+ }
+
+ // Allocate the max number of objects needed.
+ gc.arrRef = (PTRARRAYREF)AllocateObjectArray(objCountMax, g_pObjectClass);
+
+ // Populate the array
+ {
+ LockHolder lock(this);
+ Iterator curr = _hashMap.Begin();
+ Iterator end = _hashMap.End();
+
+ ExternalObjectContext* inst;
+ for (objCount = 0; curr != end && objCount < objCountMax; objCount++, curr++)
+ {
+ inst = *curr;
+
+ // Only add objects that are in the correct thread
+ // context and have the appropriate flags set.
+ if (inst->ThreadContext == threadContext
+ && (withFlags == ExternalObjectContext::Flags_None || inst->IsSet(withFlags)))
+ {
+ // Separate the wrapper from the tracker runtime prior to
+ // passing this onto the caller. This call is okay to make
+ // even if the instance isn't from the tracker runtime.
+ InteropLib::Com::SeparateWrapperFromTrackerRuntime(inst);
+ gc.arrRef->SetAt(objCount, inst->GetObjectRef());
+ STRESS_LOG1(LF_INTEROP, LL_INFO100, "Add EOC to Enumerable: 0x%p\n", inst);
+ }
+ }
+ }
+
+ // Make the array the correct size
+ if (objCount < objCountMax)
+ {
+ gc.arrRefTmp = (PTRARRAYREF)AllocateObjectArray(objCount, g_pObjectClass);
+
+ SIZE_T elementSize = gc.arrRef->GetComponentSize();
+
+ void *src = gc.arrRef->GetDataPtr();
+ void *dest = gc.arrRefTmp->GetDataPtr();
+
+ _ASSERTE(sizeof(Object*) == elementSize && "Assumption invalidated in memmoveGCRefs() usage");
+ memmoveGCRefs(dest, src, objCount * elementSize);
+ gc.arrRef = gc.arrRefTmp;
+ }
+
+ GCPROTECT_END();
+
+ RETURN gc.arrRef;
+ }
+
+ ExternalObjectContext* Find(_In_ IUnknown* instance)
+ {
+ CONTRACT(ExternalObjectContext*)
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_COOPERATIVE;
+ PRECONDITION(IsLockHeld());
+ PRECONDITION(instance != NULL);
+ POSTCONDITION(CheckPointer(RETVAL, NULL_OK));
+ }
+ CONTRACT_END;
+
+ // Forbid the GC from messing with the hash table.
+ GCX_FORBID();
+
+ RETURN _hashMap.Lookup(instance);
+ }
+
+ ExternalObjectContext* Add(_In_ ExternalObjectContext* cxt)
+ {
+ CONTRACT(ExternalObjectContext*)
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ MODE_COOPERATIVE;
+ PRECONDITION(IsLockHeld());
+ PRECONDITION(!Traits::IsNull(cxt) && !Traits::IsDeleted(cxt));
+ PRECONDITION(cxt->Identity != NULL);
+ PRECONDITION(Find(static_cast(cxt->Identity)) == NULL);
+ POSTCONDITION(RETVAL == cxt);
+ }
+ CONTRACT_END;
+
+ _hashMap.Add(cxt);
+ RETURN cxt;
+ }
+
+ ExternalObjectContext* FindOrAdd(_In_ IUnknown* key, _In_ ExternalObjectContext* newCxt)
+ {
+ CONTRACT(ExternalObjectContext*)
+ {
+ THROWS;
+ GC_NOTRIGGER;
+ MODE_COOPERATIVE;
+ PRECONDITION(IsLockHeld());
+ PRECONDITION(key != NULL);
+ PRECONDITION(!Traits::IsNull(newCxt) && !Traits::IsDeleted(newCxt));
+ PRECONDITION(key == newCxt->Identity);
+ POSTCONDITION(CheckPointer(RETVAL));
+ }
+ CONTRACT_END;
+
+ // Forbid the GC from messing with the hash table.
+ GCX_FORBID();
+
+ ExternalObjectContext* cxt = Find(key);
+ if (cxt == NULL)
+ cxt = Add(newCxt);
+
+ RETURN cxt;
+ }
+
+ void Remove(_In_ ExternalObjectContext* cxt)
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ PRECONDITION(!Traits::IsNull(cxt) && !Traits::IsDeleted(cxt));
+ PRECONDITION(cxt->Identity != NULL);
+
+ // The GC thread doesn't have to take the lock
+ // since all other threads access in cooperative mode
+ PRECONDITION(
+ (IsLockHeld() && GetThread()->PreemptiveGCDisabled())
+ || Debug_IsLockedViaThreadSuspension());
+ }
+ CONTRACTL_END;
+
+ _hashMap.Remove(cxt->Identity);
+ }
+ };
+
+ // Global instance
+ Volatile ExtObjCxtCache::g_Instance;
+
+ // Defined handle types for the specific object uses.
+ const HandleType InstanceHandleType{ HNDTYPE_STRONG };
+
+ void* CallComputeVTables(
+ _In_ OBJECTREF* implPROTECTED,
+ _In_ OBJECTREF* instancePROTECTED,
+ _In_ INT32 flags,
+ _Out_ DWORD* vtableCount)
+ {
+ CONTRACTL
+ {
+ THROWS;
+ MODE_COOPERATIVE;
+ PRECONDITION(implPROTECTED != NULL);
+ PRECONDITION(instancePROTECTED != NULL);
+ PRECONDITION(vtableCount != NULL);
+ }
+ CONTRACTL_END;
+
+ void* vtables = NULL;
+
+ PREPARE_NONVIRTUAL_CALLSITE(METHOD__COMWRAPPERS__COMPUTE_VTABLES);
+ DECLARE_ARGHOLDER_ARRAY(args, 4);
+ args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(*implPROTECTED);
+ args[ARGNUM_1] = OBJECTREF_TO_ARGHOLDER(*instancePROTECTED);
+ args[ARGNUM_2] = DWORD_TO_ARGHOLDER(flags);
+ args[ARGNUM_3] = PTR_TO_ARGHOLDER(vtableCount);
+ CALL_MANAGED_METHOD(vtables, void*, args);
+
+ return vtables;
+ }
+
+ OBJECTREF CallGetObject(
+ _In_ OBJECTREF* implPROTECTED,
+ _In_ IUnknown* externalComObject,
+ _In_ INT32 flags)
+ {
+ CONTRACTL
+ {
+ THROWS;
+ MODE_COOPERATIVE;
+ PRECONDITION(implPROTECTED != NULL);
+ PRECONDITION(externalComObject != NULL);
+ }
+ CONTRACTL_END;
+
+ OBJECTREF retObjRef;
+
+ PREPARE_NONVIRTUAL_CALLSITE(METHOD__COMWRAPPERS__CREATE_OBJECT);
+ DECLARE_ARGHOLDER_ARRAY(args, 3);
+ args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(*implPROTECTED);
+ args[ARGNUM_1] = PTR_TO_ARGHOLDER(externalComObject);
+ args[ARGNUM_2] = DWORD_TO_ARGHOLDER(flags);
+ CALL_MANAGED_METHOD(retObjRef, OBJECTREF, args);
+
+ return retObjRef;
+ }
+
+ void CallReleaseObjects(
+ _In_ OBJECTREF* implPROTECTED,
+ _In_ OBJECTREF* objsEnumPROTECTED)
+ {
+ CONTRACTL
+ {
+ THROWS;
+ MODE_COOPERATIVE;
+ PRECONDITION(implPROTECTED != NULL);
+ PRECONDITION(objsEnumPROTECTED != NULL);
+ }
+ CONTRACTL_END;
+
+ PREPARE_NONVIRTUAL_CALLSITE(METHOD__COMWRAPPERS__RELEASE_OBJECTS);
+ DECLARE_ARGHOLDER_ARRAY(args, 2);
+ args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(*implPROTECTED);
+ args[ARGNUM_1] = OBJECTREF_TO_ARGHOLDER(*objsEnumPROTECTED);
+ CALL_MANAGED_METHOD_NORET(args);
+ }
+
+ void* GetOrCreateComInterfaceForObjectInternal(
+ _In_opt_ OBJECTREF impl,
+ _In_ OBJECTREF instance,
+ _In_ CreateComInterfaceFlags flags)
+ {
+ CONTRACT(void*)
+ {
+ THROWS;
+ MODE_COOPERATIVE;
+ PRECONDITION(instance != NULL);
+ POSTCONDITION(CheckPointer(RETVAL));
+ }
+ CONTRACT_END;
+
+ HRESULT hr;
+
+ SafeComHolder newWrapper;
+ void* wrapperRaw = NULL;
+
+ struct
+ {
+ OBJECTREF implRef;
+ OBJECTREF instRef;
+ } gc;
+ ::ZeroMemory(&gc, sizeof(gc));
+ GCPROTECT_BEGIN(gc);
+
+ gc.implRef = impl;
+ gc.instRef = instance;
+
+ // Check the object's SyncBlock for a managed object wrapper.
+ SyncBlock* syncBlock = gc.instRef->GetSyncBlock();
+ InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfo();
+ _ASSERTE(syncBlock->IsPrecious());
+
+ // Query the associated InteropSyncBlockInfo for an existing managed object wrapper.
+ if (!interopInfo->TryGetManagedObjectComWrapper(&wrapperRaw))
+ {
+ // Compute VTables for the new existing COM object using the supplied COM Wrappers implementation.
+ //
+ // N.B. Calling to compute the associated VTables is perhaps early since no lock
+ // is taken. However, a key assumption here is that the returned memory will be
+ // idempotent for the same object.
+ DWORD vtableCount;
+ void* vtables = CallComputeVTables(&gc.implRef, &gc.instRef, flags, &vtableCount);
+
+ // Re-query the associated InteropSyncBlockInfo for an existing managed object wrapper.
+ if (!interopInfo->TryGetManagedObjectComWrapper(&wrapperRaw))
+ {
+ OBJECTHANDLE instHandle = GetAppDomain()->CreateTypedHandle(gc.instRef, InstanceHandleType);
+
+ // Call the InteropLib and create the associated managed object wrapper.
+ hr = InteropLib::Com::CreateWrapperForObject(
+ instHandle,
+ vtableCount,
+ vtables,
+ flags,
+ &newWrapper);
+ if (FAILED(hr))
+ {
+ DestroyHandleCommon(instHandle, InstanceHandleType);
+ COMPlusThrowHR(hr);
+ }
+ _ASSERTE(!newWrapper.IsNull());
+
+ // Try setting the newly created managed object wrapper on the InteropSyncBlockInfo.
+ if (!interopInfo->TrySetManagedObjectComWrapper(newWrapper))
+ {
+ // The new wrapper couldn't be set which means a wrapper already exists.
+ newWrapper.Release();
+
+ // If the managed object wrapper couldn't be set, then
+ // it should be possible to get the current one.
+ if (!interopInfo->TryGetManagedObjectComWrapper(&wrapperRaw))
+ {
+ UNREACHABLE();
+ }
+ }
+ }
+ }
+
+ // Determine what to return.
+ if (!newWrapper.IsNull())
+ {
+ // A new managed object wrapper was created, remove the object from the holder.
+ // No AddRef() here since the wrapper should be created with a reference.
+ wrapperRaw = newWrapper.Extract();
+ STRESS_LOG1(LF_INTEROP, LL_INFO100, "Created MOW: 0x%p\n", wrapperRaw);
+ }
+ else
+ {
+ _ASSERTE(wrapperRaw != NULL);
+
+ // It is possible the supplied wrapper is no longer valid. If so, reactivate the
+ // wrapper using the protected OBJECTREF.
+ IUnknown* wrapper = static_cast(wrapperRaw);
+ hr = InteropLib::Com::IsActiveWrapper(wrapper);
+ if (hr == S_FALSE)
+ {
+ STRESS_LOG1(LF_INTEROP, LL_INFO100, "Reactivating MOW: 0x%p\n", wrapperRaw);
+ OBJECTHANDLE h = GetAppDomain()->CreateTypedHandle(gc.instRef, InstanceHandleType);
+ hr = InteropLib::Com::ReactivateWrapper(wrapper, static_cast(h));
+ }
+
+ if (FAILED(hr))
+ COMPlusThrowHR(hr);
+ }
+
+ GCPROTECT_END();
+
+ RETURN wrapperRaw;
+ }
+
+ OBJECTREF GetOrCreateObjectForComInstanceInternal(
+ _In_opt_ OBJECTREF impl,
+ _In_ IUnknown* identity,
+ _In_ CreateObjectFlags flags,
+ _In_opt_ OBJECTREF wrapperMaybe)
+ {
+ CONTRACT(OBJECTREF)
+ {
+ THROWS;
+ MODE_COOPERATIVE;
+ PRECONDITION(identity != NULL);
+ POSTCONDITION(RETVAL != NULL);
+ }
+ CONTRACT_END;
+
+ HRESULT hr;
+ ExternalObjectContext* extObjCxt = NULL;
+
+ struct
+ {
+ OBJECTREF implRef;
+ OBJECTREF wrapperMaybeRef;
+ OBJECTREF objRef;
+ } gc;
+ ::ZeroMemory(&gc, sizeof(gc));
+ GCPROTECT_BEGIN(gc);
+
+ gc.implRef = impl;
+ gc.wrapperMaybeRef = wrapperMaybe;
+
+ ExtObjCxtCache* cache = ExtObjCxtCache::GetInstance();
+
+ // Check if the user requested a unique instance.
+ bool uniqueInstance = !!(flags & CreateObjectFlags::CreateObjectFlags_UniqueInstance);
+ if (!uniqueInstance)
+ {
+ // Query the external object cache
+ ExtObjCxtCache::LockHolder lock(cache);
+ extObjCxt = cache->Find(identity);
+ }
+
+ if (extObjCxt != NULL)
+ {
+ gc.objRef = extObjCxt->GetObjectRef();
+ }
+ else
+ {
+ // Create context instance for the possibly new external object.
+ ExternalWrapperResultHolder resultHolder;
+ hr = InteropLib::Com::CreateWrapperForExternal(
+ identity,
+ flags,
+ sizeof(ExternalObjectContext),
+ &resultHolder);
+ if (FAILED(hr))
+ COMPlusThrowHR(hr);
+
+ // The user could have supplied a wrapper so assign that now.
+ gc.objRef = gc.wrapperMaybeRef;
+
+ // If the wrapper hasn't been set yet, call the implementation to create one.
+ if (gc.objRef == NULL)
+ {
+ gc.objRef = CallGetObject(&gc.implRef, identity, flags);
+ if (gc.objRef == NULL)
+ COMPlusThrow(kArgumentNullException);
+ }
+
+ // Construct the new context with the object details.
+ DWORD flags = (resultHolder.Result.FromTrackerRuntime
+ ? ExternalObjectContext::Flags_ReferenceTracker
+ : ExternalObjectContext::Flags_None) |
+ (uniqueInstance
+ ? ExternalObjectContext::Flags_None
+ : ExternalObjectContext::Flags_InCache);
+ ExternalObjectContext::Construct(
+ resultHolder.GetContext(),
+ identity,
+ GetCurrentCtxCookie(),
+ gc.objRef->GetSyncBlockIndex(),
+ flags);
+
+ if (uniqueInstance)
+ {
+ extObjCxt = resultHolder.GetContext();
+ }
+ else
+ {
+ // Attempt to insert the new context into the cache.
+ ExtObjCxtCache::LockHolder lock(cache);
+ extObjCxt = cache->FindOrAdd(identity, resultHolder.GetContext());
+ }
+
+ // If the returned context matches the new context it means the
+ // new context was inserted or a unique instance was requested.
+ if (extObjCxt == resultHolder.GetContext())
+ {
+ // Update the object's SyncBlock with a handle to the context for runtime cleanup.
+ SyncBlock* syncBlock = gc.objRef->GetSyncBlock();
+ InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfo();
+ _ASSERTE(syncBlock->IsPrecious());
+
+ // Since the caller has the option of providing a wrapper, it is
+ // possible the supplied wrapper already has an associated external
+ // object and an object can only be associated with one external object.
+ if (!interopInfo->TrySetExternalComObjectContext((void**)extObjCxt))
+ {
+ // Failed to set the context; one must already exist.
+ // Remove from the cache above as well.
+ ExtObjCxtCache::LockHolder lock(cache);
+ cache->Remove(resultHolder.GetContext());
+
+ COMPlusThrow(kNotSupportedException);
+ }
+
+ // Detach from the holder to avoid cleanup.
+ (void)resultHolder.DetachContext();
+ STRESS_LOG2(LF_INTEROP, LL_INFO100, "Created EOC (Unique Instance: %d): 0x%p\n", (int)uniqueInstance, extObjCxt);
+ }
+
+ _ASSERTE(extObjCxt->IsActive());
+ }
+
+ GCPROTECT_END();
+
+ RETURN gc.objRef;
+ }
+}
+
+namespace InteropLibImports
+{
+ void* MemAlloc(_In_ size_t sizeInBytes, _In_ AllocScenario scenario) noexcept
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ PRECONDITION(sizeInBytes != 0);
+ }
+ CONTRACTL_END;
+
+ return ::malloc(sizeInBytes);
+ }
+
+ void MemFree(_In_ void* mem, _In_ AllocScenario scenario) noexcept
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ PRECONDITION(mem != NULL);
+ }
+ CONTRACTL_END;
+
+ ::free(mem);
+ }
+
+ HRESULT AddMemoryPressureForExternal(_In_ UINT64 memoryInBytes) noexcept
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ MODE_PREEMPTIVE;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+ BEGIN_EXTERNAL_ENTRYPOINT(&hr)
+ {
+ GCInterface::NewAddMemoryPressure(memoryInBytes);
+ }
+ END_EXTERNAL_ENTRYPOINT;
+
+ return hr;
+ }
+
+ HRESULT RemoveMemoryPressureForExternal(_In_ UINT64 memoryInBytes) noexcept
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ MODE_PREEMPTIVE;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+ BEGIN_EXTERNAL_ENTRYPOINT(&hr)
+ {
+ GCInterface::NewRemoveMemoryPressure(memoryInBytes);
+ }
+ END_EXTERNAL_ENTRYPOINT;
+
+ return hr;
+ }
+
+ HRESULT RequestGarbageCollectionForExternal(_In_ GcRequest req) noexcept
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ MODE_PREEMPTIVE;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+ BEGIN_EXTERNAL_ENTRYPOINT(&hr)
+ {
+ GCX_COOP_THREAD_EXISTS(GET_THREAD());
+ if (req == GcRequest::FullBlocking)
+ {
+ GCHeapUtilities::GetGCHeap()->GarbageCollect(2, true, collection_blocking | collection_optimized);
+ }
+ else
+ {
+ _ASSERTE(req == GcRequest::Default);
+ GCHeapUtilities::GetGCHeap()->GarbageCollect();
+ }
+ }
+ END_EXTERNAL_ENTRYPOINT;
+
+ return hr;
+ }
+
+ HRESULT WaitForRuntimeFinalizerForExternal() noexcept
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ MODE_PREEMPTIVE;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+ BEGIN_EXTERNAL_ENTRYPOINT(&hr)
+ {
+ FinalizerThread::FinalizerThreadWait();
+ }
+ END_EXTERNAL_ENTRYPOINT;
+
+ return hr;
+ }
+
+ HRESULT ReleaseExternalObjectsFromCurrentThread() noexcept
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ MODE_PREEMPTIVE;
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+ BEGIN_EXTERNAL_ENTRYPOINT(&hr)
+ {
+ // Switch to cooperative mode so the cache can be queried.
+ GCX_COOP();
+
+ struct
+ {
+ OBJECTREF implRef;
+ OBJECTREF objsEnumRef;
+ } gc;
+ ::ZeroMemory(&gc, sizeof(gc));
+ GCPROTECT_BEGIN(gc);
+
+ gc.implRef = NULL; // Use the globally registered implementation.
+
+ // Pass the objects along to get released.
+ ExtObjCxtCache* cache = ExtObjCxtCache::GetInstanceNoThrow();
+ gc.objsEnumRef = cache->CreateManagedEnumerable(
+ ExternalObjectContext::Flags_ReferenceTracker,
+ GetCurrentCtxCookie());
+
+ CallReleaseObjects(&gc.implRef, &gc.objsEnumRef);
+
+ GCPROTECT_END();
+ }
+ END_EXTERNAL_ENTRYPOINT;
+
+ return hr;
+ }
+
+ void DeleteObjectInstanceHandle(_In_ InteropLib::OBJECTHANDLE handle) noexcept
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ PRECONDITION(handle != NULL);
+ }
+ CONTRACTL_END;
+
+ DestroyHandleCommon(static_cast<::OBJECTHANDLE>(handle), InstanceHandleType);
+ }
+
+ bool GetGlobalPeggingState() noexcept
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ return (RCWWalker::s_bIsGlobalPeggingOn != FALSE);
+ }
+
+ void SetGlobalPeggingState(_In_ bool state) noexcept
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ }
+ CONTRACTL_END;
+
+ BOOL newState = state ? TRUE : FALSE;
+ VolatileStore(&RCWWalker::s_bIsGlobalPeggingOn, newState);
+ }
+
+ HRESULT GetOrCreateTrackerTargetForExternal(
+ _In_ IUnknown* externalComObject,
+ _In_ CreateObjectFlags externalObjectFlags,
+ _In_ CreateComInterfaceFlags trackerTargetFlags,
+ _Outptr_ void** trackerTarget) noexcept
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ MODE_PREEMPTIVE;
+ PRECONDITION(externalComObject != NULL);
+ PRECONDITION(trackerTarget != NULL);
+ }
+ CONTRACTL_END;
+
+ HRESULT hr = S_OK;
+ BEGIN_EXTERNAL_ENTRYPOINT(&hr)
+ {
+ // Switch to Cooperative mode since object references
+ // are being manipulated.
+ GCX_COOP();
+
+ struct
+ {
+ OBJECTREF implRef;
+ OBJECTREF wrapperMaybeRef;
+ OBJECTREF objRef;
+ } gc;
+ ::ZeroMemory(&gc, sizeof(gc));
+ GCPROTECT_BEGIN(gc);
+
+ gc.implRef = NULL; // Use the globally registered implementation.
+ gc.wrapperMaybeRef = NULL; // No supplied wrapper here.
+
+ // Get wrapper for external object
+ gc.objRef = GetOrCreateObjectForComInstanceInternal(
+ gc.implRef,
+ externalComObject,
+ externalObjectFlags,
+ gc.wrapperMaybeRef);
+
+ // Get wrapper for managed object
+ *trackerTarget = GetOrCreateComInterfaceForObjectInternal(
+ gc.implRef,
+ gc.objRef,
+ trackerTargetFlags);
+
+ STRESS_LOG2(LF_INTEROP, LL_INFO100, "Created Target for External: 0x%p => 0x%p\n", OBJECTREFToObject(gc.objRef), *trackerTarget);
+ GCPROTECT_END();
+ }
+ END_EXTERNAL_ENTRYPOINT;
+
+ return hr;
+ }
+
+ struct RuntimeCallContext
+ {
+ // Iterators for all known external objects.
+ ExtObjCxtCache::Iterator Curr;
+ ExtObjCxtCache::Iterator End;
+
+ // Pointer to cache used to create object references.
+ ExtObjCxtRefCache* RefCache;
+
+ RuntimeCallContext(_In_ ExtObjCxtCache* cache)
+ : Curr{ cache->_hashMap.Begin() }
+ , End{ cache->_hashMap.End() }
+ , RefCache{ cache->GetRefCache() }
+ { }
+ };
+
+ HRESULT IteratorNext(
+ _In_ RuntimeCallContext* runtimeContext,
+ _Outptr_result_maybenull_ void** extObjContext) noexcept
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_COOPERATIVE;
+ PRECONDITION(runtimeContext != NULL);
+ PRECONDITION(extObjContext != NULL);
+
+ // Should only be called during a GC suspension
+ PRECONDITION(Debug_IsLockedViaThreadSuspension());
+ }
+ CONTRACTL_END;
+
+ if (runtimeContext->Curr == runtimeContext->End)
+ {
+ *extObjContext = NULL;
+ return S_FALSE;
+ }
+
+ ExtObjCxtCache::Element e = *runtimeContext->Curr++;
+ *extObjContext = e;
+ return S_OK;
+ }
+
+ HRESULT FoundReferencePath(
+ _In_ RuntimeCallContext* runtimeContext,
+ _In_ void* extObjContextRaw,
+ _In_ InteropLib::OBJECTHANDLE handle) noexcept
+ {
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_COOPERATIVE;
+ PRECONDITION(runtimeContext != NULL);
+ PRECONDITION(extObjContextRaw != NULL);
+ PRECONDITION(handle != NULL);
+
+ // Should only be called during a GC suspension
+ PRECONDITION(Debug_IsLockedViaThreadSuspension());
+ }
+ CONTRACTL_END;
+
+ // Get the external object's managed wrapper
+ ExternalObjectContext* extObjContext = static_cast(extObjContextRaw);
+ OBJECTREF source = extObjContext->GetObjectRef();
+
+ // Get the target of the external object's reference.
+ ::OBJECTHANDLE objectHandle = static_cast<::OBJECTHANDLE>(handle);
+ OBJECTREF target = ObjectFromHandle(objectHandle);
+
+ // If these point at the same object don't create a reference.
+ if (source->PassiveGetSyncBlock() == target->PassiveGetSyncBlock())
+ return S_FALSE;
+
+ STRESS_LOG2(LF_INTEROP, LL_INFO1000, "Found reference path: 0x%p => 0x%p\n",
+ OBJECTREFToObject(source),
+ OBJECTREFToObject(target));
+ return runtimeContext->RefCache->AddReferenceFromObjectToObject(source, target);
+ }
+}
+
+#ifdef FEATURE_COMWRAPPERS
+
+void* QCALLTYPE ComWrappersNative::GetOrCreateComInterfaceForObject(
+ _In_ QCall::ObjectHandleOnStack comWrappersImpl,
+ _In_ QCall::ObjectHandleOnStack instance,
+ _In_ INT32 flags)
+{
+ QCALL_CONTRACT;
+
+ void* wrapper = NULL;
+
+ BEGIN_QCALL;
+
+ // Switch to Cooperative mode since object references
+ // are being manipulated.
+ {
+ GCX_COOP();
+ wrapper = GetOrCreateComInterfaceForObjectInternal(
+ ObjectToOBJECTREF(*comWrappersImpl.m_ppObject),
+ ObjectToOBJECTREF(*instance.m_ppObject),
+ (CreateComInterfaceFlags)flags);
+ }
+
+ END_QCALL;
+
+ _ASSERTE(wrapper != NULL);
+ return wrapper;
+}
+
+void QCALLTYPE ComWrappersNative::GetOrCreateObjectForComInstance(
+ _In_ QCall::ObjectHandleOnStack comWrappersImpl,
+ _In_ void* ext,
+ _In_ INT32 flags,
+ _In_ QCall::ObjectHandleOnStack wrapperMaybe,
+ _Inout_ QCall::ObjectHandleOnStack retValue)
+{
+ QCALL_CONTRACT;
+
+ _ASSERTE(ext != NULL);
+
+ BEGIN_QCALL;
+
+ HRESULT hr;
+ IUnknown* externalComObject = reinterpret_cast(ext);
+
+ // Determine the true identity of the object
+ SafeComHolder identity;
+ hr = externalComObject->QueryInterface(IID_IUnknown, &identity);
+ _ASSERTE(hr == S_OK);
+
+ // Switch to Cooperative mode since object references
+ // are being manipulated.
+ {
+ GCX_COOP();
+ OBJECTREF newObj = GetOrCreateObjectForComInstanceInternal(
+ ObjectToOBJECTREF(*comWrappersImpl.m_ppObject),
+ identity,
+ (CreateObjectFlags)flags,
+ ObjectToOBJECTREF(*wrapperMaybe.m_ppObject));
+
+ // Set the return value
+ retValue.Set(newObj);
+ }
+
+ END_QCALL;
+}
+
+void QCALLTYPE ComWrappersNative::GetIUnknownImpl(
+ _Out_ void** fpQueryInterface,
+ _Out_ void** fpAddRef,
+ _Out_ void** fpRelease)
+{
+ QCALL_CONTRACT;
+
+ _ASSERTE(fpQueryInterface != NULL);
+ _ASSERTE(fpAddRef != NULL);
+ _ASSERTE(fpRelease != NULL);
+
+ BEGIN_QCALL;
+
+ InteropLib::Com::GetIUnknownImpl(fpQueryInterface, fpAddRef, fpRelease);
+
+ END_QCALL;
+}
+
+void ComWrappersNative::DestroyManagedObjectComWrapper(_In_ void* wrapper)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ MODE_ANY;
+ PRECONDITION(wrapper != NULL);
+ }
+ CONTRACTL_END;
+
+ STRESS_LOG1(LF_INTEROP, LL_INFO100, "Destroying MOW: 0x%p\n", wrapper);
+ InteropLib::Com::DestroyWrapperForObject(wrapper);
+}
+
+void ComWrappersNative::DestroyExternalComObjectContext(_In_ void* contextRaw)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ MODE_ANY;
+ PRECONDITION(contextRaw != NULL);
+ }
+ CONTRACTL_END;
+
+#ifdef _DEBUG
+ ExternalObjectContext* context = static_cast(contextRaw);
+ _ASSERTE(!context->IsActive());
+#endif
+
+ STRESS_LOG1(LF_INTEROP, LL_INFO100, "Destroying EOC: 0x%p\n", contextRaw);
+ InteropLib::Com::DestroyWrapperForExternal(contextRaw);
+}
+
+void ComWrappersNative::MarkExternalComObjectContextCollected(_In_ void* contextRaw)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_ANY;
+ PRECONDITION(contextRaw != NULL);
+ PRECONDITION(GCHeapUtilities::IsGCInProgress());
+ }
+ CONTRACTL_END;
+
+ ExternalObjectContext* context = static_cast(contextRaw);
+ _ASSERTE(context->IsActive());
+ context->MarkCollected();
+
+ bool inCache = context->IsSet(ExternalObjectContext::Flags_InCache);
+ STRESS_LOG2(LF_INTEROP, LL_INFO100, "Mark Collected EOC (In Cache: %d): 0x%p\n", (int)inCache, contextRaw);
+
+ // Verify the caller didn't ignore the cache during creation.
+ if (inCache)
+ {
+ ExtObjCxtCache* cache = ExtObjCxtCache::GetInstanceNoThrow();
+ cache->Remove(context);
+ }
+}
+
+#endif // FEATURE_COMWRAPPERS
+
+void Interop::OnGCStarted(_In_ int nCondemnedGeneration)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+#ifdef FEATURE_COMINTEROP
+ //
+ // Let GC detect managed/native cycles with input from jupiter
+ // Jupiter will
+ // 1. Report reference from RCW to CCW based on native reference in Jupiter
+ // 2. Identify the subset of CCWs that needs to be rooted
+ //
+ // We'll build the references from RCW to CCW using
+ // 1. Preallocated arrays
+ // 2. Dependent handles
+ //
+ RCWWalker::OnGCStarted(nCondemnedGeneration);
+#endif // FEATURE_COMINTEROP
+
+#ifdef FEATURE_COMWRAPPERS
+ //
+ // Note that we could get nested GCStart/GCEnd calls, such as :
+ // GCStart for Gen 2 background GC
+ // GCStart for Gen 0/1 foregorund GC
+ // GCEnd for Gen 0/1 foreground GC
+ // ....
+ // GCEnd for Gen 2 background GC
+ //
+ // The nCondemnedGeneration >= 2 check takes care of this nesting problem
+ //
+ // See Interop::OnGCFinished()
+ if (nCondemnedGeneration >= 2)
+ {
+ // If no cache exists, then there is nothing to do here.
+ ExtObjCxtCache* cache = ExtObjCxtCache::GetInstanceNoThrow();
+ if (cache != NULL)
+ {
+ STRESS_LOG0(LF_INTEROP, LL_INFO10000, "Begin Reference Tracking\n");
+ ExtObjCxtRefCache* refCache = cache->GetRefCache();
+
+ // Reset the ref cache
+ refCache->ResetDependentHandles();
+
+ // Create a call context for the InteropLib.
+ InteropLibImports::RuntimeCallContext cxt(cache);
+ (void)InteropLib::Com::BeginExternalObjectReferenceTracking(&cxt);
+
+ // Shrink cache and clear unused handles.
+ refCache->ShrinkDependentHandles();
+ }
+ }
+#endif // FEATURE_COMWRAPPERS
+}
+
+void Interop::OnGCFinished(_In_ int nCondemnedGeneration)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ }
+ CONTRACTL_END;
+
+#ifdef FEATURE_COMINTEROP
+ //
+ // Tell Jupiter GC has finished
+ //
+ RCWWalker::OnGCFinished(nCondemnedGeneration);
+#endif // FEATURE_COMINTEROP
+
+#ifdef FEATURE_COMWRAPPERS
+ //
+ // Note that we could get nested GCStart/GCEnd calls, such as :
+ // GCStart for Gen 2 background GC
+ // GCStart for Gen 0/1 foregorund GC
+ // GCEnd for Gen 0/1 foreground GC
+ // ....
+ // GCEnd for Gen 2 background GC
+ //
+ // The nCondemnedGeneration >= 2 check takes care of this nesting problem
+ //
+ // See Interop::OnGCStarted()
+ if (nCondemnedGeneration >= 2)
+ {
+ ExtObjCxtCache* cache = ExtObjCxtCache::GetInstanceNoThrow();
+ if (cache != NULL)
+ {
+ (void)InteropLib::Com::EndExternalObjectReferenceTracking();
+ STRESS_LOG0(LF_INTEROP, LL_INFO10000, "End Reference Tracking\n");
+ }
+ }
+#endif // FEATURE_COMWRAPPERS
+}
diff --git a/src/coreclr/src/vm/interoplibinterface.h b/src/coreclr/src/vm/interoplibinterface.h
new file mode 100644
index 0000000000000..2ed54cfc90706
--- /dev/null
+++ b/src/coreclr/src/vm/interoplibinterface.h
@@ -0,0 +1,48 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+//
+// Interface between the VM and Interop library.
+//
+
+#ifdef FEATURE_COMWRAPPERS
+
+// Native calls for the managed ComWrappers API
+class ComWrappersNative
+{
+public: // Native QCalls for the abstract ComWrappers managed type.
+ static void QCALLTYPE GetIUnknownImpl(
+ _Out_ void** fpQueryInterface,
+ _Out_ void** fpAddRef,
+ _Out_ void** fpRelease);
+
+ static void* QCALLTYPE GetOrCreateComInterfaceForObject(
+ _In_ QCall::ObjectHandleOnStack comWrappersImpl,
+ _In_ QCall::ObjectHandleOnStack instance,
+ _In_ INT32 flags);
+
+ static void QCALLTYPE GetOrCreateObjectForComInstance(
+ _In_ QCall::ObjectHandleOnStack comWrappersImpl,
+ _In_ void* externalComObject,
+ _In_ INT32 flags,
+ _In_ QCall::ObjectHandleOnStack wrapperMaybe,
+ _Inout_ QCall::ObjectHandleOnStack retValue);
+
+public: // Lifetime management for COM Wrappers
+ static void DestroyManagedObjectComWrapper(_In_ void* wrapper);
+ static void DestroyExternalComObjectContext(_In_ void* context);
+ static void MarkExternalComObjectContextCollected(_In_ void* context);
+};
+
+#endif // FEATURE_COMWRAPPERS
+
+class Interop
+{
+public:
+ // Notify when GC started
+ static void OnGCStarted(_In_ int nCondemnedGeneration);
+
+ // Notify when GC finished
+ static void OnGCFinished(_In_ int nCondemnedGeneration);
+};
diff --git a/src/coreclr/src/vm/interoputil.cpp b/src/coreclr/src/vm/interoputil.cpp
index 0ec23b624aede..fc87cf9277d53 100644
--- a/src/coreclr/src/vm/interoputil.cpp
+++ b/src/coreclr/src/vm/interoputil.cpp
@@ -25,6 +25,7 @@
#include "siginfo.hpp"
#include "eemessagebox.h"
#include "finalizerthread.h"
+#include "interoplibinterface.h"
#ifdef FEATURE_COMINTEROP
#include "cominterfacemarshaler.h"
@@ -1936,6 +1937,12 @@ void MinorCleanupSyncBlockComData(InteropSyncBlockInfo* pInteropInfo)
RCW* pRCW = pInteropInfo->GetRawRCW();
if (pRCW)
pRCW->MinorCleanup();
+
+#ifdef FEATURE_COMWRAPPERS
+ void* eoc;
+ if (pInteropInfo->TryGetExternalComObjectContext(&eoc))
+ ComWrappersNative::MarkExternalComObjectContextCollected(eoc);
+#endif // FEATURE_COMWRAPPERS
}
void CleanupSyncBlockComData(InteropSyncBlockInfo* pInteropInfo)
@@ -1976,6 +1983,22 @@ void CleanupSyncBlockComData(InteropSyncBlockInfo* pInteropInfo)
pInteropInfo->SetCCW(NULL);
pCCW->Cleanup();
}
+
+#ifdef FEATURE_COMWRAPPERS
+ void* mocw;
+ if (pInteropInfo->TryGetManagedObjectComWrapper(&mocw))
+ {
+ (void)pInteropInfo->TrySetManagedObjectComWrapper(NULL, mocw);
+ ComWrappersNative::DestroyManagedObjectComWrapper(mocw);
+ }
+
+ void* eoc;
+ if (pInteropInfo->TryGetExternalComObjectContext(&eoc))
+ {
+ (void)pInteropInfo->TrySetExternalComObjectContext(NULL, eoc);
+ ComWrappersNative::DestroyExternalComObjectContext(eoc);
+ }
+#endif // FEATURE_COMWRAPPERS
}
void ReleaseRCWsInCachesNoThrow(LPVOID pCtxCookie)
diff --git a/src/coreclr/src/vm/metasig.h b/src/coreclr/src/vm/metasig.h
index 8699224e0f117..e7830468cfa58 100644
--- a/src/coreclr/src/vm/metasig.h
+++ b/src/coreclr/src/vm/metasig.h
@@ -196,6 +196,9 @@ DEFINE_METASIG_T(SM(PtrTypeName_ArrType_RetVoid, P(g(TYPENAMENATIVE)) a(C(TYPE))
DEFINE_METASIG_T(SM(PtrTypeName_RetVoid, P(g(TYPENAMENATIVE)), v))
DEFINE_METASIG_T(SM(PtrTypeName_Int_RetVoid, P(g(TYPENAMENATIVE)) i, v))
DEFINE_METASIG_T(SM(Exception_IntPtr_RetException, C(EXCEPTION) I, C(EXCEPTION)))
+DEFINE_METASIG_T(SM(ComWrappers_Obj_CreateFlags_RefInt_RetPtrVoid, C(COMWRAPPERS) j g(CREATECOMINTERFACEFLAGS) r(i), P(v)))
+DEFINE_METASIG_T(SM(ComWrappers_IntPtr_CreateFlags_RetObj, C(COMWRAPPERS) I g(CREATEOBJECTFLAGS), j))
+DEFINE_METASIG_T(SM(ComWrappers_IEnumerable_RetVoid, C(COMWRAPPERS) C(IENUMERABLE), v))
#endif // FEATURE_COMINTEROP
DEFINE_METASIG(SM(Int_RetVoid, i, v))
DEFINE_METASIG(SM(Int_Int_RetVoid, i i, v))
diff --git a/src/coreclr/src/vm/mscorlib.cpp b/src/coreclr/src/vm/mscorlib.cpp
index 40a1e14f58ff7..6cdd5d9e1a9d1 100644
--- a/src/coreclr/src/vm/mscorlib.cpp
+++ b/src/coreclr/src/vm/mscorlib.cpp
@@ -67,6 +67,7 @@
#include "variant.h"
#include "oavariant.h"
#include "mngstdinterfaces.h"
+#include "interoplibinterface.h"
#endif // FEATURE_COMINTEROP
#include "stubhelpers.h"
diff --git a/src/coreclr/src/vm/mscorlib.h b/src/coreclr/src/vm/mscorlib.h
index 3ce6149ded554..12ec6d8929123 100644
--- a/src/coreclr/src/vm/mscorlib.h
+++ b/src/coreclr/src/vm/mscorlib.h
@@ -452,12 +452,19 @@ DEFINE_METHOD(ICUSTOM_MARSHALER, MARSHAL_NATIVE_TO_MANAGED,MarshalNativeToMan
DEFINE_METHOD(ICUSTOM_MARSHALER, MARSHAL_MANAGED_TO_NATIVE,MarshalManagedToNative, IM_Obj_RetIntPtr)
DEFINE_METHOD(ICUSTOM_MARSHALER, CLEANUP_NATIVE_DATA, CleanUpNativeData, IM_IntPtr_RetVoid)
DEFINE_METHOD(ICUSTOM_MARSHALER, CLEANUP_MANAGED_DATA, CleanUpManagedData, IM_Obj_RetVoid)
-DEFINE_METHOD(ICUSTOM_MARSHALER, GET_NATIVE_DATA_SIZE, GetNativeDataSize, IM_RetInt)
+DEFINE_METHOD(ICUSTOM_MARSHALER, GET_NATIVE_DATA_SIZE, GetNativeDataSize, IM_RetInt)
#ifdef FEATURE_COMINTEROP
DEFINE_CLASS(ICUSTOM_QUERYINTERFACE, Interop, ICustomQueryInterface)
-DEFINE_METHOD(ICUSTOM_QUERYINTERFACE, GET_INTERFACE, GetInterface, IM_RefGuid_OutIntPtr_RetCustomQueryInterfaceResult)
+DEFINE_METHOD(ICUSTOM_QUERYINTERFACE, GET_INTERFACE, GetInterface, IM_RefGuid_OutIntPtr_RetCustomQueryInterfaceResult)
DEFINE_CLASS(CUSTOMQUERYINTERFACERESULT, Interop, CustomQueryInterfaceResult)
+
+DEFINE_CLASS(COMWRAPPERS, Interop, ComWrappers)
+DEFINE_CLASS(CREATECOMINTERFACEFLAGS, Interop, CreateComInterfaceFlags)
+DEFINE_CLASS(CREATEOBJECTFLAGS, Interop, CreateObjectFlags)
+DEFINE_METHOD(COMWRAPPERS, COMPUTE_VTABLES, CallComputeVtables, SM_ComWrappers_Obj_CreateFlags_RefInt_RetPtrVoid)
+DEFINE_METHOD(COMWRAPPERS, CREATE_OBJECT, CallCreateObject, SM_ComWrappers_IntPtr_CreateFlags_RetObj)
+DEFINE_METHOD(COMWRAPPERS, RELEASE_OBJECTS, CallReleaseObjects, SM_ComWrappers_IEnumerable_RetVoid)
#endif //FEATURE_COMINTEROP
DEFINE_CLASS(SERIALIZATION_INFO, Serialization, SerializationInfo)
diff --git a/src/coreclr/src/vm/rcwrefcache.cpp b/src/coreclr/src/vm/rcwrefcache.cpp
index 652f1820e3e1d..c22a84e6816a5 100644
--- a/src/coreclr/src/vm/rcwrefcache.cpp
+++ b/src/coreclr/src/vm/rcwrefcache.cpp
@@ -209,23 +209,40 @@ HRESULT RCWRefCache::AddReferenceFromRCWToCCW(RCW *pRCW, ComCallWrapper *pCCW)
CONTRACTL_END;
// Try adding reference using dependent handles
- return AddReferenceUsingDependentHandle(pRCW, pCCW);
+ return AddReferenceUsingDependentHandle(pRCW->GetExposedObject(), pCCW->GetObjectRef());
}
//
-// Add RCW -> CCW reference using dependent handle
+// Add a reference from obj1 to obj2
+//
+HRESULT RCWRefCache::AddReferenceFromObjectToObject(OBJECTREF obj1, OBJECTREF obj2)
+{
+ CONTRACTL
+ {
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_COOPERATIVE;
+ PRECONDITION(obj1 != NULL);
+ PRECONDITION(obj2 != NULL);
+ }
+ CONTRACTL_END;
+
+ // Try adding reference using dependent handles
+ return AddReferenceUsingDependentHandle(obj1, obj2);
+}
+
+//
+// Add obj1 -> obj2 reference using dependent handle
// May fail if OOM
//
-HRESULT RCWRefCache::AddReferenceUsingDependentHandle(RCW *pRCW, ComCallWrapper *pCCW)
+HRESULT RCWRefCache::AddReferenceUsingDependentHandle(OBJECTREF obj1, OBJECTREF obj2)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
MODE_COOPERATIVE;
- PRECONDITION(CheckPointer(pRCW));
- PRECONDITION(CheckPointer(pCCW));
- PRECONDITION(CheckPointer(OBJECTREFToObject(pCCW->GetObjectRef())));
+ PRECONDITION(CheckPointer(OBJECTREFToObject(obj2)));
}
CONTRACTL_END;
@@ -242,7 +259,7 @@ HRESULT RCWRefCache::AddReferenceUsingDependentHandle(RCW *pRCW, ComCallWrapper
// No, we need to create a new handle
EX_TRY
{
- OBJECTHANDLE depHnd = m_pAppDomain->CreateDependentHandle(pRCW->GetExposedObject(), pCCW->GetObjectRef());
+ OBJECTHANDLE depHnd = m_pAppDomain->CreateDependentHandle(obj1, obj2);
m_depHndList.Push(depHnd);
STRESS_LOG2(
@@ -267,8 +284,8 @@ HRESULT RCWRefCache::AddReferenceUsingDependentHandle(RCW *pRCW, ComCallWrapper
OBJECTHANDLE depHnd = (OBJECTHANDLE) m_depHndList[m_dwDepHndListFreeIndex];
IGCHandleManager *mgr = GCHandleUtilities::GetGCHandleManager();
- mgr->StoreObjectInHandle(depHnd, OBJECTREFToObject(pRCW->GetExposedObject()));
- mgr->SetDependentHandleSecondary(depHnd, OBJECTREFToObject(pCCW->GetObjectRef()));
+ mgr->StoreObjectInHandle(depHnd, OBJECTREFToObject(obj1));
+ mgr->SetDependentHandleSecondary(depHnd, OBJECTREFToObject(obj2));
STRESS_LOG3(
LF_INTEROP, LL_INFO1000,
diff --git a/src/coreclr/src/vm/rcwrefcache.h b/src/coreclr/src/vm/rcwrefcache.h
index 4566b08429b36..4e9aa20c23c19 100644
--- a/src/coreclr/src/vm/rcwrefcache.h
+++ b/src/coreclr/src/vm/rcwrefcache.h
@@ -31,6 +31,11 @@ public :
//
HRESULT AddReferenceFromRCWToCCW(RCW *pRCW, ComCallWrapper *pCCW);
+ //
+ // Add a reference from obj1 to obj2
+ //
+ HRESULT AddReferenceFromObjectToObject(OBJECTREF obj1, OBJECTREF obj2);
+
//
// Enumerate all Jupiter RCWs in the RCW cache and do the callback
// I'm using template here so there is no perf penality
@@ -84,10 +89,10 @@ public :
private :
//
- // Add RCW -> CCW reference using dependent handle
+ // Add obj1 -> obj2 reference using dependent handle
// May fail if OOM
//
- HRESULT AddReferenceUsingDependentHandle(RCW *pRCW, ComCallWrapper *pCCW);
+ HRESULT AddReferenceUsingDependentHandle(OBJECTREF obj1, OBJECTREF obj2);
private :
AppDomain *m_pAppDomain; // Domain
diff --git a/src/coreclr/src/vm/rcwwalker.h b/src/coreclr/src/vm/rcwwalker.h
index 0a532d8830ef8..f6257259b7057 100644
--- a/src/coreclr/src/vm/rcwwalker.h
+++ b/src/coreclr/src/vm/rcwwalker.h
@@ -33,12 +33,13 @@ class RCWWalker
{
friend struct _DacGlobals;
-private :
+private:
static VolatilePtr s_pGCManager; // The one and only GCManager instance
static BOOL s_bGCStarted; // Has GC started?
+
+public:
SVAL_DECL(BOOL, s_bIsGlobalPeggingOn); // Do we need to peg every CCW?
-public :
#ifndef DACCESS_COMPILE
static void OnJupiterRCWCreated(RCW *pRCW, IJupiterObject *pJupiterObject);
static void AfterJupiterRCWCreated(RCW *pRCW);
diff --git a/src/coreclr/src/vm/runtimehandles.cpp b/src/coreclr/src/vm/runtimehandles.cpp
index e740579e771d3..2b4cad229bb44 100644
--- a/src/coreclr/src/vm/runtimehandles.cpp
+++ b/src/coreclr/src/vm/runtimehandles.cpp
@@ -1721,6 +1721,28 @@ FCIMPL1(IMDInternalImport*, RuntimeTypeHandle::GetMetadataImport, ReflectClassBa
}
FCIMPLEND
+PVOID QCALLTYPE RuntimeTypeHandle::AllocateTypeAssociatedMemory(QCall::TypeHandle type, UINT32 size)
+{
+ QCALL_CONTRACT;
+
+ void *allocatedMemory = nullptr;
+
+ BEGIN_QCALL;
+
+ TypeHandle typeHandle = type.AsTypeHandle();
+ _ASSERTE(!typeHandle.IsNull());
+
+ // Get the loader allocator for the associated type.
+ // Allocating using the type's associated loader allocator means
+ // that the memory will be freed when the type is unloaded.
+ PTR_LoaderAllocator loaderAllocator = typeHandle.GetMethodTable()->GetLoaderAllocator();
+ LoaderHeap* loaderHeap = loaderAllocator->GetHighFrequencyHeap();
+ allocatedMemory = loaderHeap->AllocMem(S_SIZE_T(size));
+
+ END_QCALL;
+
+ return allocatedMemory;
+}
//***********************************************************************************
//***********************************************************************************
diff --git a/src/coreclr/src/vm/runtimehandles.h b/src/coreclr/src/vm/runtimehandles.h
index 36bc4ada54553..7787e01675305 100644
--- a/src/coreclr/src/vm/runtimehandles.h
+++ b/src/coreclr/src/vm/runtimehandles.h
@@ -261,6 +261,9 @@ class RuntimeTypeHandle {
static
FCDECL1(IMDInternalImport*, GetMetadataImport, ReflectClassBaseObject * pModuleUNSAFE);
+
+ static
+ PVOID QCALLTYPE AllocateTypeAssociatedMemory(QCall::TypeHandle type, UINT32 size);
};
class RuntimeMethodHandle {
diff --git a/src/coreclr/src/vm/syncblk.h b/src/coreclr/src/vm/syncblk.h
index f30b056d88762..e0364df193b1b 100644
--- a/src/coreclr/src/vm/syncblk.h
+++ b/src/coreclr/src/vm/syncblk.h
@@ -789,6 +789,50 @@ class InteropSyncBlockInfo
// instead.
TADDR m_pRCW;
#endif
+
+public:
+ bool TryGetManagedObjectComWrapper(_Out_ void** mocw)
+ {
+ LIMITED_METHOD_DAC_CONTRACT;
+ *mocw = m_managedObjectComWrapper;
+ return (*mocw != NULL);
+ }
+
+#ifndef DACCESS_COMPILE
+ bool TrySetManagedObjectComWrapper(_In_ void* mocw, _In_ void* curr = NULL)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ return (FastInterlockCompareExchangePointer(
+ &m_managedObjectComWrapper,
+ mocw,
+ curr) == curr);
+ }
+#endif // !DACCESS_COMPILE
+
+ bool TryGetExternalComObjectContext(_Out_ void** eoc)
+ {
+ LIMITED_METHOD_DAC_CONTRACT;
+ *eoc = m_externalComObjectContext;
+ return (*eoc != NULL);
+ }
+
+#ifndef DACCESS_COMPILE
+ bool TrySetExternalComObjectContext(_In_ void* eoc, _In_ void* curr = NULL)
+ {
+ LIMITED_METHOD_CONTRACT;
+
+ return (FastInterlockCompareExchangePointer(
+ &m_externalComObjectContext,
+ eoc,
+ curr) == curr);
+ }
+#endif // !DACCESS_COMPILE
+
+private:
+ // See InteropLib API for usage.
+ void* m_managedObjectComWrapper;
+ void* m_externalComObjectContext;
#endif // FEATURE_COMINTEROP
};
diff --git a/src/coreclr/tests/src/Interop/CMakeLists.txt b/src/coreclr/tests/src/Interop/CMakeLists.txt
index 3e534c8f8c22f..d8f23d3b8c0f0 100644
--- a/src/coreclr/tests/src/Interop/CMakeLists.txt
+++ b/src/coreclr/tests/src/Interop/CMakeLists.txt
@@ -81,6 +81,7 @@ if(CLR_CMAKE_TARGET_WIN32)
add_subdirectory(COM/NativeClients/Licensing)
add_subdirectory(COM/NativeClients/DefaultInterfaces)
add_subdirectory(COM/NativeClients/Dispatch)
+ add_subdirectory(COM/ComWrappers/MockReferenceTrackerRuntime)
add_subdirectory(WinRT/NativeComponent)
# IJW isn't supported on ARM64
diff --git a/src/coreclr/tests/src/Interop/COM/ComWrappers/ComWrappersTests.csproj b/src/coreclr/tests/src/Interop/COM/ComWrappers/ComWrappersTests.csproj
new file mode 100644
index 0000000000000..e82960ae1e101
--- /dev/null
+++ b/src/coreclr/tests/src/Interop/COM/ComWrappers/ComWrappersTests.csproj
@@ -0,0 +1,16 @@
+
+
+ Exe
+
+ true
+ true
+ true
+
+
+
+
+
+
+
+
+
diff --git a/src/coreclr/tests/src/Interop/COM/ComWrappers/MockReferenceTrackerRuntime/CMakeLists.txt b/src/coreclr/tests/src/Interop/COM/ComWrappers/MockReferenceTrackerRuntime/CMakeLists.txt
new file mode 100644
index 0000000000000..fb232b99da3e1
--- /dev/null
+++ b/src/coreclr/tests/src/Interop/COM/ComWrappers/MockReferenceTrackerRuntime/CMakeLists.txt
@@ -0,0 +1,10 @@
+project (MockReferenceTrackerRuntime)
+include_directories( ${INC_PLATFORM_DIR} )
+set(SOURCES ReferenceTrackerRuntime.cpp)
+
+# add the shared library
+add_library (MockReferenceTrackerRuntime SHARED ${SOURCES})
+target_link_libraries(MockReferenceTrackerRuntime ${LINK_LIBRARIES_ADDITIONAL})
+
+# add the install targets
+install (TARGETS MockReferenceTrackerRuntime DESTINATION bin)
diff --git a/src/coreclr/tests/src/Interop/COM/ComWrappers/MockReferenceTrackerRuntime/ReferenceTrackerRuntime.cpp b/src/coreclr/tests/src/Interop/COM/ComWrappers/MockReferenceTrackerRuntime/ReferenceTrackerRuntime.cpp
new file mode 100644
index 0000000000000..f0a0f30b277e7
--- /dev/null
+++ b/src/coreclr/tests/src/Interop/COM/ComWrappers/MockReferenceTrackerRuntime/ReferenceTrackerRuntime.cpp
@@ -0,0 +1,339 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#include
+#include
+#include
+#include
+
+namespace API
+{
+ // Documentation found at https://docs.microsoft.com/windows/win32/api/windows.ui.xaml.hosting.referencetracker/
+ class DECLSPEC_UUID("64bd43f8-bfee-4ec4-b7eb-2935158dae21") IReferenceTrackerTarget : public IUnknown
+ {
+ public:
+ STDMETHOD_(ULONG, AddRefFromReferenceTracker)() = 0;
+ STDMETHOD_(ULONG, ReleaseFromReferenceTracker)() = 0;
+ STDMETHOD(Peg)() = 0;
+ STDMETHOD(Unpeg)() = 0;
+ };
+
+ class DECLSPEC_UUID("29a71c6a-3c42-4416-a39d-e2825a07a773") IReferenceTrackerHost : public IUnknown
+ {
+ public:
+ STDMETHOD(DisconnectUnusedReferenceSources)(_In_ DWORD dwFlags) = 0;
+ STDMETHOD(ReleaseDisconnectedReferenceSources)() = 0;
+ STDMETHOD(NotifyEndOfReferenceTrackingOnThread)() = 0;
+ STDMETHOD(GetTrackerTarget)(_In_ IUnknown* obj, _Outptr_ IReferenceTrackerTarget** ppNewReference) = 0;
+ STDMETHOD(AddMemoryPressure)(_In_ UINT64 bytesAllocated) = 0;
+ STDMETHOD(RemoveMemoryPressure)(_In_ UINT64 bytesAllocated) = 0;
+ };
+
+ class DECLSPEC_UUID("3cf184b4-7ccb-4dda-8455-7e6ce99a3298") IReferenceTrackerManager : public IUnknown
+ {
+ public:
+ STDMETHOD(ReferenceTrackingStarted)() = 0;
+ STDMETHOD(FindTrackerTargetsCompleted)(_In_ BOOL bWalkFailed) = 0;
+ STDMETHOD(ReferenceTrackingCompleted)() = 0;
+ STDMETHOD(SetReferenceTrackerHost)(_In_ IReferenceTrackerHost *pCLRServices) = 0;
+ };
+
+ class DECLSPEC_UUID("04b3486c-4687-4229-8d14-505ab584dd88") IFindReferenceTargetsCallback : public IUnknown
+ {
+ public:
+ STDMETHOD(FoundTrackerTarget)(_In_ IReferenceTrackerTarget* target) = 0;
+ };
+
+ class DECLSPEC_UUID("11d3b13a-180e-4789-a8be-7712882893e6") IReferenceTracker : public IUnknown
+ {
+ public:
+ STDMETHOD(ConnectFromTrackerSource)() = 0;
+ STDMETHOD(DisconnectFromTrackerSource)() = 0;
+ STDMETHOD(FindTrackerTargets)(_In_ IFindReferenceTargetsCallback *pCallback) = 0;
+ STDMETHOD(GetReferenceTrackerManager)(_Outptr_ IReferenceTrackerManager **ppTrackerManager) = 0;
+ STDMETHOD(AddRefFromTrackerSource)() = 0;
+ STDMETHOD(ReleaseFromTrackerSource)() = 0;
+ STDMETHOD(PegFromTrackerSource)() = 0;
+ };
+}
+
+namespace
+{
+ // Testing types
+ struct DECLSPEC_UUID("447BB9ED-DA48-4ABC-8963-5BB5C3E0AA09") ITest : public IUnknown
+ {
+ STDMETHOD(SetValue)(int i) = 0;
+ };
+
+ struct DECLSPEC_UUID("42951130-245C-485E-B60B-4ED4254256F8") ITrackerObject : public IUnknown
+ {
+ STDMETHOD(AddObjectRef)(_In_ IUnknown* c, _Out_ int* id) = 0;
+ STDMETHOD(DropObjectRef)(_In_ int id) = 0;
+ };
+
+ struct TrackerObject : public ITrackerObject, public API::IReferenceTracker, public UnknownImpl
+ {
+ const size_t _id;
+ std::atomic _trackerSourceCount;
+ bool _connected;
+ std::atomic _elementId;
+ std::unordered_map> _elements;
+
+ TrackerObject(size_t id) : _id{ id }, _trackerSourceCount{ 0 }, _connected{ false }, _elementId{ 1 }
+ { }
+
+ HRESULT ToggleTargets(_In_ bool shouldPeg)
+ {
+ HRESULT hr;
+
+ auto curr = std::begin(_elements);
+ while (curr != std::end(_elements))
+ {
+ ComSmartPtr mowMaybe;
+ if (S_OK == curr->second->QueryInterface(&mowMaybe))
+ {
+ if (shouldPeg)
+ {
+ RETURN_IF_FAILED(mowMaybe->Peg());
+ }
+ else
+ {
+ RETURN_IF_FAILED(mowMaybe->Unpeg());
+ }
+ }
+ ++curr;
+ }
+
+ return S_OK;
+ }
+
+ STDMETHOD(AddObjectRef)(_In_ IUnknown* c, _Out_ int* id)
+ {
+ assert(c != nullptr && id != nullptr);
+
+ try
+ {
+ *id = _elementId;
+ if (!_elements.insert(std::make_pair(*id, ComSmartPtr{ c })).second)
+ return S_FALSE;
+
+ _elementId++;
+ }
+ catch (const std::bad_alloc&)
+ {
+ return E_OUTOFMEMORY;
+ }
+
+ ComSmartPtr mowMaybe;
+ if (S_OK == c->QueryInterface(&mowMaybe))
+ (void)mowMaybe->AddRefFromReferenceTracker();
+
+ return S_OK;
+ }
+
+ STDMETHOD(DropObjectRef)(_In_ int id)
+ {
+ auto iter = _elements.find(id);
+ if (iter == std::end(_elements))
+ return S_FALSE;
+
+ ComSmartPtr mowMaybe;
+ if (S_OK == iter->second->QueryInterface(&mowMaybe))
+ {
+ (void)mowMaybe->ReleaseFromReferenceTracker();
+ }
+
+ _elements.erase(iter);
+
+ return S_OK;
+ }
+
+ STDMETHOD(ConnectFromTrackerSource)();
+ STDMETHOD(DisconnectFromTrackerSource)();
+ STDMETHOD(FindTrackerTargets)(_In_ API::IFindReferenceTargetsCallback* pCallback);
+ STDMETHOD(GetReferenceTrackerManager)(_Outptr_ API::IReferenceTrackerManager** ppTrackerManager);
+ STDMETHOD(AddRefFromTrackerSource)();
+ STDMETHOD(ReleaseFromTrackerSource)();
+ STDMETHOD(PegFromTrackerSource)();
+
+ STDMETHOD(QueryInterface)(
+ /* [in] */ REFIID riid,
+ /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject)
+ {
+ return DoQueryInterface(riid, ppvObject, static_cast(this), static_cast(this));
+ }
+
+ DEFINE_REF_COUNTING()
+ };
+
+ std::atomic CurrentObjectId{};
+
+ class TrackerRuntimeManagerImpl : public API::IReferenceTrackerManager
+ {
+ ComSmartPtr _runtimeServices;
+ std::list> _objects;
+
+ public:
+ void RecordObject(_In_ TrackerObject* obj)
+ {
+ _objects.push_back(ComSmartPtr{ obj });
+
+ if (_runtimeServices != nullptr)
+ _runtimeServices->AddMemoryPressure(sizeof(TrackerObject));
+ }
+
+ void ReleaseObjects()
+ {
+ size_t count = _objects.size();
+ _objects.clear();
+ if (_runtimeServices != nullptr)
+ _runtimeServices->RemoveMemoryPressure(sizeof(TrackerObject) * count);
+ }
+
+ HRESULT NotifyEndOfReferenceTrackingOnThread()
+ {
+ if (_runtimeServices != nullptr)
+ return _runtimeServices->NotifyEndOfReferenceTrackingOnThread();
+
+ return S_OK;
+ }
+
+ public: // IReferenceTrackerManager
+ STDMETHOD(ReferenceTrackingStarted)()
+ {
+ // Unpeg all instances
+ for (auto& i : _objects)
+ i->ToggleTargets(/* should peg */ false);
+
+ return S_OK;
+ }
+
+ STDMETHOD(FindTrackerTargetsCompleted)(_In_ BOOL bWalkFailed)
+ {
+ // Verify and ensure all connected types are pegged
+ for (auto& i : _objects)
+ i->ToggleTargets(/* should peg */ true);
+
+ return S_OK;
+ }
+
+ STDMETHOD(ReferenceTrackingCompleted)()
+ {
+ return S_OK;
+ }
+
+ STDMETHOD(SetReferenceTrackerHost)(_In_ API::IReferenceTrackerHost* pHostServices)
+ {
+ assert(pHostServices != nullptr);
+ return pHostServices->QueryInterface(&_runtimeServices);
+ }
+
+ // Lifetime maintained by stack - we don't care about ref counts
+ STDMETHOD_(ULONG, AddRef)() { return 1; }
+ STDMETHOD_(ULONG, Release)() { return 1; }
+
+ STDMETHOD(QueryInterface)(
+ /* [in] */ REFIID riid,
+ /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject)
+ {
+ if (ppvObject == nullptr)
+ return E_POINTER;
+
+ if (IsEqualIID(riid, __uuidof(API::IReferenceTrackerManager)))
+ {
+ *ppvObject = static_cast(this);
+ }
+ else if (IsEqualIID(riid, IID_IUnknown))
+ {
+ *ppvObject = static_cast(this);
+ }
+ else
+ {
+ *ppvObject = nullptr;
+ return E_NOINTERFACE;
+ }
+
+ AddRef();
+ return S_OK;
+ }
+ };
+
+ TrackerRuntimeManagerImpl TrackerRuntimeManager;
+
+ HRESULT STDMETHODCALLTYPE TrackerObject::ConnectFromTrackerSource()
+ {
+ _connected = true;
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE TrackerObject::DisconnectFromTrackerSource()
+ {
+ _connected = false;
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE TrackerObject::FindTrackerTargets(_In_ API::IFindReferenceTargetsCallback* pCallback)
+ {
+ assert(pCallback != nullptr);
+
+ ComSmartPtr mowMaybe;
+ for (auto& e : _elements)
+ {
+ if (S_OK == e.second->QueryInterface(&mowMaybe))
+ {
+ (void)pCallback->FoundTrackerTarget(mowMaybe.p);
+ mowMaybe.Release();
+ }
+ }
+
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE TrackerObject::GetReferenceTrackerManager(_Outptr_ API::IReferenceTrackerManager** ppTrackerManager)
+ {
+ assert(ppTrackerManager != nullptr);
+ return TrackerRuntimeManager.QueryInterface(__uuidof(API::IReferenceTrackerManager), (void**)ppTrackerManager);
+ }
+
+ HRESULT STDMETHODCALLTYPE TrackerObject::AddRefFromTrackerSource()
+ {
+ assert(0 <= _trackerSourceCount);
+ ++_trackerSourceCount;
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE TrackerObject::ReleaseFromTrackerSource()
+ {
+ assert(0 < _trackerSourceCount);
+ --_trackerSourceCount;
+ return S_OK;
+ }
+
+ HRESULT STDMETHODCALLTYPE TrackerObject::PegFromTrackerSource()
+ {
+ /* Not used by runtime */
+ return E_NOTIMPL;
+ }
+}
+
+// Create external object
+extern "C" DLL_EXPORT ITrackerObject* STDMETHODCALLTYPE CreateTrackerObject()
+{
+ auto obj = new TrackerObject{ CurrentObjectId++ };
+
+ TrackerRuntimeManager.RecordObject(obj);
+
+ return obj;
+}
+
+// Release the reference on all internally held tracker objects
+extern "C" DLL_EXPORT void STDMETHODCALLTYPE ReleaseAllTrackerObjects()
+{
+ TrackerRuntimeManager.ReleaseObjects();
+}
+
+extern "C" DLL_EXPORT int STDMETHODCALLTYPE Trigger_NotifyEndOfReferenceTrackingOnThread()
+{
+ return TrackerRuntimeManager.NotifyEndOfReferenceTrackingOnThread();
+}
diff --git a/src/coreclr/tests/src/Interop/COM/ComWrappers/Program.cs b/src/coreclr/tests/src/Interop/COM/ComWrappers/Program.cs
new file mode 100644
index 0000000000000..e85f87a4b8269
--- /dev/null
+++ b/src/coreclr/tests/src/Interop/COM/ComWrappers/Program.cs
@@ -0,0 +1,517 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace ComWrappersTests
+{
+ using System;
+ using System.Collections;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Runtime.CompilerServices;
+ using System.Runtime.InteropServices;
+
+ using TestLibrary;
+
+ class Program
+ {
+ //
+ // Managed object with native wrapper definition.
+ //
+ [Guid("447BB9ED-DA48-4ABC-8963-5BB5C3E0AA09")]
+ interface ITest
+ {
+ void SetValue(int i);
+ }
+
+ class Test : ITest
+ {
+ public static int InstanceCount = 0;
+
+ private int value = -1;
+ public Test() { InstanceCount++; }
+ ~Test() { InstanceCount--; }
+
+ public void SetValue(int i) => this.value = i;
+ public int GetValue() => this.value;
+ }
+
+ public struct IUnknownVtbl
+ {
+ public IntPtr QueryInterface;
+ public IntPtr AddRef;
+ public IntPtr Release;
+ }
+
+ public struct ITestVtbl
+ {
+ public IUnknownVtbl IUnknownImpl;
+ public IntPtr SetValue;
+
+ public delegate int _SetValue(IntPtr thisPtr, int i);
+ public static _SetValue pSetValue = new _SetValue(SetValueInternal);
+
+ public static int SetValueInternal(IntPtr dispatchPtr, int i)
+ {
+ unsafe
+ {
+ try
+ {
+ ComWrappers.ComInterfaceDispatch.GetInstance((ComWrappers.ComInterfaceDispatch*)dispatchPtr).SetValue(i);
+ }
+ catch (Exception e)
+ {
+ return e.HResult;
+ }
+ }
+ return 0; // S_OK;
+ }
+ }
+
+ //
+ // Native interface defintion with managed wrapper for tracker object
+ //
+ struct MockReferenceTrackerRuntime
+ {
+ [DllImport(nameof(MockReferenceTrackerRuntime))]
+ extern public static IntPtr CreateTrackerObject();
+
+ [DllImport(nameof(MockReferenceTrackerRuntime))]
+ extern public static void ReleaseAllTrackerObjects();
+
+ [DllImport(nameof(MockReferenceTrackerRuntime))]
+ extern public static int Trigger_NotifyEndOfReferenceTrackingOnThread();
+ }
+
+ [Guid("42951130-245C-485E-B60B-4ED4254256F8")]
+ public interface ITrackerObject
+ {
+ int AddObjectRef(IntPtr obj);
+ void DropObjectRef(int id);
+ };
+
+ public struct VtblPtr
+ {
+ public IntPtr Vtbl;
+ }
+
+ public class ITrackerObjectWrapper : ITrackerObject
+ {
+ private struct ITrackerObjectWrapperVtbl
+ {
+ public IntPtr QueryInterface;
+ public _AddRef AddRef;
+ public _Release Release;
+ public _AddObjectRef AddObjectRef;
+ public _DropObjectRef DropObjectRef;
+ }
+
+ private delegate int _AddRef(IntPtr This);
+ private delegate int _Release(IntPtr This);
+ private delegate int _AddObjectRef(IntPtr This, IntPtr obj, out int id);
+ private delegate int _DropObjectRef(IntPtr This, int id);
+
+ private readonly IntPtr instance;
+ private readonly ITrackerObjectWrapperVtbl vtable;
+
+ public ITrackerObjectWrapper(IntPtr instance)
+ {
+ var inst = Marshal.PtrToStructure(instance);
+ this.vtable = Marshal.PtrToStructure(inst.Vtbl);
+ this.instance = instance;
+ }
+
+ ~ITrackerObjectWrapper()
+ {
+ if (this.instance != IntPtr.Zero)
+ {
+ this.vtable.Release(this.instance);
+ }
+ }
+
+ public int AddObjectRef(IntPtr obj)
+ {
+ int id;
+ int hr = this.vtable.AddObjectRef(this.instance, obj, out id);
+ if (hr != 0)
+ {
+ throw new COMException($"{nameof(AddObjectRef)}", hr);
+ }
+
+ return id;
+ }
+
+ public void DropObjectRef(int id)
+ {
+ int hr = this.vtable.DropObjectRef(this.instance, id);
+ if (hr != 0)
+ {
+ throw new COMException($"{nameof(DropObjectRef)}", hr);
+ }
+ }
+ }
+
+ class TestComWrappers : ComWrappers
+ {
+ public static readonly TestComWrappers Global = new TestComWrappers();
+
+ protected unsafe override ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count)
+ {
+ Assert.IsTrue(obj is Test);
+
+ IntPtr fpQueryInteface = default;
+ IntPtr fpAddRef = default;
+ IntPtr fpRelease = default;
+ ComWrappers.GetIUnknownImpl(out fpQueryInteface, out fpAddRef, out fpRelease);
+
+ var vtbl = new ITestVtbl()
+ {
+ IUnknownImpl = new IUnknownVtbl()
+ {
+ QueryInterface = fpQueryInteface,
+ AddRef = fpAddRef,
+ Release = fpRelease
+ },
+ SetValue = Marshal.GetFunctionPointerForDelegate(ITestVtbl.pSetValue)
+ };
+ var vtblRaw = RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(ITestVtbl), sizeof(ITestVtbl));
+ Marshal.StructureToPtr(vtbl, vtblRaw, false);
+
+ var entryRaw = (ComInterfaceEntry*)RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(ITestVtbl), sizeof(ComInterfaceEntry));
+ entryRaw->IID = typeof(ITest).GUID;
+ entryRaw->Vtable = vtblRaw;
+
+ count = 1;
+ return entryRaw;
+ }
+
+ protected override object? CreateObject(IntPtr externalComObject, CreateObjectFlags flag)
+ {
+ var iid = typeof(ITrackerObject).GUID;
+ IntPtr iTestComObject;
+ int hr = Marshal.QueryInterface(externalComObject, ref iid, out iTestComObject);
+ Assert.AreEqual(hr, 0);
+
+ return new ITrackerObjectWrapper(iTestComObject);
+ }
+
+ public const int ReleaseObjectsCallAck = unchecked((int)-1);
+
+ protected override void ReleaseObjects(IEnumerable objects)
+ {
+ throw new Exception() { HResult = ReleaseObjectsCallAck };
+ }
+
+ public static void ValidateIUnknownImpls()
+ {
+ Console.WriteLine($"Running {nameof(ValidateIUnknownImpls)}...");
+
+ ComWrappers.GetIUnknownImpl(out IntPtr fpQueryInteface, out IntPtr fpAddRef, out IntPtr fpRelease);
+
+ Assert.AreNotEqual(fpQueryInteface, IntPtr.Zero);
+ Assert.AreNotEqual(fpAddRef, IntPtr.Zero);
+ Assert.AreNotEqual(fpRelease, IntPtr.Zero);
+ }
+ }
+
+ static void ValidateComInterfaceCreation()
+ {
+ Console.WriteLine($"Running {nameof(ValidateComInterfaceCreation)}...");
+
+ var testObj = new Test();
+
+ var wrappers = new TestComWrappers();
+
+ // Allocate a wrapper for the object
+ IntPtr comWrapper = wrappers.GetOrCreateComInterfaceForObject(testObj, CreateComInterfaceFlags.TrackerSupport);
+ Assert.AreNotEqual(comWrapper, IntPtr.Zero);
+
+ // Get a wrapper for an object and verify it is the same one.
+ IntPtr comWrapperMaybe = wrappers.GetOrCreateComInterfaceForObject(testObj, CreateComInterfaceFlags.TrackerSupport);
+ Assert.AreEqual(comWrapper, comWrapperMaybe);
+
+ // Release the wrapper
+ int count = Marshal.Release(comWrapper);
+ Assert.AreEqual(count, 1);
+ count = Marshal.Release(comWrapperMaybe);
+ Assert.AreEqual(count, 0);
+
+ // Create a new wrapper
+ IntPtr comWrapperNew = wrappers.GetOrCreateComInterfaceForObject(testObj, CreateComInterfaceFlags.TrackerSupport);
+
+ // Once a wrapper is created for a managed object it is always present
+ Assert.AreEqual(comWrapperNew, comWrapper);
+
+ // Release the new wrapper
+ count = Marshal.Release(comWrapperNew);
+ Assert.AreEqual(count, 0);
+ }
+
+ static void ValidateCreateObjectCachingScenario()
+ {
+ Console.WriteLine($"Running {nameof(ValidateCreateObjectCachingScenario)}...");
+
+ var cw = new TestComWrappers();
+
+ // Get an object from a tracker runtime.
+ IntPtr trackerObjRaw = MockReferenceTrackerRuntime.CreateTrackerObject();
+
+ var trackerObj1 = (ITrackerObjectWrapper)cw.GetOrCreateObjectForComInstance(trackerObjRaw, CreateObjectFlags.TrackerObject);
+ var trackerObj2 = (ITrackerObjectWrapper)cw.GetOrCreateObjectForComInstance(trackerObjRaw, CreateObjectFlags.TrackerObject);
+ Assert.AreEqual(trackerObj1, trackerObj2);
+
+ // Ownership has been transferred to the wrapper.
+ Marshal.Release(trackerObjRaw);
+
+ var trackerObj3 = (ITrackerObjectWrapper)cw.GetOrCreateObjectForComInstance(trackerObjRaw, CreateObjectFlags.TrackerObject | CreateObjectFlags.UniqueInstance);
+ Assert.AreNotEqual(trackerObj1, trackerObj3);
+ }
+
+ static void ValidatePrecreatedExternalWrapper()
+ {
+ Console.WriteLine($"Running {nameof(ValidatePrecreatedExternalWrapper)}...");
+
+ var cw = new TestComWrappers();
+
+ // Get an object from a tracker runtime.
+ IntPtr trackerObjRaw = MockReferenceTrackerRuntime.CreateTrackerObject();
+
+ // Manually create a wrapper
+ var iid = typeof(ITrackerObject).GUID;
+ IntPtr iTestComObject;
+ int hr = Marshal.QueryInterface(trackerObjRaw, ref iid, out iTestComObject);
+ Assert.AreEqual(hr, 0);
+ var nativeWrapper = new ITrackerObjectWrapper(iTestComObject);
+
+ // Register wrapper, but supply the wrapper.
+ var nativeWrapper2 = (ITrackerObjectWrapper)cw.GetOrRegisterObjectForComInstance(trackerObjRaw, CreateObjectFlags.TrackerObject, nativeWrapper);
+ Assert.AreEqual(nativeWrapper, nativeWrapper2);
+
+ // Ownership has been transferred to the wrapper.
+ Marshal.Release(trackerObjRaw);
+
+ // Validate reuse of a wrapper fails.
+ IntPtr trackerObjRaw2 = MockReferenceTrackerRuntime.CreateTrackerObject();
+ Assert.Throws(
+ () =>
+ {
+ cw.GetOrRegisterObjectForComInstance(trackerObjRaw2, CreateObjectFlags.None, nativeWrapper2);
+ });
+ Marshal.Release(trackerObjRaw2);
+
+ // Validate passing null wrapper fails.
+ Assert.Throws(
+ () =>
+ {
+ cw.GetOrRegisterObjectForComInstance(trackerObjRaw, CreateObjectFlags.None, null);
+ });
+ }
+
+ static void ValidateIUnknownImpls()
+ => TestComWrappers.ValidateIUnknownImpls();
+
+ class BadComWrappers : ComWrappers
+ {
+ public enum FailureMode
+ {
+ ReturnInvalid,
+ ThrowException,
+ }
+
+ public const int ExceptionErrorCode = 0x27;
+
+ public FailureMode ComputeVtablesMode { get; set; }
+ public FailureMode CreateObjectMode { get; set; }
+
+ protected unsafe override ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count)
+ {
+ switch (ComputeVtablesMode)
+ {
+ case FailureMode.ReturnInvalid:
+ {
+ count = -1;
+ return null;
+ }
+ case FailureMode.ThrowException:
+ throw new Exception() { HResult = ExceptionErrorCode };
+ default:
+ Assert.Fail("Invalid failure mode");
+ throw new Exception("UNREACHABLE");
+ }
+ }
+
+ protected override object? CreateObject(IntPtr externalComObject, CreateObjectFlags flags)
+ {
+ switch (CreateObjectMode)
+ {
+ case FailureMode.ReturnInvalid:
+ return null;
+ case FailureMode.ThrowException:
+ throw new Exception() { HResult = ExceptionErrorCode };
+ default:
+ Assert.Fail("Invalid failure mode");
+ throw new Exception("UNREACHABLE");
+ }
+ }
+
+ protected override void ReleaseObjects(IEnumerable objects)
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ static void ValidateBadComWrapperImpl()
+ {
+ Console.WriteLine($"Running {nameof(ValidateBadComWrapperImpl)}...");
+
+ var wrapper = new BadComWrappers();
+
+ Assert.Throws(
+ () =>
+ {
+ wrapper.ComputeVtablesMode = BadComWrappers.FailureMode.ReturnInvalid;
+ wrapper.GetOrCreateComInterfaceForObject(new Test(), CreateComInterfaceFlags.None);
+ });
+
+ try
+ {
+ wrapper.ComputeVtablesMode = BadComWrappers.FailureMode.ThrowException;
+ wrapper.GetOrCreateComInterfaceForObject(new Test(), CreateComInterfaceFlags.None);
+ }
+ catch (Exception e)
+ {
+ Assert.AreEqual(BadComWrappers.ExceptionErrorCode, e.HResult);
+ }
+
+ IntPtr trackerObjRaw = MockReferenceTrackerRuntime.CreateTrackerObject();
+
+ Assert.Throws(
+ () =>
+ {
+ wrapper.CreateObjectMode = BadComWrappers.FailureMode.ReturnInvalid;
+ wrapper.GetOrCreateObjectForComInstance(trackerObjRaw, CreateObjectFlags.None);
+ });
+
+ try
+ {
+ wrapper.CreateObjectMode = BadComWrappers.FailureMode.ThrowException;
+ wrapper.GetOrCreateObjectForComInstance(trackerObjRaw, CreateObjectFlags.None);
+ }
+ catch (Exception e)
+ {
+ Assert.AreEqual(BadComWrappers.ExceptionErrorCode, e.HResult);
+ }
+
+ Marshal.Release(trackerObjRaw);
+ }
+
+ static void ValidateRuntimeTrackerScenario()
+ {
+ Console.WriteLine($"Running {nameof(ValidateRuntimeTrackerScenario)}...");
+
+ var cw = new TestComWrappers();
+
+ // Get an object from a tracker runtime.
+ IntPtr trackerObjRaw = MockReferenceTrackerRuntime.CreateTrackerObject();
+
+ // Create a managed wrapper for the native object.
+ var trackerObj = (ITrackerObjectWrapper)cw.GetOrCreateObjectForComInstance(trackerObjRaw, CreateObjectFlags.TrackerObject);
+
+ // Ownership has been transferred to the wrapper.
+ Marshal.Release(trackerObjRaw);
+
+ var testWrapperIds = new List();
+ for (int i = 0; i < 1000; ++i)
+ {
+ // Create a native wrapper for the managed object.
+ IntPtr testWrapper = cw.GetOrCreateComInterfaceForObject(new Test(), CreateComInterfaceFlags.TrackerSupport);
+
+ // Pass the managed object to the native object.
+ int id = trackerObj.AddObjectRef(testWrapper);
+
+ // Retain the managed object wrapper ptr.
+ testWrapperIds.Add(id);
+ }
+
+ Assert.IsTrue(testWrapperIds.Count <= Test.InstanceCount);
+
+ GC.Collect();
+ GC.Collect();
+ GC.Collect();
+ GC.Collect();
+ GC.Collect();
+
+ Assert.IsTrue(testWrapperIds.Count <= Test.InstanceCount);
+
+ // Remove the managed object ref from the native object.
+ foreach (int id in testWrapperIds)
+ {
+ trackerObj.DropObjectRef(id);
+ }
+
+ testWrapperIds.Clear();
+
+ GC.Collect();
+ GC.Collect();
+ GC.Collect();
+ GC.Collect();
+ GC.Collect();
+ }
+
+ static void ValidateGlobalInstanceScenarios()
+ {
+ Console.WriteLine($"Running {nameof(ValidateGlobalInstanceScenarios)}...");
+ Console.WriteLine($"Validate RegisterAsGlobalInstance()...");
+
+ var wrappers1 = TestComWrappers.Global;
+ wrappers1.RegisterAsGlobalInstance();
+
+ Assert.Throws(
+ () =>
+ {
+ wrappers1.RegisterAsGlobalInstance();
+ }, "Should not be able to re-register for global ComWrappers");
+
+ var wrappers2 = new TestComWrappers();
+ Assert.Throws(
+ () =>
+ {
+ wrappers2.RegisterAsGlobalInstance();
+ }, "Should not be able to reset for global ComWrappers");
+
+ Console.WriteLine($"Validate NotifyEndOfReferenceTrackingOnThread()...");
+
+ int hr;
+ var cw = TestComWrappers.Global;
+
+ // Trigger the thread lifetime end API and verify the callback occurs.
+ hr = MockReferenceTrackerRuntime.Trigger_NotifyEndOfReferenceTrackingOnThread();
+ Assert.AreEqual(TestComWrappers.ReleaseObjectsCallAck, hr);
+ }
+
+ static int Main(string[] doNotUse)
+ {
+ try
+ {
+ ValidateComInterfaceCreation();
+ ValidateCreateObjectCachingScenario();
+ ValidatePrecreatedExternalWrapper();
+ ValidateIUnknownImpls();
+ ValidateBadComWrapperImpl();
+ ValidateRuntimeTrackerScenario();
+
+ // Perform all global impacting test scenarios last to
+ // avoid polluting non-global tests.
+ ValidateGlobalInstanceScenarios();
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine($"Test Failure: {e}");
+ return 101;
+ }
+
+ return 100;
+ }
+ }
+}
+
diff --git a/src/coreclr/tests/src/Interop/COM/NativeServer/Servers.h b/src/coreclr/tests/src/Interop/COM/NativeServer/Servers.h
index 2ff3f88ce0b1c..3e0fccc16e422 100644
--- a/src/coreclr/tests/src/Interop/COM/NativeServer/Servers.h
+++ b/src/coreclr/tests/src/Interop/COM/NativeServer/Servers.h
@@ -68,61 +68,9 @@ struct CoreShimComActivation
}
};
-template
-struct ComSmartPtr
-{
- ComSmartPtr()
- : p{}
- { }
-
- ComSmartPtr(_In_ const ComSmartPtr &) = delete;
- ComSmartPtr(_Inout_ ComSmartPtr &&) = delete;
-
- ComSmartPtr& operator=(_In_ const ComSmartPtr &) = delete;
- ComSmartPtr& operator=(_Inout_ ComSmartPtr &&) = delete;
-
- ~ComSmartPtr()
- {
- if (p != nullptr)
- p->Release();
- }
-
- operator T*()
- {
- return p;
- }
-
- T** operator&()
- {
- return &p;
- }
-
- T* operator->()
- {
- return p;
- }
-
- void Attach(_In_opt_ T *t)
- {
- if (p != nullptr)
- p->Release();
-
- p = t;
- }
-
- T *Detach()
- {
- T *tmp = p;
- p = nullptr;
- return tmp;
- }
-
- T *p;
-};
+#include
#ifndef COM_CLIENT
- #include
-
#define DEF_FUNC(n) virtual COM_DECLSPEC_NOTHROW HRESULT STDMETHODCALLTYPE n
#include "NumericTesting.h"
diff --git a/src/coreclr/tests/src/Interop/common/ComHelpers.h b/src/coreclr/tests/src/Interop/common/ComHelpers.h
index f6a35d30c4a3e..c90ff7a773a2e 100644
--- a/src/coreclr/tests/src/Interop/common/ComHelpers.h
+++ b/src/coreclr/tests/src/Interop/common/ComHelpers.h
@@ -331,3 +331,76 @@ class ClassFactoryLicense : public UnknownImpl, public IClassFactory2
DEFINE_REF_COUNTING();
};
+
+template
+struct ComSmartPtr
+{
+ T* p;
+
+ ComSmartPtr()
+ : p{ nullptr }
+ { }
+
+ ComSmartPtr(_In_ T* t)
+ : p{ t }
+ {
+ if (p != nullptr)
+ (void)p->AddRef();
+ }
+
+ ComSmartPtr(_In_ const ComSmartPtr&) = delete;
+
+ ComSmartPtr(_Inout_ ComSmartPtr&& other)
+ : p{ other.Detach() }
+ { }
+
+ ~ComSmartPtr()
+ {
+ Release();
+ }
+
+ ComSmartPtr& operator=(_In_ const ComSmartPtr&) = delete;
+
+ ComSmartPtr& operator=(_Inout_ ComSmartPtr&& other)
+ {
+ Attach(other.Detach());
+ return (*this);
+ }
+
+ operator T*()
+ {
+ return p;
+ }
+
+ T** operator&()
+ {
+ return &p;
+ }
+
+ T* operator->()
+ {
+ return p;
+ }
+
+ void Attach(_In_opt_ T* t) noexcept
+ {
+ Release();
+ p = t;
+ }
+
+ T* Detach() noexcept
+ {
+ T* tmp = p;
+ p = nullptr;
+ return tmp;
+ }
+
+ void Release() noexcept
+ {
+ if (p != nullptr)
+ {
+ (void)p->Release();
+ p = nullptr;
+ }
+ }
+};
diff --git a/src/coreclr/tests/src/baseservices/compilerservices/RuntimeHelpers/ExecuteCodeWithGuaranteedCleanup.cs b/src/coreclr/tests/src/baseservices/compilerservices/RuntimeHelpers/ExecuteCodeWithGuaranteedCleanup.cs
index 40b92b715f316..faf6504252dcc 100644
--- a/src/coreclr/tests/src/baseservices/compilerservices/RuntimeHelpers/ExecuteCodeWithGuaranteedCleanup.cs
+++ b/src/coreclr/tests/src/baseservices/compilerservices/RuntimeHelpers/ExecuteCodeWithGuaranteedCleanup.cs
@@ -5,57 +5,49 @@
using System;
using System.Runtime.CompilerServices;
-namespace GCD
+class GCD
{
- ///
- /// Summary description for Class1.
- ///
- class GCD
+ private int _val = -2;
+ private int _exitcode = -1;
+ public GCD() {}
+ public int GetExitCode(){ return _exitcode;}
+ public void g ()
{
- private int _val = -2;
- private int _exitcode = -1;
- public GCD() {}
- public int GetExitCode(){ return _exitcode;}
- public void g ()
- {
- throw new System.Exception("TryCode test");
- }
- public void TryCode0 (object obj)
- {
- _val = (int)obj;
- g();
- }
- public void CleanupCode0 (object obj, bool excpThrown)
+ throw new System.Exception("TryCode test");
+ }
+ public void TryCode0 (object obj)
+ {
+ _val = (int)obj;
+ g();
+ }
+ public void CleanupCode0 (object obj, bool excpThrown)
+ {
+ if(excpThrown && ((int)obj == _val))
{
- if(excpThrown && ((int)obj == _val))
- {
- _exitcode = 100;
- }
+ _exitcode = 100;
}
}
+}
-
- class GCDTest
+class ExecuteCodeWithGuaranteedCleanupTest
+{
+ public static void Run()
{
- ///
- /// The main entry point for the application.
- ///
- static int Main(string[] args)
- {
- GCD gcd = new GCD();
- RuntimeHelpers.TryCode t = new RuntimeHelpers.TryCode(gcd.TryCode0);
- RuntimeHelpers.CleanupCode c = new RuntimeHelpers.CleanupCode(gcd.CleanupCode0);
- int val = 21;
- try
- {
- RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(t, c, val);
- }
- catch (Exception Ex)
- {
+ GCD gcd = new GCD();
+ RuntimeHelpers.TryCode t = new RuntimeHelpers.TryCode(gcd.TryCode0);
+ RuntimeHelpers.CleanupCode c = new RuntimeHelpers.CleanupCode(gcd.CleanupCode0);
+ int val = 21;
+ try
+ {
+ RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(t, c, val);
+ }
+ catch (Exception Ex)
+ {
- }
+ }
- return gcd.GetExitCode();
- }
+ int res = gcd.GetExitCode();
+ if (res != 100)
+ throw new Exception($"{nameof(ExecuteCodeWithGuaranteedCleanupTest)} failed. Result: {res}");
}
}
diff --git a/src/coreclr/tests/src/baseservices/compilerservices/RuntimeHelpers/RuntimeHelpersTests.cs b/src/coreclr/tests/src/baseservices/compilerservices/RuntimeHelpers/RuntimeHelpersTests.cs
new file mode 100644
index 0000000000000..5b667a78be6d0
--- /dev/null
+++ b/src/coreclr/tests/src/baseservices/compilerservices/RuntimeHelpers/RuntimeHelpersTests.cs
@@ -0,0 +1,23 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+//
+using System;
+
+class RuntimeHelpersTests
+{
+ static int Main(string[] args)
+ {
+ try
+ {
+ ExecuteCodeWithGuaranteedCleanupTest.Run();
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ return 101;
+ }
+
+ return 100;
+ }
+}
diff --git a/src/coreclr/tests/src/baseservices/compilerservices/RuntimeHelpers/ExecuteCodeWithGuaranteedCleanup.csproj b/src/coreclr/tests/src/baseservices/compilerservices/RuntimeHelpers/RuntimeHelpersTests.csproj
similarity index 65%
rename from src/coreclr/tests/src/baseservices/compilerservices/RuntimeHelpers/ExecuteCodeWithGuaranteedCleanup.csproj
rename to src/coreclr/tests/src/baseservices/compilerservices/RuntimeHelpers/RuntimeHelpersTests.csproj
index 2e8010d058d9e..fa8ab39bb0ea0 100644
--- a/src/coreclr/tests/src/baseservices/compilerservices/RuntimeHelpers/ExecuteCodeWithGuaranteedCleanup.csproj
+++ b/src/coreclr/tests/src/baseservices/compilerservices/RuntimeHelpers/RuntimeHelpersTests.csproj
@@ -5,9 +5,9 @@
1
-
+
-
+
diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
index d3e1a963ee6be..0639dc7d40794 100644
--- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
+++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
@@ -3652,6 +3652,9 @@
Type '{0}' has more than one COM unregistration function.
+
+ Attempt to update previously set global instance.
+
The callback populated its buffer with ill-formed UTF-8 data. Callbacks are required to populate the buffer only with well-formed UTF-8 data.
diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
index 62e69dd3b3663..3ee3d97192bf0 100644
--- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
+++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
@@ -1488,6 +1488,9 @@
+
+
+
diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.PlatformNotSupported.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.PlatformNotSupported.cs
new file mode 100644
index 0000000000000..77e8b35baae46
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.PlatformNotSupported.cs
@@ -0,0 +1,76 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections;
+
+namespace System.Runtime.InteropServices
+{
+ [Flags]
+ public enum CreateComInterfaceFlags
+ {
+ None = 0,
+ CallerDefinedIUnknown = 1,
+ TrackerSupport = 2,
+ }
+
+ [Flags]
+ public enum CreateObjectFlags
+ {
+ None = 0,
+ TrackerObject = 1,
+ UniqueInstance = 2,
+ }
+
+ [CLSCompliant(false)]
+ public abstract class ComWrappers
+ {
+ public struct ComInterfaceEntry
+ {
+ public Guid IID;
+ public IntPtr Vtable;
+ }
+
+ public struct ComInterfaceDispatch
+ {
+ public IntPtr Vtable;
+
+ public static unsafe T GetInstance(ComInterfaceDispatch* dispatchPtr) where T : class
+ {
+ throw new PlatformNotSupportedException();
+ }
+ }
+
+ public IntPtr GetOrCreateComInterfaceForObject(object instance, CreateComInterfaceFlags flags)
+ {
+ throw new PlatformNotSupportedException();
+ }
+
+ protected unsafe abstract ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count);
+
+ public object GetOrCreateObjectForComInstance(IntPtr externalComObject, CreateObjectFlags flags)
+ {
+ throw new PlatformNotSupportedException();
+ }
+
+ protected abstract object? CreateObject(IntPtr externalComObject, CreateObjectFlags flags);
+
+ public object GetOrRegisterObjectForComInstance(IntPtr externalComObject, CreateObjectFlags flags, object wrapper)
+ {
+ throw new PlatformNotSupportedException();
+ }
+
+ protected abstract void ReleaseObjects(IEnumerable objects);
+
+ public void RegisterAsGlobalInstance()
+ {
+ throw new PlatformNotSupportedException();
+ }
+
+ protected static void GetIUnknownImpl(out IntPtr fpQueryInterface, out IntPtr fpAddRef, out IntPtr fpRelease)
+ {
+ throw new PlatformNotSupportedException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs
index aee1cf868493c..92ddefd113e4a 100644
--- a/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs
+++ b/src/libraries/System.Runtime.InteropServices/ref/System.Runtime.InteropServices.cs
@@ -983,6 +983,42 @@ public sealed partial class VariantWrapper
public VariantWrapper(object? obj) { }
public object? WrappedObject { get { throw null; } }
}
+ [System.FlagsAttribute]
+ public enum CreateComInterfaceFlags
+ {
+ None = 0,
+ CallerDefinedIUnknown = 1,
+ TrackerSupport = 2,
+ }
+ [System.FlagsAttribute]
+ public enum CreateObjectFlags
+ {
+ None = 0,
+ TrackerObject = 1,
+ UniqueInstance = 2,
+ }
+ [System.CLSCompliantAttribute(false)]
+ public abstract class ComWrappers
+ {
+ public struct ComInterfaceEntry
+ {
+ public System.Guid IID;
+ public System.IntPtr Vtable;
+ }
+ public struct ComInterfaceDispatch
+ {
+ public System.IntPtr Vtable;
+ public static unsafe T GetInstance(ComInterfaceDispatch* dispatchPtr) where T : class { throw null; }
+ }
+ public System.IntPtr GetOrCreateComInterfaceForObject(object instance, CreateComInterfaceFlags flags) { throw null; }
+ protected unsafe abstract ComInterfaceEntry* ComputeVtables(object obj, CreateComInterfaceFlags flags, out int count);
+ public object GetOrCreateObjectForComInstance(System.IntPtr externalComObject, CreateObjectFlags flags) { throw null; }
+ protected abstract object CreateObject(System.IntPtr externalComObject, CreateObjectFlags flags);
+ public object GetOrRegisterObjectForComInstance(System.IntPtr externalComObject, CreateObjectFlags flags, object wrapper) { throw null; }
+ protected abstract void ReleaseObjects(System.Collections.IEnumerable objects);
+ public void RegisterAsGlobalInstance() { }
+ protected static void GetIUnknownImpl(out System.IntPtr fpQueryInterface, out System.IntPtr fpAddRef, out System.IntPtr fpRelease) { throw null; }
+ }
}
namespace System.Runtime.InteropServices.ComTypes
{
diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs
index 467d544051fa5..0f0362a3d9e5e 100644
--- a/src/libraries/System.Runtime/ref/System.Runtime.cs
+++ b/src/libraries/System.Runtime/ref/System.Runtime.cs
@@ -9025,6 +9025,7 @@ public static partial class RuntimeFeature
public static partial class RuntimeHelpers
{
public static int OffsetToStringData { get { throw null; } }
+ public static IntPtr AllocateTypeAssociatedMemory(Type type, int size) { throw null; }
public static void EnsureSufficientExecutionStack() { }
public static new bool Equals(object? o1, object? o2) { throw null; }
public static void ExecuteCodeWithGuaranteedCleanup(System.Runtime.CompilerServices.RuntimeHelpers.TryCode code, System.Runtime.CompilerServices.RuntimeHelpers.CleanupCode backoutCode, object? userData) { }
diff --git a/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs b/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs
index bd4054da4df06..dea93fa64195b 100644
--- a/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs
+++ b/src/libraries/System.Runtime/tests/System/Runtime/CompilerServices/RuntimeHelpersTests.cs
@@ -264,6 +264,22 @@ public static void ArrayRangeHelperTest()
Assert.Throws(() => { int [] array = RuntimeHelpers.GetSubArray(a, range); });
}
+ [Fact]
+ [SkipOnMono("Not presently implemented on Mono")]
+ public static void AllocateTypeAssociatedMemoryInvalidArguments()
+ {
+ Assert.Throws(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(null, 10); });
+ Assert.Throws(() => { RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), -1); });
+ }
+
+ [Fact]
+ [SkipOnMono("Not presently implemented on Mono")]
+ public static void AllocateTypeAssociatedMemoryValidArguments()
+ {
+ IntPtr memory = RuntimeHelpers.AllocateTypeAssociatedMemory(typeof(RuntimeHelpersTests), 32);
+ Assert.NotEqual(memory, IntPtr.Zero);
+ }
+
[StructLayoutAttribute(LayoutKind.Sequential)]
private struct StructWithoutReferences
{
diff --git a/src/mono/netcore/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs b/src/mono/netcore/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs
index 79a6a6902593b..e054eee0ab2cf 100644
--- a/src/mono/netcore/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs
+++ b/src/mono/netcore/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.Mono.cs
@@ -99,6 +99,11 @@ public static void RunModuleConstructor (ModuleHandle module)
RunModuleConstructor (module.Value);
}
+ public static IntPtr AllocateTypeAssociatedMemory (Type type, int size)
+ {
+ throw new PlatformNotSupportedException ();
+ }
+
[Intrinsic]
public static bool IsReferenceOrContainsReferences () => IsReferenceOrContainsReferences ();