diff options
author | Aaron Robinson <arobins@microsoft.com> | 2020-12-10 02:17:18 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-12-10 02:17:18 +0300 |
commit | a7c31798739670fae93e962d1158439a83a118cf (patch) | |
tree | f4e9963506b89df416dd985f4fc2c5112e9b1c1a /src/coreclr/interop | |
parent | 84c817b89b0ab23f03435906cbcfeb2ebc852762 (diff) |
Fix ComWrappers interaction with the IReferenceTracker interface (#45624)
* Fix ComWrappers' leak in aggregation scenario
Convert Managed Object Wrapper (MOW) GC Handle from HNDTYPE_STRONG
to HNDTYPE_REFCOUNTED.
Add new CreateObjectFlags value to indicate aggregation during
CreateObject scenario. This isn't reflected in the managed
.NET 5 API surface area.
In the ReferenceTracker scenario the ref count may never reach 0 so
the MOW destructor needs to handle that case. The previous expectation
for a null in the destructor was based on the STRONG handle logic.
During aggregation scenarios involving ReferenceTracker, ownership
of the inner is now the responsibility of the runtime.
* ComWrappers tests
Diffstat (limited to 'src/coreclr/interop')
-rw-r--r-- | src/coreclr/interop/comwrappers.cpp | 215 | ||||
-rw-r--r-- | src/coreclr/interop/comwrappers.hpp | 18 | ||||
-rw-r--r-- | src/coreclr/interop/inc/interoplib.h | 18 | ||||
-rw-r--r-- | src/coreclr/interop/interoplib.cpp | 92 | ||||
-rw-r--r-- | src/coreclr/interop/trackerobjectmanager.cpp | 3 |
5 files changed, 220 insertions, 126 deletions
diff --git a/src/coreclr/interop/comwrappers.cpp b/src/coreclr/interop/comwrappers.cpp index a7d4db77698..f39472778cd 100644 --- a/src/coreclr/interop/comwrappers.cpp +++ b/src/coreclr/interop/comwrappers.cpp @@ -223,12 +223,10 @@ namespace namespace { const int32_t TrackerRefShift = 32; - const ULONGLONG TrackerRefCounter = ULONGLONG{ 1 } << TrackerRefShift; - const ULONGLONG ComRefCounter = ULONGLONG{ 1 }; - const ULONGLONG TrackerRefZero = 0x0000000080000000; + const ULONGLONG TrackerRefCounter = ULONGLONG{ 1 } << TrackerRefShift; + const ULONGLONG DestroySentinel = 0x0000000080000000; const ULONGLONG TrackerRefCountMask = 0xffffffff00000000; const ULONGLONG ComRefCountMask = 0x000000007fffffff; - const ULONGLONG RefCountMask = 0xffffffff7fffffff; constexpr ULONG GetTrackerCount(_In_ ULONGLONG c) { @@ -419,11 +417,29 @@ HRESULT ManagedObjectWrapper::Create( void ManagedObjectWrapper::Destroy(_In_ ManagedObjectWrapper* wrapper) { _ASSERTE(wrapper != nullptr); + _ASSERTE(GetComCount(wrapper->_refCount) == 0); - // Manually trigger the destructor since placement - // new was used to allocate the object. - wrapper->~ManagedObjectWrapper(); - InteropLibImports::MemFree(wrapper, AllocScenario::ManagedObjectWrapper); + // Attempt to set the destroyed bit. + LONGLONG refCount; + LONGLONG prev; + do + { + prev = wrapper->_refCount; + refCount = prev | DestroySentinel; + } while (::InterlockedCompareExchange64(&wrapper->_refCount, refCount, prev) != prev); + + // The destroy sentinel represents the bit that indicates the wrapper + // should be destroyed. Since the reference count field (64-bit) holds + // two counters we rely on the singular sentinal value - no other bits + // in the 64-bit counter are set. If there are outstanding bits set it + // indicates there are still outstanding references. + if (refCount == DestroySentinel) + { + // Manually trigger the destructor since placement + // new was used to allocate the object. + wrapper->~ManagedObjectWrapper(); + InteropLibImports::MemFree(wrapper, AllocScenario::ManagedObjectWrapper); + } } ManagedObjectWrapper::ManagedObjectWrapper( @@ -449,48 +465,9 @@ ManagedObjectWrapper::ManagedObjectWrapper( ManagedObjectWrapper::~ManagedObjectWrapper() { - // If the target isn't null, then a managed object - // is going to leak. - _ASSERTE(Target == nullptr); -} - -ULONGLONG ManagedObjectWrapper::UniversalRelease(_In_ ULONGLONG dec) -{ - OBJECTHANDLE local = Target; - - LONGLONG refCount; - if (dec == ComRefCounter) - { - _ASSERTE(dec == 1); - refCount = ::InterlockedDecrement64(&_refCount); - } - else - { - _ASSERTE(dec == TrackerRefCounter); - LONGLONG prev; - do - { - prev = _refCount; - refCount = prev - dec; - } while (::InterlockedCompareExchange64(&_refCount, refCount, prev) != prev); - } - - // It is possible that a target wasn't set during an - // attempt to reactive the wrapper. - if ((RefCountMask & refCount) == 0 && local != nullptr) - { - _ASSERTE(!IsSet(CreateComInterfaceFlagsEx::IsPegged)); - _ASSERTE(refCount == TrackerRefZero || refCount == 0); - - // Attempt to reset the target if its current value is the same. - // It is possible the wrapper is in the middle of being reactivated. - (void)TrySetObjectHandle(nullptr, local); - - // Tell the runtime to delete the managed object instance handle. - InteropLibImports::DeleteObjectInstanceHandle(local); - } - - return refCount; + // If the target isn't null, then release it. + if (Target != nullptr) + InteropLibImports::DeleteObjectInstanceHandle(Target); } void* ManagedObjectWrapper::AsRuntimeDefined(_In_ REFIID riid) @@ -551,16 +528,18 @@ void ManagedObjectWrapper::ResetFlag(_In_ CreateComInterfaceFlagsEx flag) ::InterlockedAnd((LONG*)&_flags, resetMask); } -ULONG ManagedObjectWrapper::IsActiveAddRef() +bool ManagedObjectWrapper::IsRooted() const { - ULONG count = GetComCount(::InterlockedIncrement64(&_refCount)); - if (count == 1) + bool rooted = GetComCount(_refCount) > 0; + if (!rooted) { - // Ensure the current target is null. - ::InterlockedExchangePointer(&Target, nullptr); + // Only consider tracker ref count to be a "strong" ref count if it is pegged and alive. + rooted = (GetTrackerCount(_refCount) > 0) + && (IsSet(CreateComInterfaceFlagsEx::IsPegged) + || InteropLibImports::GetGlobalPeggingState()); } - return count; + return rooted; } ULONG ManagedObjectWrapper::AddRefFromReferenceTracker() @@ -578,7 +557,29 @@ ULONG ManagedObjectWrapper::AddRefFromReferenceTracker() ULONG ManagedObjectWrapper::ReleaseFromReferenceTracker() { - return GetTrackerCount(UniversalRelease(TrackerRefCounter)); + if (GetTrackerCount(_refCount) == 0) + { + _ASSERTE(!"Over release of MOW - ReferenceTracker"); + return (ULONG)-1; + } + + LONGLONG refCount; + LONGLONG prev; + do + { + prev = _refCount; + refCount = prev - TrackerRefCounter; + } while (::InterlockedCompareExchange64(&_refCount, refCount, prev) != prev); + + // If we observe the destroy sentinel, then this release + // must destroy the wrapper. + if (refCount == DestroySentinel) + { + _ASSERTE(!IsSet(CreateComInterfaceFlagsEx::IsPegged)); + Destroy(this); + } + + return GetTrackerCount(refCount); } HRESULT ManagedObjectWrapper::Peg() @@ -652,12 +653,20 @@ HRESULT ManagedObjectWrapper::QueryInterface( ULONG ManagedObjectWrapper::AddRef(void) { + _ASSERTE((_refCount & DestroySentinel) == 0); return GetComCount(::InterlockedIncrement64(&_refCount)); } ULONG ManagedObjectWrapper::Release(void) { - return GetComCount(UniversalRelease(ComRefCounter)); + _ASSERTE((_refCount & DestroySentinel) == 0); + if (GetComCount(_refCount) == 0) + { + _ASSERTE(!"Over release of MOW - COM"); + return (ULONG)-1; + } + + return GetComCount(::InterlockedDecrement64(&_refCount)); } namespace @@ -684,12 +693,19 @@ NativeObjectWrapperContext* NativeObjectWrapperContext::MapFromRuntimeContext(_I HRESULT NativeObjectWrapperContext::Create( _In_ IUnknown* external, + _In_opt_ IUnknown* inner, _In_ InteropLib::Com::CreateObjectFlags flags, _In_ size_t runtimeContextSize, _Outptr_ NativeObjectWrapperContext** context) { _ASSERTE(external != nullptr && context != nullptr); + // Aggregated inners are only currently supported for Aggregated + // scenarios involving IReferenceTracker. + _ASSERTE(inner == nullptr + || ((flags & InteropLib::Com::CreateObjectFlags_TrackerObject) + && (flags & InteropLib::Com::CreateObjectFlags_Aggregated))); + HRESULT hr; ComHolder<IReferenceTracker> trackerObject; @@ -710,7 +726,7 @@ HRESULT NativeObjectWrapperContext::Create( // Contract specifically requires zeroing out runtime context. ::memset(runtimeContext, 0, runtimeContextSize); - NativeObjectWrapperContext* contextLocal = new (cxtMem) NativeObjectWrapperContext{ runtimeContext, trackerObject }; + NativeObjectWrapperContext* contextLocal = new (cxtMem) NativeObjectWrapperContext{ runtimeContext, trackerObject, inner }; if (trackerObject != nullptr) { @@ -722,6 +738,13 @@ HRESULT NativeObjectWrapperContext::Create( Destroy(contextLocal); return hr; } + + // Aggregation with a tracker object must be "cleaned up". + if (flags & InteropLib::Com::CreateObjectFlags_Aggregated) + { + _ASSERTE(inner != nullptr); + contextLocal->HandleReferenceTrackerAggregation(); + } } *context = contextLocal; @@ -732,21 +755,48 @@ void NativeObjectWrapperContext::Destroy(_In_ NativeObjectWrapperContext* wrappe { _ASSERTE(wrapper != nullptr); + // Check if the tracker object manager should be informed prior to being destroyed. + IReferenceTracker* trackerMaybe = wrapper->GetReferenceTracker(); + if (trackerMaybe != nullptr) + { + // We only call this during a GC so ignore the failure as + // there is no way we can handle it at this point. + HRESULT hr = TrackerObjectManager::BeforeWrapperDestroyed(trackerMaybe); + _ASSERTE(SUCCEEDED(hr)); + (void)hr; + } + // Manually trigger the destructor since placement // new was used to allocate the object. wrapper->~NativeObjectWrapperContext(); InteropLibImports::MemFree(wrapper, AllocScenario::NativeObjectWrapper); } -NativeObjectWrapperContext::NativeObjectWrapperContext(_In_ void* runtimeContext, _In_opt_ IReferenceTracker* trackerObject) +namespace +{ + // State ownership mechanism. + enum : int + { + TrackerObjectState_NotSet = 0, + TrackerObjectState_SetNoRelease = 1, + TrackerObjectState_SetForRelease = 2, + }; +} + +NativeObjectWrapperContext::NativeObjectWrapperContext( + _In_ void* runtimeContext, + _In_opt_ IReferenceTracker* trackerObject, + _In_opt_ IUnknown* nativeObjectAsInner) : _trackerObject{ trackerObject } , _runtimeContext{ runtimeContext } - , _isValidTracker{ (trackerObject != nullptr ? TRUE : FALSE) } + , _trackerObjectDisconnected{ FALSE } + , _trackerObjectState{ (trackerObject == nullptr ? TrackerObjectState_NotSet : TrackerObjectState_SetForRelease) } + , _nativeObjectAsInner{ nativeObjectAsInner } #ifdef _DEBUG , _sentinel{ LiveContextSentinel } #endif { - if (_isValidTracker == TRUE) + if (_trackerObjectState == TrackerObjectState_SetForRelease) (void)_trackerObject->AddRef(); } @@ -754,6 +804,10 @@ NativeObjectWrapperContext::~NativeObjectWrapperContext() { DisconnectTracker(); + // If the inner was supplied, we need to release our reference. + if (_nativeObjectAsInner != nullptr) + (void)_nativeObjectAsInner->Release(); + #ifdef _DEBUG _sentinel = DeadContextSentinel; #endif @@ -766,12 +820,43 @@ void* NativeObjectWrapperContext::GetRuntimeContext() const noexcept IReferenceTracker* NativeObjectWrapperContext::GetReferenceTracker() const noexcept { - return ((_isValidTracker == TRUE) ? _trackerObject : nullptr); + return ((_trackerObjectState == TrackerObjectState_NotSet) ? nullptr : _trackerObject); } +// See TrackerObjectManager::AfterWrapperCreated() for AddRefFromTrackerSource() usage. +// See NativeObjectWrapperContext::HandleReferenceTrackerAggregation() for additional +// cleanup logistics. void NativeObjectWrapperContext::DisconnectTracker() noexcept { - // Attempt to disconnect from the tracker. - if (TRUE == ::InterlockedCompareExchange((LONG*)&_isValidTracker, FALSE, TRUE)) + // Return if already disconnected or the tracker isn't set. + if (FALSE != ::InterlockedCompareExchange((LONG*)&_trackerObjectDisconnected, TRUE, FALSE) + || _trackerObjectState == TrackerObjectState_NotSet) + { + return; + } + + _ASSERTE(_trackerObject != nullptr); + + // Always release the tracker source during a disconnect. + // This to account for the implied IUnknown ownership by the runtime. + (void)_trackerObject->ReleaseFromTrackerSource(); // IUnknown + + // Disconnect from the tracker. + if (_trackerObjectState == TrackerObjectState_SetForRelease) + { + (void)_trackerObject->ReleaseFromTrackerSource(); // IReferenceTracker (void)_trackerObject->Release(); + } +} + +void NativeObjectWrapperContext::HandleReferenceTrackerAggregation() noexcept +{ + _ASSERTE(_trackerObjectState == TrackerObjectState_SetForRelease && _trackerObject != nullptr); + + // Aggregation with an IReferenceTracker instance creates an extra AddRef() + // on the outer (e.g. MOW) so we clean up that issue here. + _trackerObjectState = TrackerObjectState_SetNoRelease; + + (void)_trackerObject->ReleaseFromTrackerSource(); // IReferenceTracker + (void)_trackerObject->Release(); } diff --git a/src/coreclr/interop/comwrappers.hpp b/src/coreclr/interop/comwrappers.hpp index 3ae91d8a88c..e4d849a5625 100644 --- a/src/coreclr/interop/comwrappers.hpp +++ b/src/coreclr/interop/comwrappers.hpp @@ -82,10 +82,6 @@ private: ~ManagedObjectWrapper(); - // Represents a single implementation of how to release - // the wrapper. Supplied with a decrementing value. - ULONGLONG UniversalRelease(_In_ ULONGLONG dec); - // Query the runtime defined tables. void* AsRuntimeDefined(_In_ REFIID riid); @@ -102,8 +98,8 @@ public: void SetFlag(_In_ CreateComInterfaceFlagsEx flag); void ResetFlag(_In_ CreateComInterfaceFlagsEx flag); - // Used while validating wrapper is active. - ULONG IsActiveAddRef(); + // Indicate if the wrapper should be considered a GC root. + bool IsRooted() const; public: // IReferenceTrackerTarget ULONG AddRefFromReferenceTracker(); @@ -139,7 +135,9 @@ class NativeObjectWrapperContext { IReferenceTracker* _trackerObject; void* _runtimeContext; - Volatile<BOOL> _isValidTracker; + Volatile<BOOL> _trackerObjectDisconnected; + int _trackerObjectState; + IUnknown* _nativeObjectAsInner; #ifdef _DEBUG size_t _sentinel; @@ -151,6 +149,7 @@ public: // static // Create a NativeObjectWrapperContext instance static HRESULT NativeObjectWrapperContext::Create( _In_ IUnknown* external, + _In_opt_ IUnknown* nativeObjectAsInner, _In_ InteropLib::Com::CreateObjectFlags flags, _In_ size_t runtimeContextSize, _Outptr_ NativeObjectWrapperContext** context); @@ -159,7 +158,7 @@ public: // static static void Destroy(_In_ NativeObjectWrapperContext* wrapper); private: - NativeObjectWrapperContext(_In_ void* runtimeContext, _In_opt_ IReferenceTracker* trackerObject); + NativeObjectWrapperContext(_In_ void* runtimeContext, _In_opt_ IReferenceTracker* trackerObject, _In_opt_ IUnknown* nativeObjectAsInner); ~NativeObjectWrapperContext(); public: @@ -171,6 +170,9 @@ public: // Disconnect reference tracker instance. void DisconnectTracker() noexcept; + +private: + void HandleReferenceTrackerAggregation() noexcept; }; // Manage native object wrappers that support IReferenceTracker. diff --git a/src/coreclr/interop/inc/interoplib.h b/src/coreclr/interop/inc/interoplib.h index a1f32b99ecd..39ceadfbb80 100644 --- a/src/coreclr/interop/inc/interoplib.h +++ b/src/coreclr/interop/inc/interoplib.h @@ -38,11 +38,8 @@ namespace InteropLib // Destroy the supplied wrapper void DestroyWrapperForObject(_In_ void* wrapper) noexcept; - // Check if a wrapper is active. - HRESULT IsActiveWrapper(_In_ IUnknown* wrapper) noexcept; - - // Reactivate the supplied wrapper. - HRESULT ReactivateWrapper(_In_ IUnknown* wrapper, _In_ InteropLib::OBJECTHANDLE handle) noexcept; + // Check if a wrapper is considered a GC root. + HRESULT IsWrapperRooted(_In_ IUnknown* wrapper) noexcept; // Get the object for the supplied wrapper HRESULT GetObjectForWrapper(_In_ IUnknown* wrapper, _Outptr_result_maybenull_ OBJECTHANDLE* object) noexcept; @@ -58,6 +55,9 @@ namespace InteropLib // See https://docs.microsoft.com/windows/win32/api/windows.ui.xaml.hosting.referencetracker/ // for details. bool FromTrackerRuntime; + + // The supplied external object is wrapping a managed object. + bool ManagedObjectWrapper; }; // See CreateObjectFlags in ComWrappers.cs @@ -66,13 +66,21 @@ namespace InteropLib CreateObjectFlags_None = 0, CreateObjectFlags_TrackerObject = 1, CreateObjectFlags_UniqueInstance = 2, + CreateObjectFlags_Aggregated = 4, }; + // Get the true identity for the supplied IUnknown. + HRESULT GetIdentityForCreateWrapperForExternal( + _In_ IUnknown* external, + _In_ enum CreateObjectFlags flags, + _Outptr_ IUnknown** identity) noexcept; + // Allocate a wrapper context for an external object. // The runtime supplies the external object, flags, and a memory // request in order to bring the object into the runtime. HRESULT CreateWrapperForExternal( _In_ IUnknown* external, + _In_opt_ IUnknown* inner, _In_ enum CreateObjectFlags flags, _In_ size_t contextSize, _Out_ ExternalWrapperResult* result) noexcept; diff --git a/src/coreclr/interop/interoplib.cpp b/src/coreclr/interop/interoplib.cpp index f730817dea3..9aff8c2bb33 100644 --- a/src/coreclr/interop/interoplib.cpp +++ b/src/coreclr/interop/interoplib.cpp @@ -54,53 +54,26 @@ namespace InteropLib ManagedObjectWrapper::Destroy(wrapper); } - HRESULT IsActiveWrapper(_In_ IUnknown* wrapperMaybe) noexcept + HRESULT IsWrapperRooted(_In_ IUnknown* wrapperMaybe) noexcept { ManagedObjectWrapper* wrapper = ManagedObjectWrapper::MapFromIUnknown(wrapperMaybe); if (wrapper == nullptr) return E_INVALIDARG; - ULONG count = wrapper->IsActiveAddRef(); - if (count == 1 || wrapper->Target == nullptr) - { - // The wrapper isn't active. - (void)wrapper->Release(); - return S_FALSE; - } - - return S_OK; - } - - HRESULT ReactivateWrapper(_In_ IUnknown* wrapperMaybe, _In_ OBJECTHANDLE handle) noexcept - { - ManagedObjectWrapper* wrapper = ManagedObjectWrapper::MapFromIUnknown(wrapperMaybe); - if (wrapper == nullptr || handle == nullptr) - return E_INVALIDARG; - - // Take an AddRef() as an indication of ownership. - (void)wrapper->AddRef(); - - // If setting this object handle fails, then the race - // was lost and we will cleanup the handle. - if (!wrapper->TrySetObjectHandle(handle)) - InteropLibImports::DeleteObjectInstanceHandle(handle); - - return S_OK; + return wrapper->IsRooted() ? S_OK : S_FALSE; } HRESULT GetObjectForWrapper(_In_ IUnknown* wrapper, _Outptr_result_maybenull_ OBJECTHANDLE* object) noexcept { - if (object == nullptr) - return E_POINTER; - + _ASSERTE(wrapper != nullptr && object != nullptr); *object = nullptr; - HRESULT hr = IsActiveWrapper(wrapper); - if (hr != S_OK) - return hr; - + // Attempt to get the managed object wrapper. ManagedObjectWrapper *mow = ManagedObjectWrapper::MapFromIUnknown(wrapper); - _ASSERTE(mow != nullptr); + if (mow == nullptr) + return E_INVALIDARG; + + (void)mow->AddRef(); *object = mow->Target; return S_OK; @@ -125,8 +98,43 @@ namespace InteropLib return wrapper->IsSet(CreateComInterfaceFlagsEx::IsComActivated) ? S_OK : S_FALSE; } + HRESULT GetIdentityForCreateWrapperForExternal( + _In_ IUnknown* external, + _In_ enum CreateObjectFlags flags, + _Outptr_ IUnknown** identity) noexcept + { + _ASSERTE(external != nullptr && identity != nullptr); + + IUnknown* checkForIdentity = external; + + // Check if the flags indicate we are creating + // an object for an external IReferenceTracker instance + // that we are aggregating with. + bool refTrackerInnerScenario = (flags & CreateObjectFlags_TrackerObject) + && (flags & CreateObjectFlags_Aggregated); + + ComHolder<IReferenceTracker> trackerObject; + if (refTrackerInnerScenario) + { + // We are checking the supplied external value + // for IReferenceTracker since in .NET 5 this could + // actually be the inner and we want the true identity + // not the inner . This is a trick since the only way + // to get identity from an inner is through a non-IUnknown + // interface QI. Once we have the IReferenceTracker + // instance we can be sure the QI for IUnknown will really + // be the true identity. + HRESULT hr = external->QueryInterface(&trackerObject); + if (SUCCEEDED(hr)) + checkForIdentity = trackerObject.p; + } + + return checkForIdentity->QueryInterface(identity); + } + HRESULT CreateWrapperForExternal( _In_ IUnknown* external, + _In_opt_ IUnknown* inner, _In_ enum CreateObjectFlags flags, _In_ size_t contextSize, _Out_ ExternalWrapperResult* result) noexcept @@ -136,10 +144,11 @@ namespace InteropLib HRESULT hr; NativeObjectWrapperContext* wrapperContext; - RETURN_IF_FAILED(NativeObjectWrapperContext::Create(external, flags, contextSize, &wrapperContext)); + RETURN_IF_FAILED(NativeObjectWrapperContext::Create(external, inner, flags, contextSize, &wrapperContext)); result->Context = wrapperContext->GetRuntimeContext(); result->FromTrackerRuntime = (wrapperContext->GetReferenceTracker() != nullptr); + result->ManagedObjectWrapper = (ManagedObjectWrapper::MapFromIUnknown(external) != nullptr); return S_OK; } @@ -150,17 +159,6 @@ namespace InteropLib // A caller should not be destroying a context without knowing if the context is valid. _ASSERTE(context != nullptr); - // Check if the tracker object manager should be informed prior to being destroyed. - IReferenceTracker* trackerMaybe = context->GetReferenceTracker(); - if (trackerMaybe != nullptr) - { - // We only call this during a GC so ignore the failure as - // there is no way we can handle it at this point. - HRESULT hr = TrackerObjectManager::BeforeWrapperDestroyed(trackerMaybe); - _ASSERTE(SUCCEEDED(hr)); - (void)hr; - } - NativeObjectWrapperContext::Destroy(context); } diff --git a/src/coreclr/interop/trackerobjectmanager.cpp b/src/coreclr/interop/trackerobjectmanager.cpp index 91cc894c0ee..f205484d3b0 100644 --- a/src/coreclr/interop/trackerobjectmanager.cpp +++ b/src/coreclr/interop/trackerobjectmanager.cpp @@ -296,7 +296,8 @@ HRESULT TrackerObjectManager::AfterWrapperCreated(_In_ IReferenceTracker* obj) // Send out AddRefFromTrackerSource callbacks to notify tracker runtime we've done AddRef() // for certain interfaces. We should do this *after* we made a AddRef() because we should never // be in a state where report refs > actual refs - RETURN_IF_FAILED(obj->AddRefFromTrackerSource()); + RETURN_IF_FAILED(obj->AddRefFromTrackerSource()); // IUnknown + RETURN_IF_FAILED(obj->AddRefFromTrackerSource()); // IReferenceTracker return S_OK; } |