mirror of
https://github.com/ZuneDev/dotnet-runtime.git
synced 2026-07-27 13:22:01 -07:00
2074 lines
66 KiB
C++
2074 lines
66 KiB
C++
// 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 <interoplibimports.h>
|
|
|
|
#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<ExternalObjectContext*>(Result.Context);
|
|
}
|
|
ExternalObjectContext* DetachContext()
|
|
{
|
|
ExternalObjectContext* t = GetContext();
|
|
Result.Context = NULL;
|
|
return t;
|
|
}
|
|
};
|
|
|
|
using ExtObjCxtRefCache = RCWRefCache;
|
|
|
|
class ExtObjCxtCache
|
|
{
|
|
static Volatile<ExtObjCxtCache*> 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<ExternalObjectContext *>
|
|
{
|
|
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<Traits>::element_t;
|
|
using Iterator = SHash<Traits>::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<Traits> _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<ExternalObjectContext*> 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*> 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<IUnknown> 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<IUnknown*>(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<ExternalObjectContext*>(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<IUnknown*>(ext);
|
|
IUnknown* inner = reinterpret_cast<IUnknown*>(innerMaybe);
|
|
|
|
// Determine the true identity and inner of the object
|
|
SafeComHolder<IUnknown> 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<ExternalObjectContext*>(contextMaybe);
|
|
IUnknown* identity = reinterpret_cast<IUnknown*>(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<IUnknown> 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<ExternalObjectContext*>(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<ExternalObjectContext*>(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<IUnknown> 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<IUnknown> 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<ExternalObjectContext*>(contextMaybe);
|
|
*wrapperId = context->WrapperId;
|
|
*isAggregated = context->IsSet(ExternalObjectContext::Flags_Aggregated);
|
|
|
|
IUnknown* identity = reinterpret_cast<IUnknown*>(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<CallbackContext*>(cxtRaw);
|
|
cxt->HasWrapper = true;
|
|
|
|
IUnknown* wrapper = static_cast<IUnknown*>(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
|