Я хочу поделиться с вами этой проблемой, с которой я сталкиваюсь. Короче говоря, у меня есть этот маленький код (только для целей тестирования):
int main ()
{
IXMLDOMDocument *pDoc(nullptr);
CoCreateInstance(CLSID_DOMDocument, nullptr, CLSCTX_ALL, IID_IXMLDOMDocument, reinterpret_cast<LPVOID*>(&pDoc));
DWORD d = pDoc->AddRef();
std::cout << "pDoc: add ptr=" << pDoc << " d=" << d << std::endl;
d = pDoc->Release();
std::cout << "pDoc: rel ptr=" << pDoc << " d=" << d << std::endl;
IUnknown *pUnk(nullptr);
pDoc->QueryInterface(IID_IUnknown, reinterpret_cast<LPVOID*>(&pUnk));
d = pUnk->AddRef();
std::cout << "pUnk: add ptr=" << pUnk << " d=" << d << std::endl;
d = pUnk->Release();
std::cout << "pUnk: rel ptr=" << pUnk << " d=" << d << std::endl;
/*Release objects*/
d = pUnk->Release();
std::cout << "pUnk: rel ptr=" << pUnk << " d=" << d << std::endl;
d = pDoc->Release();
std::cout << "pDoc: rel ptr=" << pDoc << " d=" << d << std::endl;
return 0;
}
Я ожидаю, что последние 2 cout
выведите 0 в качестве возвращенного количества, на их месте я вижу:
pDoc: add ptr=004A4628 d=2
pDoc: rel ptr=004A4628 d=1
pUnk: add ptr=004A3A10 d=4
pUnk: rel ptr=004A3A10 d=3
pUnk: rel ptr=004A3A10 d=2
pDoc: rel ptr=004A4628 d=0
Зачем QueryInterface
вернул мне IUnknown
какой внутренний счет начинается с 3?
Почему последний Release
метод IUnknown
объект не возвращается 0
как исключено?
Что мне может не хватать?
Согласно документации MDSN AddRef
а также Release
, возвращаемое значение предназначено для использования только в целях тестирования. Это может не быть точным отражением фактического количества ссылок; и, в частности, тестирование его против 0
не является гарантией того, что объект закончен.
Зачем
QueryInterface
вернул мнеIUnknown
какой внутренний счет начинается с 3?
pDoc
а также pUnk
по сути два способа доступа к одному объекту. Поскольку это один объект, это отражается в подсчете ссылок и объясняет, почему он не начинается с 1.
Но из этого объяснения можно ожидать, что счетчик ссылок будет начинаться с 2, а не с 3. Тот факт, что он начинается с 3, вероятно, вызван внутренним вспомогательным объектом, используемым DOMDocument
обращаться с IUnknown
интерфейс, где этот внутренний вспомогательный объект поддерживает дополнительную ссылку.
Почему последний
Release
методIUnknown
объект не возвращает 0 как исключено?
По той же причине: pDoc
а также pUnk
по сути один и тот же объект. Поскольку у вас все еще есть невыпущенная ссылка (доступная через pDoc
) в этот момент объект все еще жив.
Когда объект запрашивается для его IUnknown
В частности, COM ожидает, что один и тот же объект будет возвращаться каждый раз, чтобы обеспечить работу тестов идентичности (вы можете проверить, указывают ли два интерфейса на один и тот же объект в памяти, запросив оба интерфейса для IUnknown
а затем сравнить запрашиваемые указатели). Это указано в QueryInterface()
документация:
Для любого одного объекта, конкретный запрос для
IUnknown
Интерфейс на любом из интерфейсов объекта должен всегда возвращать одно и то же значение указателя. Это позволяет клиенту определить, указывают ли два указателя на один и тот же компонент, вызываяQueryInterface
сIID_IUnknown
и сравнивая результаты. Это конкретно не тот случай, когда запросы для интерфейсов, кромеIUnknown
(даже тот же интерфейс через тот же указатель) должен возвращать одно и то же значение указателя.
Таким образом, при запросе объекта DOMDocument IUnknown
интерфейс через QueryInterface()
можно ожидать, что счетчик ссылок будет увеличен на 1, а не на 2. В этом случае вы должен получили следующие цифры в вашем выводе:
int main ()
{
IXMLDOMDocument *pDoc(nullptr);
CoCreateInstance(CLSID_DOMDocument, nullptr, CLSCTX_ALL, IID_IXMLDOMDocument, reinterpret_cast<LPVOID*>(&pDoc));
// DOMDoc refcnt=1
DWORD d = pDoc->AddRef();
// DOMDoc refcnt=2
std::cout << "pDoc: add ptr=" << pDoc << " d=" << d << std::endl;
d = pDoc->Release();
// DOMDoc refcnt=1
std::cout << "pDoc: rel ptr=" << pDoc << " d=" << d << std::endl;
IUnknown *pUnk(nullptr);
pDoc->QueryInterface(IID_IUnknown, reinterpret_cast<LPVOID*>(&pUnk));
// DOMDoc refcnt=2
d = pUnk->AddRef();
// DOMDoc refcnt=3
std::cout << "pUnk: add ptr=" << pUnk << " d=" << d << std::endl;
d = pUnk->Release();
// DOMDoc refcnt=2
std::cout << "pUnk: rel ptr=" << pUnk << " d=" << d << std::endl;
/*Release objects*/
d = pUnk->Release();
// DOMDoc refcnt=1
std::cout << "pUnk: rel ptr=" << pUnk << " d=" << d << std::endl;
d = pDoc->Release();
// DOMDoc refcnt=0
std::cout << "pDoc: rel ptr=" << pDoc << " d=" << d << std::endl;
return 0;
}
Однако в действительности, когда вы запрашиваете объект DOMDocument для его IUnknown
В интерфейсе явно указывается дополнительная внутренняя ссылка на объект, и эта дополнительная ссылка не освобождается до тех пор, пока все ссылки на запрошенный IUnknown
интерфейс был выпущен. Это будет учитывать числа, которые вы видите:
int main ()
{
IXMLDOMDocument *pDoc(nullptr);
CoCreateInstance(CLSID_DOMDocument, nullptr, CLSCTX_ALL, IID_IXMLDOMDocument, reinterpret_cast<LPVOID*>(&pDoc));
// DOMDoc refcnt=1
DWORD d = pDoc->AddRef();
// DOMDoc refcnt=2
std::cout << "pDoc: add ptr=" << pDoc << " d=" << d << std::endl;
d = pDoc->Release();
// DOMDoc refcnt=1
std::cout << "pDoc: rel ptr=" << pDoc << " d=" << d << std::endl;
IUnknown *pUnk(nullptr);
pDoc->QueryInterface(IID_IUnknown, reinterpret_cast<LPVOID*>(&pUnk));
// DOMDoc refcnt=3, not 2!
d = pUnk->AddRef();
// DOMDoc refcnt=4
std::cout << "pUnk: add ptr=" << pUnk << " d=" << d << std::endl;
d = pUnk->Release();
// DOMDoc refcnt=3
std::cout << "pUnk: rel ptr=" << pUnk << " d=" << d << std::endl;
/*Release objects*/
d = pUnk->Release();
// DOMDoc refcnt=1, not 2!
std::cout << "pUnk: rel ptr=" << pUnk << " d=" << d << std::endl;
d = pDoc->Release();
// DOMDoc refcnt=0
std::cout << "pDoc: rel ptr=" << pDoc << " d=" << d << std::endl;
return 0;
}
Объект DOMDocument, вероятно, возвращает указатель на внутренний вспомогательный объект при запросе для IUnknown
и этот вспомогательный объект возвращает счетчик ссылок DOMDocument из AddRef()
а также Release()
вместо того, чтобы возвращать свой собственный счетчик ссылок.
то, что вы видите — это Com агрегирование и здесь pUnk
является внутренний объект и pDoc
является aggregable объект. также интересно, что при запросе IXMLDOMDocument
интерфейс на внутренний объект — он каждый раз выделяет новый aggregable объект, который реализует этот интерфейс
Позвольте вначале создать служебную функцию для печати подсчета ссылок на объекте, а также сравнить 2 указателя объекта с точки зрения com (двоичные значения этих указателей могут быть разными, но IUnknown
для обоих объектов одинаковое)
ULONG GetRefCount(IUnknown *pUnk, BOOLEAN bPrint = TRUE)
{
pUnk->AddRef();
ULONG d = pUnk->Release();
if (bPrint) DbgPrint("%p>%u\n", pUnk, d);
return d;
}
BOOLEAN IsSameObjects(IUnknown *p, IUnknown *q)
{
BOOLEAN f = FALSE;
IUnknown *Unk1, *Unk2;
if (0 <= p->QueryInterface(IID_PPV_ARGS(&Unk1)))
{
if (0 <= q->QueryInterface(IID_PPV_ARGS(&Unk2)))
{
f = Unk1 == Unk2;
Unk2->Release();
}
Unk1->Release();
}
DbgPrint("%p[%u] %s %p[%u]\n", p, GetRefCount(p, FALSE), f ? "==" : "!=", q, GetRefCount(q, FALSE));
return f;
}
Теперь давайте сделаем первый тест:
void test1 ()
{
IXMLDOMDocument *pDoc, *pDoc2;
if (0 <= CoCreateInstance(__uuidof(DOMDocument), 0, CLSCTX_ALL, IID_PPV_ARGS(&pDoc)))
{
GetRefCount(pDoc);
IUnknown *pUnk;
if (0 <= pDoc->QueryInterface(IID_PPV_ARGS(&pUnk)))
{
IsSameObjects(pDoc, pUnk);
if (0 <= pUnk->QueryInterface(IID_PPV_ARGS(&pDoc2)))
{
IsSameObjects(pDoc, pDoc2);
GetRefCount(pUnk);
pDoc2->Release();
GetRefCount(pUnk);
}
pUnk->Release();
}
GetRefCount(pDoc);
DbgPrint("Final Release=%u\n", pDoc->Release());
}
}
и это вывод:
000001DD8DCE71A0>1
000001DD8DCE71A0[1] == 000001DD8DCE5950[3]
000001DD8DCE71A0[1] == 000001DD8DCE7270[1]
000001DD8DCE5950>4
000001DD8DCE5950>3
000001DD8DCE71A0>1
Final Release=0
здесь видно что pUnk
а также pDoc
(pDoc2
) указывают на разные области памяти, но это один и тот же объект com
на основании этого давайте сделаем более симметричный тест:
void test2 ()
{
IUnknown *pUnk;
if (0 <= CoCreateInstance(__uuidof(DOMDocument), 0, CLSCTX_ALL, IID_PPV_ARGS(&pUnk)))
{
GetRefCount(pUnk);
IXMLDOMDocument *pDoc, *pDoc2;
if (0 <= pUnk->QueryInterface(IID_PPV_ARGS(&pDoc)))
{
IsSameObjects(pUnk, pDoc);
if (0 <= pUnk->QueryInterface(IID_PPV_ARGS(&pDoc2)))
{
IsSameObjects(pDoc2, pDoc);
GetRefCount(pUnk);
pDoc2->Release();
}
if (0 <= pDoc->QueryInterface(IID_PPV_ARGS(&pDoc2)))
{
IsSameObjects(pDoc2, pDoc);
GetRefCount(pUnk);
pDoc2->Release();
}
pDoc->Release();
}
GetRefCount(pUnk);
DbgPrint("Final Release=%u\n", pUnk->Release());
}
}
и вывод:
000001DD8DCE5950>1
000001DD8DCE5950[3] == 000001DD8DCE7270[1]
000001DD8DCE71A0[1] == 000001DD8DCE7270[1]
000001DD8DCE5950>4
000001DD8DCE7270[2] == 000001DD8DCE7270[2]
000001DD8DCE5950>3
000001DD8DCE5950>1
Final Release=0
Здесь лучше видно, что впервые создан внутренний объект. каждый раз, когда мы запрашиваем IXMLDOMDocument
для этого объекта — создан новый агрегируемый объект и возвращен указатель на него.
как это реализовано в коде? просто демо
struct __declspec(novtable) __declspec(uuid("78979DF1-A166-4797-AF2B-21BBE60D0B2E")) IDemo : public IUnknown
{
virtual void Demo() = 0;
};
class CDemo : public IDemo
{
IUnknown* _pUnkOuter;
LONG _dwRef;
~CDemo()
{
DbgPrint("%s<%p>\n", __FUNCTION__, this);
_pUnkOuter->Release();
}
public:
CDemo(IUnknown* pUnkOuter) : _pUnkOuter(pUnkOuter)
{
DbgPrint("%s<%p>\n", __FUNCTION__, this);
_dwRef = 1;
pUnkOuter->AddRef();
}
virtual void Demo()
{
DbgPrint("%s<%p>\n", __FUNCTION__, this);
}
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void**ppvObject)
{
if (riid == __uuidof(IDemo))
{
AddRef();
*ppvObject = static_cast<IUnknown*>(this);
return S_OK;
}
return _pUnkOuter->QueryInterface(riid, ppvObject);
}
virtual ULONG STDMETHODCALLTYPE AddRef()
{
return InterlockedIncrement(&_dwRef);
}
virtual ULONG STDMETHODCALLTYPE Release()
{
ULONG dwRef = InterlockedDecrement(&_dwRef);
if (!dwRef) delete this;
return dwRef;
}
};
class CObject : public IUnknown
{
LONG _dwRef;
~CObject()
{
DbgPrint("%s<%p>\n", __FUNCTION__, this);
}
public:
CObject()
{
DbgPrint("%s<%p>\n", __FUNCTION__, this);
_dwRef = 1;
}
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void**ppvObject)
{
*ppvObject = 0;
if (riid == __uuidof(IUnknown))
{
AddRef();
*ppvObject = static_cast<IUnknown*>(this);
return S_OK;
}
else if (riid == __uuidof(IDemo))
{
if (CDemo* pDoc = new CDemo(this))
{
*ppvObject = static_cast<IUnknown*>(pDoc);
return S_OK;
}
return E_OUTOFMEMORY;
}
return E_NOINTERFACE;
}
virtual ULONG STDMETHODCALLTYPE AddRef()
{
return InterlockedIncrement(&_dwRef);
}
virtual ULONG STDMETHODCALLTYPE Release()
{
ULONG dwRef = InterlockedDecrement(&_dwRef);
if (!dwRef) delete this;
return dwRef;
}
};
void test3()
{
if (CObject* pUnk = new CObject)
{
GetRefCount(pUnk);
IDemo *pDoc, *pDoc2;
if (0 <= pUnk->QueryInterface(IID_PPV_ARGS(&pDoc)))
{
IsSameObjects(pUnk, pDoc);
if (0 <= pUnk->QueryInterface(IID_PPV_ARGS(&pDoc2)))
{
IsSameObjects(pDoc2, pDoc);
GetRefCount(pUnk);
pDoc2->Release();
}
if (0 <= pDoc->QueryInterface(IID_PPV_ARGS(&pDoc2)))
{
IsSameObjects(pDoc2, pDoc);
GetRefCount(pUnk);
pDoc2->Release();
}
pDoc->Release();
}
GetRefCount(pUnk);
DbgPrint("Final Release=%u\n", pUnk->Release());
}
}
и вывод:
CObject::CObject<000001DD8C340970>
000001DD8C340970>1
CDemo::CDemo<000001DD8C33B950>
000001DD8C340970[2] == 000001DD8C33B950[1]
CDemo::CDemo<000001DD8C338930>
000001DD8C338930[1] == 000001DD8C33B950[1]
000001DD8C340970>3
CDemo::~CDemo<000001DD8C338930>
000001DD8C33B950[2] == 000001DD8C33B950[2]
000001DD8C340970>2
CDemo::~CDemo<000001DD8C33B950>
000001DD8C340970>1
CObject::~CObject<000001DD8C340970>
Final Release=0