// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #ifdef FEATURE_COMWRAPPERS // Runtime headers #include "common.h" #include "simplerwlock.hpp" #include "rcwrefcache.h" #ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT #include "olecontexthelpers.h" #endif #include "finalizerthread.h" // Interop library header #include #include "interoplibinterface.h" using CreateObjectFlags = InteropLib::Com::CreateObjectFlags; using CreateComInterfaceFlags = InteropLib::Com::CreateComInterfaceFlags; namespace { void* GetCurrentCtxCookieWrapper() { STATIC_CONTRACT_WRAPPER; #ifdef FEATURE_COMINTEROP_APARTMENT_SUPPORT return GetCurrentCtxCookie(); #else return NULL; #endif // FEATURE_COMINTEROP_APARTMENT_SUPPORT } // This class is used to track the external object within the runtime. struct ExternalObjectContext : public InteropLibInterface::ExternalObjectContextBase { static const DWORD InvalidSyncBlockIndex; void* ThreadContext; INT64 WrapperId; enum { Flags_None = 0, // The EOC has been collected and is no longer visible from managed code. Flags_Collected = 1, Flags_ReferenceTracker = 2, Flags_InCache = 4, // The EOC is "detached" and no longer used to map between identity and a managed object. // This will only be set if the EOC was inserted into the cache. Flags_Detached = 8, // This EOC is an aggregated instance Flags_Aggregated = 16 }; DWORD Flags; static void Construct( _Out_ ExternalObjectContext* cxt, _In_ IUnknown* identity, _In_opt_ void* threadContext, _In_ DWORD syncBlockIndex, _In_ INT64 wrapperId, _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->WrapperId = wrapperId; 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; } void MarkDetached() { _ASSERTE(GCHeapUtilities::IsGCInProgress()); Flags |= Flags_Detached; } void MarkNotInCache() { ::InterlockedAnd((LONG*)&Flags, (~Flags_InCache)); } OBJECTREF GetObjectRef() { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_COOPERATIVE; } CONTRACTL_END; _ASSERTE(IsActive()); return ObjectToOBJECTREF(g_pSyncTable[SyncBlockIndex].m_Object); } struct Key { public: Key(void* identity, INT64 wrapperId) : _identity { identity } , _wrapperId { wrapperId } { _ASSERTE(identity != NULL); _ASSERTE(wrapperId != ComWrappersNative::InvalidWrapperId); } DWORD Hash() const { DWORD hash = (_wrapperId >> 32) ^ (_wrapperId & 0xFFFFFFFF); #if POINTER_BITS == 32 return hash ^ (DWORD)_identity; #else INT64 identityInt64 = (INT64)_identity; return hash ^ (identityInt64 >> 32) ^ (identityInt64 & 0xFFFFFFFF); #endif } bool operator==(const Key & rhs) const { return _identity == rhs._identity && _wrapperId == rhs._wrapperId; } private: void* _identity; INT64 _wrapperId; }; Key GetKey() const { return Key(Identity, WrapperId); } }; 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) { GCX_PREEMP(); // We also request collection notification since this holder is presently // only used for new activation of wrappers therefore the notification won't occur // at the typical time of before finalization. InteropLib::Com::DestroyWrapperForExternal(Result.Context, /* notifyIsBeingCollected */ true); } } 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 != InterlockedCompareExchangeT(&g_Instance, instMaybe, NULL)) delete instMaybe; } RETURN g_Instance; } public: // Inner class definitions class Traits : public DefaultSHashTraits { public: using key_t = ExternalObjectContext::Key; static const key_t GetKey(_In_ element_t e) { LIMITED_METHOD_CONTRACT; return e->GetKey(); } static count_t Hash(_In_ key_t key) { LIMITED_METHOD_CONTRACT; return (count_t)key.Hash(); } 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 ReaderLock final { SimpleReadLockHolder _lock; public: ReaderLock(_In_ ExtObjCxtCache* cache) : _lock{ &cache->_lock } { } ~ReaderLock() = default; }; class WriterLock final { SimpleWriteLockHolder _lock; public: WriterLock(_In_ ExtObjCxtCache* cache) : _lock{ &cache->_lock } { } ~WriterLock() = default; }; private: friend struct InteropLibImports::RuntimeCallContext; SHash _hashMap; SimpleRWLock _lock; ExtObjCxtRefCache* _refCache; ExtObjCxtCache() : _lock(COOPERATIVE, LOCK_TYPE_DEFAULT) , _refCache(GetAppDomain()->GetRCWRefCache()) { } ~ExtObjCxtCache() = default; public: #if _DEBUG bool IsLockHeld() { WRAPPER_NO_CONTRACT; return (_lock.LockTaken() != 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. OBJECTREF CreateManagedEnumerable(_In_ DWORD withFlags, _In_opt_ void* threadContext) { CONTRACT(OBJECTREF) { THROWS; GC_TRIGGERS; MODE_COOPERATIVE; PRECONDITION(!IsLockHeld()); PRECONDITION(!(withFlags & ExternalObjectContext::Flags_Collected)); PRECONDITION(!(withFlags & ExternalObjectContext::Flags_Detached)); POSTCONDITION(RETVAL != NULL); } CONTRACT_END; struct { PTRARRAYREF arrRefTmp; PTRARRAYREF arrRef; } gc; gc.arrRefTmp = NULL; gc.arrRef = NULL; GCPROTECT_BEGIN(gc); // Only add objects that are in the correct thread // context, haven't been detached from the cache, and have // the appropriate flags set. // Define a macro predicate since it used in multiple places. // If an instance is in the hashmap, it is active. This invariant // holds because the GC is what marks and removes from the cache. #define SELECT_OBJECT(XX) XX->ThreadContext == threadContext \ && !XX->IsSet(ExternalObjectContext::Flags_Detached) \ && (withFlags == ExternalObjectContext::Flags_None || XX->IsSet(withFlags)) // Determine the count of objects to return. SIZE_T objCountMax = 0; { ReaderLock lock(this); Iterator end = _hashMap.End(); for (Iterator curr = _hashMap.Begin(); curr != end; ++curr) { ExternalObjectContext* inst = *curr; if (SELECT_OBJECT(inst)) { objCountMax++; } } } // Allocate enumerable type to return. gc.arrRef = (PTRARRAYREF)AllocateObjectArray((DWORD)objCountMax, g_pObjectClass); CQuickArrayList localList; // Iterate over the hashmap again while populating the above array // using the same predicate as before and holding onto context instances. SIZE_T objCount = 0; if (0 < objCountMax) { ReaderLock lock(this); Iterator end = _hashMap.End(); for (Iterator curr = _hashMap.Begin(); curr != end; ++curr) { ExternalObjectContext* inst = *curr; if (SELECT_OBJECT(inst)) { localList.Push(inst); gc.arrRef->SetAt(objCount, inst->GetObjectRef()); objCount++; STRESS_LOG1(LF_INTEROP, LL_INFO1000, "Add EOC to Enumerable: 0x%p\n", inst); } // There is a chance more objects were added to the hash while the // lock was released during array allocation. Once we hit the computed max // we stop to avoid looking longer than needed. if (objCount == objCountMax) break; } } #undef SELECT_OBJECT // During the allocation of the array to return, a GC could have // occurred and objects detached from this cache. In order to avoid // having null array elements we will allocate a new array. // This subsequent allocation is okay because the array we are // replacing extends all object lifetimes. if (objCount < objCountMax) { gc.arrRefTmp = (PTRARRAYREF)AllocateObjectArray((DWORD)objCount, g_pObjectClass); void* dest = gc.arrRefTmp->GetDataPtr(); void* src = gc.arrRef->GetDataPtr(); SIZE_T elementSize = gc.arrRef->GetComponentSize(); memmoveGCRefs(dest, src, objCount * elementSize); gc.arrRef = gc.arrRefTmp; } // All objects are now referenced from the array so won't be collected // at this point. This means we can safely iterate over the ExternalObjectContext // instances. { // Separate the wrapper from the tracker runtime prior to // passing them onto the caller. This call is okay to make // even if the instance isn't from the tracker runtime. // We switch to Preemptive mode since separating a wrapper // requires us to call out to non-runtime code which could // call back into the runtime and/or trigger a GC. GCX_PREEMP(); for (SIZE_T i = 0; i < localList.Size(); i++) { ExternalObjectContext* inst = localList[i]; InteropLib::Com::SeparateWrapperFromTrackerRuntime(inst); } } GCPROTECT_END(); RETURN gc.arrRef; } ExternalObjectContext* Find(_In_ const ExternalObjectContext::Key& key) { CONTRACT(ExternalObjectContext*) { NOTHROW; GC_NOTRIGGER; MODE_COOPERATIVE; PRECONDITION(IsLockHeld()); POSTCONDITION(CheckPointer(RETVAL, NULL_OK)); } CONTRACT_END; // Forbid the GC from messing with the hash table. GCX_FORBID(); RETURN _hashMap.Lookup(key); } 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(cxt->GetKey()) == NULL); POSTCONDITION(RETVAL == cxt); } CONTRACT_END; _hashMap.Add(cxt); RETURN cxt; } ExternalObjectContext* FindOrAdd(_In_ const ExternalObjectContext::Key& key, _In_ ExternalObjectContext* newCxt) { CONTRACT(ExternalObjectContext*) { THROWS; GC_NOTRIGGER; MODE_COOPERATIVE; PRECONDITION(IsLockHeld()); PRECONDITION(!Traits::IsNull(newCxt) && !Traits::IsDeleted(newCxt)); PRECONDITION(key == newCxt->GetKey()); 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->GetKey()); } void DetachNotPromotedEOCs() { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; PRECONDITION(GCHeapUtilities::IsGCInProgress()); // GC is in progress and the runtime is suspended } CONTRACTL_END; Iterator curr = _hashMap.Begin(); Iterator end = _hashMap.End(); ExternalObjectContext* cxt; for (; curr != end; ++curr) { cxt = *curr; if (!cxt->IsSet(ExternalObjectContext::Flags_Detached) && !GCHeapUtilities::GetGCHeap()->IsPromoted(OBJECTREFToObject(cxt->GetObjectRef()))) { // Indicate the wrapper entry has been detached. cxt->MarkDetached(); // Notify the wrapper it was not promoted and is being collected. InteropLib::Com::NotifyWrapperForExternalIsBeingCollected(cxt); } } } }; // Global instance of the external object cache Volatile ExtObjCxtCache::g_Instance; // Indicator for if a ComWrappers implementation is globally registered INT64 g_marshallingGlobalInstanceId = ComWrappersNative::InvalidWrapperId; INT64 g_trackerSupportGlobalInstanceId = ComWrappersNative::InvalidWrapperId; // Defined handle types for the specific object uses. const HandleType InstanceHandleType{ HNDTYPE_REFCOUNTED }; // Scenarios for ComWrappers usage. // These values should match the managed definition in ComWrappers. enum class ComWrappersScenario { Instance = 0, TrackerSupportGlobalInstance = 1, MarshallingGlobalInstance = 2, }; void* CallComputeVTables( _In_ ComWrappersScenario scenario, _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, 5); args[ARGNUM_0] = DWORD_TO_ARGHOLDER(scenario); args[ARGNUM_1] = OBJECTREF_TO_ARGHOLDER(*implPROTECTED); args[ARGNUM_2] = OBJECTREF_TO_ARGHOLDER(*instancePROTECTED); args[ARGNUM_3] = DWORD_TO_ARGHOLDER(flags); args[ARGNUM_4] = PTR_TO_ARGHOLDER(vtableCount); CALL_MANAGED_METHOD(vtables, void*, args); return vtables; } OBJECTREF CallCreateObject( _In_ ComWrappersScenario scenario, _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, 4); args[ARGNUM_0] = DWORD_TO_ARGHOLDER(scenario); args[ARGNUM_1] = OBJECTREF_TO_ARGHOLDER(*implPROTECTED); args[ARGNUM_2] = PTR_TO_ARGHOLDER(externalComObject); args[ARGNUM_3] = DWORD_TO_ARGHOLDER(flags); CALL_MANAGED_METHOD_RETREF(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); } int CallICustomQueryInterface( _In_ OBJECTREF* implPROTECTED, _In_ REFGUID iid, _Outptr_result_maybenull_ void** ppObject) { CONTRACTL { THROWS; MODE_COOPERATIVE; PRECONDITION(implPROTECTED != NULL); PRECONDITION(ppObject != NULL); } CONTRACTL_END; int result; PREPARE_NONVIRTUAL_CALLSITE(METHOD__COMWRAPPERS__CALL_ICUSTOMQUERYINTERFACE); DECLARE_ARGHOLDER_ARRAY(args, 3); args[ARGNUM_0] = OBJECTREF_TO_ARGHOLDER(*implPROTECTED); args[ARGNUM_1] = PTR_TO_ARGHOLDER(&iid); args[ARGNUM_2] = PTR_TO_ARGHOLDER(ppObject); CALL_MANAGED_METHOD(result, int, args); return result; } bool TryGetOrCreateComInterfaceForObjectInternal( _In_opt_ OBJECTREF impl, _In_ INT64 wrapperId, _In_ OBJECTREF instance, _In_ CreateComInterfaceFlags flags, _In_ ComWrappersScenario scenario, _Outptr_ void** wrapperRaw) { CONTRACT(bool) { THROWS; MODE_COOPERATIVE; PRECONDITION(instance != NULL); PRECONDITION(wrapperRaw != NULL); PRECONDITION((impl != NULL && scenario == ComWrappersScenario::Instance) || (impl == NULL && scenario != ComWrappersScenario::Instance)); PRECONDITION(wrapperId != ComWrappersNative::InvalidWrapperId); } CONTRACT_END; HRESULT hr; SafeComHolder newWrapper; void* wrapperRawMaybe = NULL; struct { OBJECTREF implRef; OBJECTREF instRef; } gc; gc.implRef = impl; gc.instRef = instance; GCPROTECT_BEGIN(gc); // 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(wrapperId, &wrapperRawMaybe)) { // 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(scenario, &gc.implRef, &gc.instRef, flags, &vtableCount); // Re-query the associated InteropSyncBlockInfo for an existing managed object wrapper. if (!interopInfo->TryGetManagedObjectComWrapper(wrapperId, &wrapperRawMaybe) && ((vtables != nullptr && vtableCount > 0) || (vtableCount == 0))) { OBJECTHANDLE instHandle = GetAppDomain()->CreateTypedHandle(gc.instRef, InstanceHandleType); // Call the InteropLib and create the associated managed object wrapper. { GCX_PREEMP(); 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(wrapperId, 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(wrapperId, &wrapperRawMaybe)) { 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. wrapperRawMaybe = newWrapper.Extract(); STRESS_LOG1(LF_INTEROP, LL_INFO100, "Created MOW: 0x%p\n", wrapperRawMaybe); } else if (wrapperRawMaybe != NULL) { // AddRef() the existing wrapper. IUnknown* wrapper = static_cast(wrapperRawMaybe); (void)wrapper->AddRef(); } GCPROTECT_END(); *wrapperRaw = wrapperRawMaybe; RETURN (wrapperRawMaybe != NULL); } bool TryGetOrCreateObjectForComInstanceInternal( _In_opt_ OBJECTREF impl, _In_ INT64 wrapperId, _In_ IUnknown* identity, _In_opt_ IUnknown* inner, _In_ CreateObjectFlags flags, _In_ ComWrappersScenario scenario, _In_opt_ OBJECTREF wrapperMaybe, _Out_ OBJECTREF* objRef) { CONTRACT(bool) { THROWS; MODE_COOPERATIVE; PRECONDITION(identity != NULL); PRECONDITION(objRef != NULL); PRECONDITION((impl != NULL && scenario == ComWrappersScenario::Instance) || (impl == NULL && scenario != ComWrappersScenario::Instance)); PRECONDITION(wrapperId != ComWrappersNative::InvalidWrapperId); } CONTRACT_END; HRESULT hr; ExternalObjectContext* extObjCxt = NULL; struct { OBJECTREF implRef; OBJECTREF wrapperMaybeRef; OBJECTREF objRefMaybe; } gc; gc.implRef = impl; gc.wrapperMaybeRef = wrapperMaybe; gc.objRefMaybe = NULL; GCPROTECT_BEGIN(gc); STRESS_LOG4(LF_INTEROP, LL_INFO1000, "Get or Create EOC: (Identity: 0x%p) (Flags: %x) (Maybe: 0x%p) (ID: %lld)\n", identity, flags, OBJECTREFToObject(wrapperMaybe), wrapperId); ExtObjCxtCache* cache = ExtObjCxtCache::GetInstance(); InteropLib::OBJECTHANDLE handle = NULL; ExternalObjectContext::Key cacheKey(identity, wrapperId); // Check if the user requested a unique instance. bool uniqueInstance = !!(flags & CreateObjectFlags::CreateObjectFlags_UniqueInstance); if (!uniqueInstance) { bool objectFound = false; bool tryRemove = false; { // Perform a quick look up to determine if we know of the object and if // we need to perform a more expensive cleanup operation below. ExtObjCxtCache::ReaderLock lock(cache); extObjCxt = cache->Find(cacheKey); objectFound = extObjCxt != NULL; tryRemove = objectFound && extObjCxt->IsSet(ExternalObjectContext::Flags_Detached); } if (tryRemove) { // Perform the slower cleanup operation that may be appropriate // if the object still exists and has been detached. ExtObjCxtCache::WriterLock lock(cache); extObjCxt = cache->Find(cacheKey); objectFound = extObjCxt != NULL; if (objectFound && extObjCxt->IsSet(ExternalObjectContext::Flags_Detached)) { // If an EOC has been found but is marked detached, then we will remove it from the // cache here instead of letting the GC do it later and pretend like it wasn't found. STRESS_LOG1(LF_INTEROP, LL_INFO10, "Detached EOC requested: 0x%p\n", extObjCxt); cache->Remove(extObjCxt); extObjCxt->MarkNotInCache(); extObjCxt = NULL; } } // If is no object found in the cache, check if the object COM instance is actually the CCW // representing a managed object. If the user passed the Unwrap flag, COM instances that are // actually CCWs should be unwrapped to the original managed object to allow for round // tripping object -> COM instance -> object. if (!objectFound && (flags & CreateObjectFlags::CreateObjectFlags_Unwrap)) { GCX_PREEMP(); // If the COM instance is a CCW that is not COM-activated, use the object of that wrapper object. InteropLib::OBJECTHANDLE handleLocal; if (InteropLib::Com::GetObjectForWrapper(identity, &handleLocal) == S_OK && InteropLib::Com::IsComActivated(identity) == S_FALSE) { handle = handleLocal; } } } STRESS_LOG2(LF_INTEROP, LL_INFO1000, "EOC: 0x%p or Handle: 0x%p\n", extObjCxt, handle); if (extObjCxt != NULL) { gc.objRefMaybe = extObjCxt->GetObjectRef(); } else if (handle != NULL) { // We have an object handle from the COM instance which is a CCW. ::OBJECTHANDLE objectHandle = static_cast<::OBJECTHANDLE>(handle); // Now we need to check if this object is a CCW from the same ComWrappers instance // as the one creating the EOC. If it is not, we need to create a new EOC for it. // Otherwise, use it. This allows for the round-trip from object -> COM instance -> object. OBJECTREF objRef = NULL; GCPROTECT_BEGIN(objRef); objRef = ObjectFromHandle(objectHandle); SyncBlock* syncBlock = objRef->GetSyncBlock(); InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfo(); // If we found a managed object wrapper in this ComWrappers instance // and it's the same identity pointer as the one we're creating an EOC for, // unwrap it. We don't AddRef the wrapper as we don't take a reference to it. // // A managed object can have multiple managed object wrappers, with a max of one per context. // Let's say we have a managed object A and ComWrappers instances C1 and C2. Let B1 and B2 be the // managed object wrappers for A created with C1 and C2 respectively. // If we are asked to create an EOC for B1 with the unwrap flag on the C2 ComWrappers instance, // we will create a new wrapper. In this scenario, we'll only unwrap B2. void* wrapperRawMaybe = NULL; if (interopInfo->TryGetManagedObjectComWrapper(wrapperId, &wrapperRawMaybe) && wrapperRawMaybe == identity) { gc.objRefMaybe = objRef; } else { STRESS_LOG2(LF_INTEROP, LL_INFO1000, "Not unwrapping handle (0x%p) because the object's MOW in this ComWrappers instance (if any) (0x%p) is not the provided identity\n", handle, wrapperRawMaybe); } GCPROTECT_END(); } if (gc.objRefMaybe == NULL) { // Create context instance for the possibly new external object. ExternalWrapperResultHolder resultHolder; { GCX_PREEMP(); hr = InteropLib::Com::CreateWrapperForExternal( identity, inner, flags, sizeof(ExternalObjectContext), &resultHolder); } if (FAILED(hr)) COMPlusThrowHR(hr); // The user could have supplied a wrapper so assign that now. gc.objRefMaybe = gc.wrapperMaybeRef; // If the wrapper hasn't been set yet, call the implementation to create one. if (gc.objRefMaybe == NULL) { gc.objRefMaybe = CallCreateObject(scenario, &gc.implRef, identity, flags); } // The object may be null if the specified ComWrapper implementation returns null // or there is no registered global instance. It is the caller's responsibility // to handle this case and error if necessary. if (gc.objRefMaybe != NULL) { // Construct the new context with the object details. DWORD eocFlags = (resultHolder.Result.FromTrackerRuntime ? ExternalObjectContext::Flags_ReferenceTracker : ExternalObjectContext::Flags_None) | (uniqueInstance ? ExternalObjectContext::Flags_None : ExternalObjectContext::Flags_InCache) | ((flags & CreateObjectFlags::CreateObjectFlags_Aggregated) != 0 ? ExternalObjectContext::Flags_Aggregated : ExternalObjectContext::Flags_None); ExternalObjectContext::Construct( resultHolder.GetContext(), identity, GetCurrentCtxCookieWrapper(), gc.objRefMaybe->GetSyncBlockIndex(), wrapperId, eocFlags); if (uniqueInstance) { extObjCxt = resultHolder.GetContext(); } else { // Attempt to insert the new context into the cache. ExtObjCxtCache::WriterLock lock(cache); extObjCxt = cache->FindOrAdd(cacheKey, resultHolder.GetContext()); } STRESS_LOG2(LF_INTEROP, LL_INFO100, "EOC cache insert: 0x%p == 0x%p\n", extObjCxt, 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.objRefMaybe->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::WriterLock 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); // If this is an aggregation scenario and the identity object // is a managed object wrapper, we need to call Release() to // indicate this external object isn't rooted. In the event the // object is passed out to native code an AddRef() must be called // based on COM convention and will "fix" the count. if (flags & CreateObjectFlags::CreateObjectFlags_Aggregated && resultHolder.Result.ManagedObjectWrapper) { (void)identity->Release(); STRESS_LOG1(LF_INTEROP, LL_INFO100, "EOC aggregated with MOW: 0x%p\n", identity); } } _ASSERTE(extObjCxt->IsActive()); } } STRESS_LOG3(LF_INTEROP, LL_INFO1000, "EOC: 0x%p, 0x%p => 0x%p\n", extObjCxt, identity, OBJECTREFToObject(gc.objRefMaybe)); GCPROTECT_END(); *objRef = gc.objRefMaybe; RETURN (gc.objRefMaybe != NULL); } } namespace { BOOL g_isGlobalPeggingOn = TRUE; } 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::AddMemoryPressure(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::RemoveMemoryPressure(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; gc.implRef = NULL; // Use the globally registered implementation. gc.objsEnumRef = NULL; GCPROTECT_BEGIN(gc); // Pass the objects along to get released. ExtObjCxtCache* cache = ExtObjCxtCache::GetInstanceNoThrow(); gc.objsEnumRef = cache->CreateManagedEnumerable( ExternalObjectContext::Flags_ReferenceTracker, GetCurrentCtxCookieWrapper()); 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 HasValidTarget(_In_ InteropLib::OBJECTHANDLE handle) noexcept { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; PRECONDITION(handle != NULL); } CONTRACTL_END; ::OBJECTHANDLE objectHandle = static_cast<::OBJECTHANDLE>(handle); // A valid target is one that is not null. bool isNotNull = ObjectHandleIsNull(objectHandle) == FALSE; return isNotNull; } bool GetGlobalPeggingState() noexcept { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; } CONTRACTL_END; return (VolatileLoad(&g_isGlobalPeggingOn) != FALSE); } void SetGlobalPeggingState(_In_ bool state) noexcept { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; } CONTRACTL_END; BOOL newState = state ? TRUE : FALSE; VolatileStore(&g_isGlobalPeggingOn, 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; gc.implRef = NULL; // Use the globally registered implementation. gc.wrapperMaybeRef = NULL; // No supplied wrapper here. gc.objRef = NULL; GCPROTECT_BEGIN(gc); // Get wrapper for external object bool success = TryGetOrCreateObjectForComInstanceInternal( gc.implRef, g_trackerSupportGlobalInstanceId, externalComObject, NULL, externalObjectFlags, ComWrappersScenario::TrackerSupportGlobalInstance, gc.wrapperMaybeRef, &gc.objRef); if (!success) COMPlusThrow(kArgumentNullException); // Get wrapper for managed object success = TryGetOrCreateComInterfaceForObjectInternal( gc.implRef, g_trackerSupportGlobalInstanceId, gc.objRef, trackerTargetFlags, ComWrappersScenario::TrackerSupportGlobalInstance, trackerTarget); if (!success) COMPlusThrow(kArgumentException); 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; } TryInvokeICustomQueryInterfaceResult TryInvokeICustomQueryInterface( _In_ InteropLib::OBJECTHANDLE handle, _In_ REFGUID iid, _Outptr_result_maybenull_ void** obj) noexcept { CONTRACTL { NOTHROW; MODE_ANY; PRECONDITION(handle != NULL); PRECONDITION(obj != NULL); } CONTRACTL_END; *obj = NULL; // If this is a GC thread, then someone is trying to query for something // at a time when we can't run managed code. if (IsGCThread()) return TryInvokeICustomQueryInterfaceResult::OnGCThread; // Ideally the BEGIN_EXTERNAL_ENTRYPOINT/END_EXTERNAL_ENTRYPOINT pairs // would be used here. However, this code path can be entered from within // and from outside the runtime. MAKE_CURRENT_THREAD_AVAILABLE_EX(GetThreadNULLOk()); if (CURRENT_THREAD == NULL) { CURRENT_THREAD = SetupThreadNoThrow(); // If we failed to set up a new thread, we are going to indicate // there was a general failure to invoke instead of failing fast. if (CURRENT_THREAD == NULL) return TryInvokeICustomQueryInterfaceResult::FailedToInvoke; } HRESULT hr; auto result = TryInvokeICustomQueryInterfaceResult::FailedToInvoke; EX_TRY_THREAD(CURRENT_THREAD) { // Switch to Cooperative mode since object references // are being manipulated. GCX_COOP(); struct { OBJECTREF objRef; } gc; gc.objRef = NULL; GCPROTECT_BEGIN(gc); // Get the target of the external object's reference. ::OBJECTHANDLE objectHandle = static_cast<::OBJECTHANDLE>(handle); gc.objRef = ObjectFromHandle(objectHandle); result = (TryInvokeICustomQueryInterfaceResult)CallICustomQueryInterface(&gc.objRef, iid, obj); GCPROTECT_END(); } EX_CATCH_HRESULT(hr); // Assert valid value. _ASSERTE(TryInvokeICustomQueryInterfaceResult::Min <= result && result <= TryInvokeICustomQueryInterfaceResult::Max); return result; } 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); // Return if the target has been collected or these are the same object. if (target == NULL || 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); } } extern "C" BOOL QCALLTYPE ComWrappers_TryGetOrCreateComInterfaceForObject( _In_ QCall::ObjectHandleOnStack comWrappersImpl, _In_ INT64 wrapperId, _In_ QCall::ObjectHandleOnStack instance, _In_ INT32 flags, _Outptr_ void** wrapper) { QCALL_CONTRACT; bool success = false; BEGIN_QCALL; // Switch to Cooperative mode since object references // are being manipulated. { GCX_COOP(); success = TryGetOrCreateComInterfaceForObjectInternal( ObjectToOBJECTREF(*comWrappersImpl.m_ppObject), wrapperId, ObjectToOBJECTREF(*instance.m_ppObject), (CreateComInterfaceFlags)flags, ComWrappersScenario::Instance, wrapper); } END_QCALL; return (success ? TRUE : FALSE); } extern "C" BOOL QCALLTYPE ComWrappers_TryGetOrCreateObjectForComInstance( _In_ QCall::ObjectHandleOnStack comWrappersImpl, _In_ INT64 wrapperId, _In_ void* ext, _In_opt_ void* innerMaybe, _In_ INT32 flags, _In_ QCall::ObjectHandleOnStack wrapperMaybe, _Inout_ QCall::ObjectHandleOnStack retValue) { QCALL_CONTRACT; _ASSERTE(ext != NULL); bool success = false; BEGIN_QCALL; HRESULT hr; IUnknown* externalComObject = reinterpret_cast(ext); IUnknown* inner = reinterpret_cast(innerMaybe); // Determine the true identity and inner of the object SafeComHolder identity; hr = InteropLib::Com::DetermineIdentityAndInnerForExternal( externalComObject, (CreateObjectFlags)flags, &identity, &inner); _ASSERTE(hr == S_OK); // Switch to Cooperative mode since object references // are being manipulated. { GCX_COOP(); OBJECTREF newObj; success = TryGetOrCreateObjectForComInstanceInternal( ObjectToOBJECTREF(*comWrappersImpl.m_ppObject), wrapperId, identity, inner, (CreateObjectFlags)flags, ComWrappersScenario::Instance, ObjectToOBJECTREF(*wrapperMaybe.m_ppObject), &newObj); // Set the return value if (success) retValue.Set(newObj); } END_QCALL; return (success ? TRUE : FALSE); } extern "C" void QCALLTYPE ComWrappers_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; } extern "C" BOOL QCALLTYPE ComWrappers_TryGetComInstance( _In_ QCall::ObjectHandleOnStack wrapperMaybe, _Out_ void** externalComObject) { QCALL_CONTRACT; _ASSERTE(externalComObject != NULL); bool success = false; BEGIN_QCALL; // Switch to Cooperative mode since object references // are being manipulated. { GCX_COOP(); SyncBlock* syncBlock = ObjectToOBJECTREF(*wrapperMaybe.m_ppObject)->PassiveGetSyncBlock(); if (syncBlock != nullptr) { InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfoNoCreate(); if (interopInfo != nullptr) { void* contextMaybe; if (interopInfo->TryGetExternalComObjectContext(&contextMaybe)) { ExternalObjectContext* context = reinterpret_cast(contextMaybe); IUnknown* identity = reinterpret_cast(context->Identity); GCX_PREEMP(); success = SUCCEEDED(identity->QueryInterface(IID_IUnknown, externalComObject)); } } } } END_QCALL; return (success ? TRUE : FALSE); } extern "C" BOOL QCALLTYPE ComWrappers_TryGetObject( _In_ void* wrapperMaybe, _Inout_ QCall::ObjectHandleOnStack instance) { QCALL_CONTRACT; _ASSERTE(wrapperMaybe != NULL); bool success = false; BEGIN_QCALL; // Determine the true identity of the object SafeComHolder identity; HRESULT hr = ((IUnknown*)wrapperMaybe)->QueryInterface(IID_IUnknown, &identity); _ASSERTE(hr == S_OK); InteropLib::OBJECTHANDLE handle; if (InteropLib::Com::GetObjectForWrapper(identity, &handle) == S_OK) { // Switch to Cooperative mode since object references // are being manipulated. GCX_COOP(); // We have an object handle from the COM instance which is a CCW. ::OBJECTHANDLE objectHandle = static_cast<::OBJECTHANDLE>(handle); instance.Set(ObjectFromHandle(objectHandle)); success = true; } END_QCALL; return (success ? TRUE : FALSE); } void ComWrappersNative::DestroyManagedObjectComWrapper(_In_ void* wrapper) { CONTRACTL { NOTHROW; GC_TRIGGERS; MODE_ANY; PRECONDITION(wrapper != NULL); } CONTRACTL_END; STRESS_LOG1(LF_INTEROP, LL_INFO100, "Destroying MOW: 0x%p\n", wrapper); { GCX_PREEMP(); InteropLib::Com::DestroyWrapperForObject(wrapper); } } void ComWrappersNative::DestroyExternalComObjectContext(_In_ void* contextRaw) { CONTRACTL { NOTHROW; GC_TRIGGERS; 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); { GCX_PREEMP(); 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); } } void ComWrappersNative::MarkWrapperAsComActivated(_In_ IUnknown* wrapperMaybe) { CONTRACTL { THROWS; GC_TRIGGERS; MODE_COOPERATIVE; PRECONDITION(wrapperMaybe != NULL); } CONTRACTL_END; { GCX_PREEMP(); // The IUnknown may or may not represent a wrapper, so E_INVALIDARG is okay here. HRESULT hr = InteropLib::Com::MarkComActivated(wrapperMaybe); _ASSERTE(SUCCEEDED(hr) || hr == E_INVALIDARG); } } extern "C" void QCALLTYPE ComWrappers_SetGlobalInstanceRegisteredForMarshalling(INT64 id) { QCALL_CONTRACT_NO_GC_TRANSITION; _ASSERTE(g_marshallingGlobalInstanceId == ComWrappersNative::InvalidWrapperId && id != ComWrappersNative::InvalidWrapperId); g_marshallingGlobalInstanceId = id; } bool GlobalComWrappersForMarshalling::IsRegisteredInstance(INT64 id) { return g_marshallingGlobalInstanceId != ComWrappersNative::InvalidWrapperId && g_marshallingGlobalInstanceId == id; } bool GlobalComWrappersForMarshalling::TryGetOrCreateComInterfaceForObject( _In_ OBJECTREF instance, _Outptr_ void** wrapperRaw) { CONTRACTL { THROWS; MODE_COOPERATIVE; } CONTRACTL_END; if (g_marshallingGlobalInstanceId == ComWrappersNative::InvalidWrapperId) return false; // Switch to Cooperative mode since object references // are being manipulated. { GCX_COOP(); CreateComInterfaceFlags flags = CreateComInterfaceFlags::CreateComInterfaceFlags_TrackerSupport; // Passing NULL as the ComWrappers implementation indicates using the globally registered instance return TryGetOrCreateComInterfaceForObjectInternal( NULL, g_marshallingGlobalInstanceId, instance, flags, ComWrappersScenario::MarshallingGlobalInstance, wrapperRaw); } } bool GlobalComWrappersForMarshalling::TryGetOrCreateObjectForComInstance( _In_ IUnknown* externalComObject, _In_ INT32 objFromComIPFlags, _Out_ OBJECTREF* objRef) { CONTRACTL { THROWS; MODE_COOPERATIVE; } CONTRACTL_END; if (g_marshallingGlobalInstanceId == ComWrappersNative::InvalidWrapperId) return false; // Determine the true identity of the object SafeComHolder identity; { GCX_PREEMP(); HRESULT hr = externalComObject->QueryInterface(IID_IUnknown, &identity); _ASSERTE(hr == S_OK); } // Switch to Cooperative mode since object references // are being manipulated. { GCX_COOP(); // TrackerObject support and unwrapping matches the built-in semantics that the global marshalling scenario mimics. int flags = CreateObjectFlags::CreateObjectFlags_TrackerObject | CreateObjectFlags::CreateObjectFlags_Unwrap; if ((objFromComIPFlags & ObjFromComIP::UNIQUE_OBJECT) != 0) flags |= CreateObjectFlags::CreateObjectFlags_UniqueInstance; // Passing NULL as the ComWrappers implementation indicates using the globally registered instance return TryGetOrCreateObjectForComInstanceInternal( NULL /*comWrappersImpl*/, g_marshallingGlobalInstanceId, identity, NULL, (CreateObjectFlags)flags, ComWrappersScenario::MarshallingGlobalInstance, NULL /*wrapperMaybe*/, objRef); } } extern "C" void QCALLTYPE ComWrappers_SetGlobalInstanceRegisteredForTrackerSupport(INT64 id) { QCALL_CONTRACT_NO_GC_TRANSITION; _ASSERTE(g_trackerSupportGlobalInstanceId == ComWrappersNative::InvalidWrapperId && id != ComWrappersNative::InvalidWrapperId); g_trackerSupportGlobalInstanceId = id; } bool GlobalComWrappersForTrackerSupport::IsRegisteredInstance(INT64 id) { return g_trackerSupportGlobalInstanceId != ComWrappersNative::InvalidWrapperId && g_trackerSupportGlobalInstanceId == id; } bool GlobalComWrappersForTrackerSupport::TryGetOrCreateComInterfaceForObject( _In_ OBJECTREF instance, _Outptr_ void** wrapperRaw) { CONTRACTL { THROWS; MODE_COOPERATIVE; } CONTRACTL_END; if (g_trackerSupportGlobalInstanceId == ComWrappersNative::InvalidWrapperId) return false; // Passing NULL as the ComWrappers implementation indicates using the globally registered instance return TryGetOrCreateComInterfaceForObjectInternal( NULL, g_trackerSupportGlobalInstanceId, instance, CreateComInterfaceFlags::CreateComInterfaceFlags_TrackerSupport, ComWrappersScenario::TrackerSupportGlobalInstance, wrapperRaw); } bool GlobalComWrappersForTrackerSupport::TryGetOrCreateObjectForComInstance( _In_ IUnknown* externalComObject, _Out_ OBJECTREF* objRef) { CONTRACTL { THROWS; MODE_COOPERATIVE; } CONTRACTL_END; if (g_trackerSupportGlobalInstanceId == ComWrappersNative::InvalidWrapperId) return false; // Determine the true identity of the object SafeComHolder identity; { GCX_PREEMP(); HRESULT hr = externalComObject->QueryInterface(IID_IUnknown, &identity); _ASSERTE(hr == S_OK); } // Passing NULL as the ComWrappers implementation indicates using the globally registered instance return TryGetOrCreateObjectForComInstanceInternal( NULL /*comWrappersImpl*/, g_trackerSupportGlobalInstanceId, identity, NULL, CreateObjectFlags::CreateObjectFlags_TrackerObject, ComWrappersScenario::TrackerSupportGlobalInstance, NULL /*wrapperMaybe*/, objRef); } IUnknown* ComWrappersNative::GetIdentityForObject(_In_ OBJECTREF* objectPROTECTED, _In_ REFIID riid, _Out_ INT64* wrapperId, _Out_ bool* isAggregated) { CONTRACTL { NOTHROW; GC_TRIGGERS; MODE_COOPERATIVE; PRECONDITION(CheckPointer(objectPROTECTED)); PRECONDITION(CheckPointer(wrapperId)); } CONTRACTL_END; ASSERT_PROTECTED(objectPROTECTED); *wrapperId = ComWrappersNative::InvalidWrapperId; SyncBlock* syncBlock = (*objectPROTECTED)->PassiveGetSyncBlock(); if (syncBlock == nullptr) { return nullptr; } InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfoNoCreate(); if (interopInfo == nullptr) { return nullptr; } void* contextMaybe; if (interopInfo->TryGetExternalComObjectContext(&contextMaybe)) { ExternalObjectContext* context = reinterpret_cast(contextMaybe); *wrapperId = context->WrapperId; *isAggregated = context->IsSet(ExternalObjectContext::Flags_Aggregated); IUnknown* identity = reinterpret_cast(context->Identity); GCX_PREEMP(); IUnknown* result; if (SUCCEEDED(identity->QueryInterface(riid, (void**)&result))) { return result; } } return nullptr; } namespace { struct CallbackContext { bool HasWrapper; bool IsRooted; }; bool IsWrapperRootedCallback(_In_ void* mocw, _In_ void* cxtRaw) { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; PRECONDITION(mocw != NULL); PRECONDITION(cxtRaw != NULL); } CONTRACTL_END; auto cxt = static_cast(cxtRaw); cxt->HasWrapper = true; IUnknown* wrapper = static_cast(mocw); cxt->IsRooted = (InteropLib::Com::IsWrapperRooted(wrapper) == S_OK); // If we find a single rooted wrapper then the managed object // is considered rooted and we can stop enumerating. if (cxt->IsRooted) return false; return true; } } bool ComWrappersNative::HasManagedObjectComWrapper(_In_ OBJECTREF object, _Out_ bool* isRooted) { CONTRACTL { NOTHROW; GC_NOTRIGGER; PRECONDITION(CheckPointer(isRooted)); } CONTRACTL_END; *isRooted = false; SyncBlock* syncBlock = object->PassiveGetSyncBlock(); if (syncBlock == nullptr) return false; InteropSyncBlockInfo* interopInfo = syncBlock->GetInteropInfoNoCreate(); if (interopInfo == nullptr) return false; CallbackContext cxt{}; interopInfo->EnumManagedObjectComWrappers(&IsWrapperRootedCallback, &cxt); *isRooted = cxt.IsRooted; return cxt.HasWrapper; } void ComWrappersNative::OnFullGCStarted() { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; // 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(); } } void ComWrappersNative::OnFullGCFinished() { CONTRACTL { NOTHROW; GC_NOTRIGGER; } CONTRACTL_END; ExtObjCxtCache* cache = ExtObjCxtCache::GetInstanceNoThrow(); if (cache != NULL) { (void)InteropLib::Com::EndExternalObjectReferenceTracking(); STRESS_LOG0(LF_INTEROP, LL_INFO10000, "End Reference Tracking\n"); } } void ComWrappersNative::AfterRefCountedHandleCallbacks() { CONTRACTL { NOTHROW; GC_NOTRIGGER; MODE_ANY; } CONTRACTL_END; ExtObjCxtCache* cache = ExtObjCxtCache::GetInstanceNoThrow(); if (cache != NULL) cache->DetachNotPromotedEOCs(); } #endif // FEATURE_COMWRAPPERS