Каков правильный способ приведения при использовании ATL и IUnknownPtr?
-
18-09-2019 - |
Вопрос
Во время модификации существующего COM-объекта ATL я наткнулся на статью из блога «The Old New Thing» под названием «Как люди портят IUnknown::QueryInterface», и в разделе комментариев было обсуждение, которое началось, когда один из респонденты (Норман Даймонд) отметили, что в одном из примеров статьи приведение к void** было неверным.
Однако, когда я пытаюсь исправить свой код, чтобы правильно выполнить приведение, у меня возникает утечка памяти.
Пример был следующий:
IShellFolder *psf = some object;
IUnknown *punk = NULL;
psf->QueryInterface(IID_IUnknown, (void**)&punk);
Норман сказал
панк – это не пустота*.панк — это IUnknown*.
void** не является универсальным типом указателя.void* — это универсальный тип указателя, а char* и его родственники являются эквивалентными в этом смысле, но void** — нет.
Если вы хотите соблюдать соглашение о вызовах и избежать ужасных смертей, вам нужно сделать следующее:IНеизвестно *punk;пустота *punkvoid;psf->QueryInterface(IID_IUnknown, &punkvoid);панк = (IUnknown *)punkvoid;
Многие другие участники MSDN совершили ту же ошибку....некоторые люди могут сказать, что на сегодняшний день он работает во всех реализациях VС++, но это не делает его правильным кодом, и он по-прежнему нарушает соглашение о вызовах.
В свете этого я решил изменить свой старый код, который выглядел следующим образом:
#include <comdef.h>
...
HRESULT FinalConstruct()
{
if (m_dwROTCookie != 0)
return E_FAIL;
//Check whether there already is an instance of the Object
IUnknownPtr pUnk = NULL;
if (GetActiveObject(CLSID_Object, NULL, &pUnk) == S_OK)
{
TRACE_WARNING("An instance of Object already exists in the current context");
return S_OK;
}
HRESULT hr = QueryInterface(IID_IUnknown, reinterpret_cast<void **>(&pUnk));
hr = RegisterActiveObject(pUnk, CLSID_Object, ACTIVEOBJECT_WEAK, m_dwROTCookie);
if (FAILED(hr))
return hr;
hr = CoLockObjectExternal(pUnk, TRUE, TRUE);
pUnk = NULL;
ATLASSERT(m_dwRef == 2);
return hr;
}
Затем я изменил его следующим образом:
HRESULT FinalConstruct()
{
if (m_dwROTCookie != 0)
return E_FAIL;
//Check whether there already is an instance of the Object
IUnknownPtr pUnk = NULL;
if (GetActiveObject(CLSID_Object, NULL, &pUnk) == S_OK)
{
TRACE_WARNING("An instance of Object already exists in the current context");
return S_OK;
}
void* pUnkVoid = NULL;
HRESULT hr = QueryInterface(IID_IUnknown, &pUnkVoid);
if (SUCCEEDED(hr)
{
pUnk = reinterpret_cast<IUnknown*>(pUnkVoid);
hr = RegisterActiveObject(pUnk, CLSID_Object, ACTIVEOBJECT_WEAK, m_dwROTCookie);
if (FAILED(hr))
return hr;
hr = CoLockObjectExternal(pUnk, TRUE, TRUE);
pUnk = NULL;
}
ATLASSERT(m_dwRef == 2);
return hr;
Однако теперь в моем приложении есть утечка памяти из-за этого COM-объекта.
Решение
Вероятно, у вас есть утечка памяти, потому что вы вызываете GetActiveObject()
и QueryInterface()
который в случае успеха увеличивает счетчик ссылок на объект, но не вызывает Release()
позже, чтобы уменьшить счетчик ссылок.
Другие советы
Ммм, я думаю, что вместо того, чтобы присваивать void* pUnk, мне следует использовать:
pUnk.Attach(reinterpret_cast<IUnknown*>(pUnkVoid));