// ITUnk.cpp -- Implementation for our base level IUnknown interface #include "stdafx.h" CITCriticalSection CITUnknown::s_csUnk; CITUnknown *CITUnknown::s_pitunkActive; // = NULL; void CITUnknown::CloseActiveObjects() { while (s_pitunkActive) { CITUnknown *pitNext = s_pitunkActive; for (; pitNext; pitNext = pitNext->m_pitunkNext) if (!(pitNext->IsSecondary())) break; RonM_ASSERT(pitNext); if (!pitNext) break; while (pitNext->Release() > 0); } } #ifdef _DEBUG BOOL CITUnknown::s_fCSInitialed; // = FALSE; void CITUnknown::OpenReferee(void) { // RonM_ASSERT(!s_fCSInitialed); if (s_fCSInitialed) // We can be called repeatedly because LoadLibrary return; // causes a Process_Attach! s_fCSInitialed= TRUE; } void CITUnknown::CloseReferee(void) { RonM_ASSERT(s_fCSInitialed); // BugBug: Can UnloadLibrary cause a Process_Detach? s_fCSInitialed = FALSE; } #endif // _DEBUG CITUnknown::CITUnknown(const IID *paIID, UINT cIID, IUnknown **papIFace) { m_paIID = paIID; m_cIID = cIID; m_papIFace = papIFace; m_pIFace = NULL; CommonInitialing(); } CITUnknown::CITUnknown(const IID *paIID, UINT cIID, IUnknown *pIFace) { m_paIID = paIID; m_cIID = cIID; m_pIFace = pIFace; m_papIFace = NULL; CommonInitialing(); } #if 0 CITUnknown::CITUnknown() { m_paIID = NULL; m_cIID = 0; m_pIFace = NULL; m_papIFace = NULL; CommonInitialing(); } #endif // 0 void CITUnknown::Uncouple() { CSyncWith sw(s_csUnk); #ifdef _DEBUG BOOL fFoundThis = FALSE; CITUnknown *pITUnkNext = s_pitunkActive; for (;pITUnkNext; pITUnkNext = pITUnkNext->m_pitunkNext) { if (pITUnkNext == this) { RonM_ASSERT(!fFoundThis); fFoundThis = TRUE; } } RonM_ASSERT(fFoundThis); #endif // _DEBUG if (m_pitunkPrev) m_pitunkPrev->m_pitunkNext = m_pitunkNext; else s_pitunkActive = m_pitunkNext; if (m_pitunkNext) m_pitunkNext->m_pitunkPrev = m_pitunkPrev; } void CITUnknown::MoveInFrontOf(CITUnknown *pITUnk) { if (!pITUnk) pITUnk = s_pitunkActive; CSyncWith sw(s_csUnk); RonM_ASSERT(pITUnk); RonM_ASSERT(pITUnk != this); #ifdef _DEBUG BOOL fFoundThis = FALSE; BOOL fFoundThat = FALSE; CITUnknown *pITUnkNext = s_pitunkActive; for (;pITUnkNext; pITUnkNext = pITUnkNext->m_pitunkNext) { if (pITUnkNext == this) { RonM_ASSERT(!fFoundThis); fFoundThis = TRUE; } if (pITUnkNext == pITUnk) { RonM_ASSERT(!fFoundThat); fFoundThat = TRUE; } } RonM_ASSERT(fFoundThis && fFoundThat); #endif // _DEBUG Uncouple(); m_pitunkNext = pITUnk; m_pitunkPrev = pITUnk->m_pitunkPrev; pITUnk->m_pitunkPrev = this; if (m_pitunkPrev) m_pitunkPrev->m_pitunkNext = this; else s_pitunkActive = this; } void CITUnknown::CommonInitialing() { m_cRefs = 0; m_fSecondary = FALSE; m_fAlreadyDead = FALSE; pDLLServerState->ObjectAdded(); // #ifdef _DEBUG { CSyncWith sw(s_csUnk); m_pitunkPrev = NULL; m_pitunkNext = s_pitunkActive; if (m_pitunkNext) m_pitunkNext->m_pitunkPrev = this; s_pitunkActive = this; } // #endif // _DEBUG #if 0 // #ifdef _DEBUG // The loop below inserts this object at the head of the s_pitunkActive list. // This works correctly when multiple threads are simultaneously altering // the list. for (;;) { // Note! InterlockedCompareExchange is an NT-only API. if (PVOID(m_pitunkNext) == InterlockedCompareExchange ((PVOID *)&s_pitunkActive, PVOID(this), PVOID(m_pitunkNext) ) ) break; } #endif // _DEBUG } STDMETHODIMP_(ULONG) CITUnknown::AddRef (void) { return InterlockedIncrement(&m_cRefs); } STDMETHODIMP_(ULONG) CITUnknown::Release(void) { /* Each COM object is reference counted and their destructor is invoked when the reference count goes from 1 to zero. There are several often forgotten assumptions which go along with reference counting. For example each increment of the count is supposed to correspond to a pointer variable somewhere which points to the object. So whenever you call Release, you're also promising that the corresponding pointer variable no longer points to the released object. If the calling code isn't disciplined about nulling out pointers when they're released, we can get subsequent calls to release an object that's already dead. One way to avoid that problem is to use the DetachRef define (See ITUnk.h) to disconnect pointers for objects derived from PCImpITUnknown. However that still leaves the possibility of concurrent threads simultaneously racing to detach the same pointer variable. In practice the way to deal with that problem is to put the call to Release in one place in the calling code and to protect it with a critical section. The other design issue with reference counts is circular references. For example if you create object A with one external reference and its constructor creates object B in turn which refers to A, we have two references -- one reference to A from the outside environment and one internal circular reference from A to B to A. If you let a circular reference come into being and take no further action, your object can never be destroyed. The way that we solve this problem in the ITSS code is to have the containing object ("A" in the example above) follow this discipline: 1. In its contructor after the circular references have been created, it must first call AddRef to account for the external reference and then call Release once for each circular reference. 2. In its destructor it must first call AddRef for each circular reference. The code below makes that discipline work and also detects undisciplined situations. In particular it enforces the rule that an object is destroyed only once no matter how many times its reference count goes from one to zero. */ RonM_ASSERT(m_cRefs > 0); LONG cRefs= InterlockedDecrement(&m_cRefs); if (!cRefs) { RonM_ASSERT(!m_fAlreadyDead); if (!m_fAlreadyDead) // No double jeopardy for zombies! { m_fAlreadyDead = TRUE; // To keep objects from dying more than once. delete this; } } return cRefs; } STDMETHODIMP CITUnknown::QueryInterface(REFIID riid, PPVOID ppv) { IUnknown *pUnkResult = NULL; if (riid == IID_IUnknown) pUnkResult = this; else { RonM_ASSERT(!m_pIFace || !m_papIFace); // Can't use both... RonM_ASSERT( m_pIFace || m_papIFace); // Must have at least one. IUnknown **ppUnk = m_papIFace; const IID *pIID = m_paIID; for (UINT c = m_cIID; c--; pIID++) { IUnknown *pUnk = ppUnk? *ppUnk++ : m_pIFace; if (riid == *pIID) { pUnkResult = pUnk; break; } } if (!pUnkResult) return E_NOINTERFACE; } pUnkResult->AddRef(); *ppv = pUnkResult; return NOERROR; } CITUnknown::~CITUnknown() { pDLLServerState->ObjectReleased(); // #ifdef _DEBUG // RonM_ASSERT(s_fCSInitialed); Uncouple(); // #endif // _DEBUG #if 0 // #ifdef _DEBUG // The loop below removes this object from the s_pitunkActive list. // This works correctly when multiple threads are simultaneously altering // the list. for (;;) { // We always make at least two passes through the list. // First we scan to locate our object and remove it from the list. // Then we scan the list again to verify that our object has been // removed. The verification scan is necessary given that other // threads may be modifying the list at the same time. BOOL fFound = FALSE; for (CITUnknown **ppitunk= &s_pitunkActive; *ppitunk; ppitunk= &((*ppitunk)->m_pitunkNext) ) { if (this != *ppitunk) continue; fFound= TRUE; // Note! InterlockedCompareExchange is an NT-only API. InterlockedCompareExchange((PVOID *) ppitunk, PVOID(m_pitunkNext), PVOID(this)); break; } if (!fFound) break; } #endif // _DEBUG } HRESULT CITUnknown::FinishSetup(HRESULT hr, CITUnknown *pUnk, REFIID riid, PPVOID ppv) { if (SUCCEEDED(hr)) { pUnk->AddRef(); CImpITUnknown *pImp; hr = pUnk->QueryInterface(riid, (PPVOID) &pImp); if (SUCCEEDED(hr)) { if (riid != IID_IUnknown && pImp->HasControllingUnknown()) { hr = CLASS_E_NOAGGREGATION; pImp->Release(); pImp = NULL; } } *ppv = (PVOID) pImp; pUnk->Release(); } else if (pUnk) delete pUnk; return hr; } CImpITUnknown::CImpITUnknown(CITUnknown *pBackObj, IUnknown *punkOuter) { m_fControlled = punkOuter != NULL; m_pUnkOuter = m_fControlled? punkOuter : pBackObj; m_pBackObj = pBackObj; m_fActive = FALSE; m_pImpITUnknownNext = NULL; m_ppImpITUnknownList = NULL; } CImpITUnknown::~CImpITUnknown() { if (m_fActive) MarkInactive(); } STDMETHODIMP_(ULONG) CImpITUnknown::AddRef (void) { return m_pUnkOuter->AddRef(); } void CImpITUnknown::DetachReference(PCImpITUnknown &pITUnk) { PCImpITUnknown pITUnkTmp = pITUnk; pITUnk = NULL; pITUnkTmp->Release(); } STDMETHODIMP_(ULONG) CImpITUnknown::Release(void) { return m_pUnkOuter->Release(); } STDMETHODIMP CImpITUnknown::QueryInterface(REFIID riid, PPVOID ppv) { return m_pUnkOuter->QueryInterface(riid, ppv); } void CImpITUnknown::MarkActive(PCImpITUnknown &pListStart) { RonM_ASSERT(!m_fActive); RonM_ASSERT(!m_ppImpITUnknownList); m_ppImpITUnknownList = &pListStart; m_pImpITUnknownNext = pListStart; pListStart = this; m_fActive = TRUE; } void CImpITUnknown::MarkInactive() { RonM_ASSERT(m_fActive); RonM_ASSERT(m_ppImpITUnknownList); PCImpITUnknown *ppImpITUnk = m_ppImpITUnknownList; RonM_ASSERT(*ppImpITUnk); for (;;) { CImpITUnknown *pImpITUnk = *ppImpITUnk; if (pImpITUnk == this) { *ppImpITUnk = m_pImpITUnknownNext; m_ppImpITUnknownList = NULL; m_fActive = FALSE; return; } RonM_ASSERT(pImpITUnk->m_pImpITUnknownNext); ppImpITUnk = &(pImpITUnk->m_pImpITUnknownNext); } RonM_ASSERT(FALSE); // Should have found this object in the chain. }