From b41296a37f4bfead66db234e9d14b81fff302567 Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Fri, 6 Mar 2020 23:37:47 -0800 Subject: [PATCH] Implement new COM interop API for RCW/CCW creation/management (#32091) * Implement RuntimeHelpers.AllocateTypeAssociatedMemory(). * Add tests for RuntimeHelpers.AllocateTypeAssociatedMemory(). * Implement ComWrappers API. * Add tests for ComWrappers API. * Add a FEATURE_COMWRAPPERS feature flag. --- src/coreclr/clr.featuredefines.props | 2 + src/coreclr/clrdefinitions.cmake | 1 + src/coreclr/src/CMakeLists.txt | 1 + .../System.Private.CoreLib.csproj | 3 + .../CompilerServices/RuntimeHelpers.CoreCLR.cs | 22 + .../System/Runtime/InteropServices/ComWrappers.cs | 252 ++++ .../src/dlls/mscoree/coreclr/CMakeLists.txt | 2 + src/coreclr/src/inc/CrstTypes.def | 3 + src/coreclr/src/inc/crsttypes.h | 217 ++-- src/coreclr/src/interop/CMakeLists.txt | 36 + src/coreclr/src/interop/comwrappers.cpp | 717 +++++++++++ src/coreclr/src/interop/comwrappers.hpp | 249 ++++ src/coreclr/src/interop/inc/interoplib.h | 98 ++ src/coreclr/src/interop/inc/interoplibimports.h | 78 ++ src/coreclr/src/interop/interoplib.cpp | 161 +++ src/coreclr/src/interop/platform.h | 31 + src/coreclr/src/interop/referencetrackertypes.hpp | 59 + src/coreclr/src/interop/trackerobjectmanager.cpp | 372 ++++++ src/coreclr/src/vm/CMakeLists.txt | 3 + src/coreclr/src/vm/ceemain.cpp | 2 +- src/coreclr/src/vm/ecalllist.h | 12 + src/coreclr/src/vm/gcenv.ee.cpp | 17 +- src/coreclr/src/vm/gcenv.ee.standalone.cpp | 2 +- src/coreclr/src/vm/gcenv.ee.static.cpp | 2 +- src/coreclr/src/vm/interoplibinterface.cpp | 1299 ++++++++++++++++++++ src/coreclr/src/vm/interoplibinterface.h | 48 + src/coreclr/src/vm/interoputil.cpp | 23 + src/coreclr/src/vm/metasig.h | 3 + src/coreclr/src/vm/mscorlib.cpp | 1 + src/coreclr/src/vm/mscorlib.h | 11 +- src/coreclr/src/vm/rcwrefcache.cpp | 35 +- src/coreclr/src/vm/rcwrefcache.h | 9 +- src/coreclr/src/vm/rcwwalker.h | 5 +- src/coreclr/src/vm/runtimehandles.cpp | 22 + src/coreclr/src/vm/runtimehandles.h | 3 + src/coreclr/src/vm/syncblk.h | 44 + src/coreclr/tests/src/Interop/CMakeLists.txt | 1 + .../COM/ComWrappers/ComWrappersTests.csproj | 16 + .../MockReferenceTrackerRuntime/CMakeLists.txt | 10 + .../ReferenceTrackerRuntime.cpp | 339 +++++ .../tests/src/Interop/COM/ComWrappers/Program.cs | 517 ++++++++ .../tests/src/Interop/COM/NativeServer/Servers.h | 54 +- src/coreclr/tests/src/Interop/common/ComHelpers.h | 73 ++ .../ExecuteCodeWithGuaranteedCleanup.cs | 78 +- .../RuntimeHelpers/RuntimeHelpersTests.cs | 23 + ...edCleanup.csproj => RuntimeHelpersTests.csproj} | 4 +- .../src/Resources/Strings.resx | 3 + .../src/System.Private.CoreLib.Shared.projitems | 3 + .../ComWrappers.PlatformNotSupported.cs | 76 ++ .../ref/System.Runtime.InteropServices.cs | 36 + src/libraries/System.Runtime/ref/System.Runtime.cs | 1 + .../CompilerServices/RuntimeHelpersTests.cs | 16 + .../CompilerServices/RuntimeHelpers.Mono.cs | 5 + 53 files changed, 4862 insertions(+), 238 deletions(-) create mode 100644 src/coreclr/src/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs create mode 100644 src/coreclr/src/interop/CMakeLists.txt create mode 100644 src/coreclr/src/interop/comwrappers.cpp create mode 100644 src/coreclr/src/interop/comwrappers.hpp create mode 100644 src/coreclr/src/interop/inc/interoplib.h create mode 100644 src/coreclr/src/interop/inc/interoplibimports.h create mode 100644 src/coreclr/src/interop/interoplib.cpp create mode 100644 src/coreclr/src/interop/platform.h create mode 100644 src/coreclr/src/interop/referencetrackertypes.hpp create mode 100644 src/coreclr/src/interop/trackerobjectmanager.cpp create mode 100644 src/coreclr/src/vm/interoplibinterface.cpp create mode 100644 src/coreclr/src/vm/interoplibinterface.h create mode 100644 src/coreclr/tests/src/Interop/COM/ComWrappers/ComWrappersTests.csproj create mode 100644 src/coreclr/tests/src/Interop/COM/ComWrappers/MockReferenceTrackerRuntime/CMakeLists.txt create mode 100644 src/coreclr/tests/src/Interop/COM/ComWrappers/MockReferenceTrackerRuntime/ReferenceTrackerRuntime.cpp create mode 100644 src/coreclr/tests/src/Interop/COM/ComWrappers/Program.cs create mode 100644 src/coreclr/tests/src/baseservices/compilerservices/RuntimeHelpers/RuntimeHelpersTests.cs rename src/coreclr/tests/src/baseservices/compilerservices/RuntimeHelpers/{ExecuteCodeWithGuaranteedCleanup.csproj => RuntimeHelpersTests.csproj} (65%) create mode 100644 src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.PlatformNotSupported.cs diff --git a/src/coreclr/clr.featuredefines.props b/src/coreclr/clr.featuredefines.props index 7ae2f2f..b407467 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 4747880..a58e3af 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 6a75325..b6584b2 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 881d460..7e215d1 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 117e7e2..705e611 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 @@ namespace System.Runtime.CompilerServices 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 0000000..f5d0e06 --- /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 2fabad1..e86e91f 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 cf53f5b..5bbf1b2 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 4bc0665..3638826 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 0000000..d7eaa1b --- /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 0000000..d7fa94b --- /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 0000000..d233774 --- /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 0000000..df59b8b --- /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 0000000..3217b78 --- /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 0000000..8283024 --- /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 0000000..b21ec99 --- /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 0000000..b9802a4 --- /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 0000000..7d1cb15 --- /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 43b9bc3..30471d4 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 2790349..23b31b6 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 5a20f27..1ae20b1 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 fe0fc30..8352bb6 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 85f6a69..5d6a58d 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 e04fd58..697fed5b 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 0000000..70e5f14 --- /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 0000000..2ed54cf --- /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 0ec23b6..fc87cf92 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 8699224..e783046 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 40a1e14..6cdd5d9 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 3ce6149..12ec6d8 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 652f182..c22a84e 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 4566b08..4e9aa20 100644 --- a/src/coreclr/src/vm/rcwrefcache.h +++ b/src/coreclr/src/vm/rcwrefcache.h @@ -32,6 +32,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 0a532d8..f625725 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 e740579..2b4cad2 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 36bc4ad..7787e01 100644 --- a/src/coreclr/src/vm/runtimehandles.h +++ b/src/coreclr/src/vm/runtimehandles.h @@ -261,6 +261,9 @@ public: 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 f30b056..e0364df 100644 --- a/src/coreclr/src/vm/syncblk.h +++ b/src/coreclr/src/vm/syncblk.h @@ -789,6 +789,50 @@ public: // 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 3e534c8..d8f23d3 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 0000000..e82960a --- /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 0000000..fb232b9 --- /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 0000000..f0a0f30 --- /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 0000000..e85f87a --- /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 2ff3f88..3e0fccc 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 @@ private: } }; -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 f6a35d3..c90ff7a 100644 --- a/src/coreclr/tests/src/Interop/common/ComHelpers.h +++ b/src/coreclr/tests/src/Interop/common/ComHelpers.h @@ -331,3 +331,76 @@ public: // IUnknown 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 40b92b7..faf6504 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 0000000..5b667a7 --- /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 2e8010d..fa8ab39 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 d3e1a96..0639dc7 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 62e69dd..3ee3d97 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 0000000..77e8b35 --- /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 aee1cf8..92ddefd 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 @@ namespace System.Runtime.InteropServices 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 467d544..0f0362a 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -9025,6 +9025,7 @@ namespace System.Runtime.CompilerServices 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 bd4054d..dea93fa 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 @@ namespace System.Runtime.CompilerServices.Tests 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 79a6a69..e054eee 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 @@ namespace System.Runtime.CompilerServices RunModuleConstructor (module.Value); } + public static IntPtr AllocateTypeAssociatedMemory (Type type, int size) + { + throw new PlatformNotSupportedException (); + } + [Intrinsic] public static bool IsReferenceOrContainsReferences () => IsReferenceOrContainsReferences (); -- 2.7.4