Проблема с хранением COM-указателей в глобальном одноэлементном объекте
-
19-09-2019 - |
Вопрос
Предыстория
Приложение, с которым я работаю, имеет несколько COM-библиотек DLL.
Одна из библиотек DLL COM имеет глобальный одноэлементный объект, который хранит указатели на COM-интерфейсы в других библиотеках DLL.Поскольку это глобальный одноэлементный объект, я использовал отложенная инициализация идиома, потому что возможно, что интерфейс, на который я пытаюсь получить указатель, существует в DLL, которая еще не была загружена.
(Побочное примечание: Это особенно важно при регистрации отдельной библиотеки DLL, поскольку глобальные объекты будут создаваться внутри regsvr32
процесс, и я не хочу, чтобы DLL пыталась получить интерфейс к другой DLL во время этого процесса.)
Например, мой метод ленивой инициализации будет делать что-то вроде этого:
CComPtr<IMyOtherObject>&
CGlobalSingleton::
GetMyOtherObject()
{
// SNIP: Other code removed for clarity...
if (! m_pMyOtherObject)
{
hr = pUnknown->QueryInterface(IID_IMyOtherObject,
(void**) &m_pMyOtherObject);
}
return m_pMyOtherObject;
}
ПРИМЕЧАНИЕ: m_pMyOtherObject
является переменной -членом CComPtr
Тип.
Отложенная инициализация может не иметь отношения к моей проблеме здесь, но я включаю ее для полноты картины.
Проблема
Что я заметил, так это то, что при некоторых обстоятельствах я получаю неудачные утверждения, когда мое приложение завершает работу.Однако, если я изменю свой код на вызов QueryInterface()
каждый время, которое мне нужно для доступа IID_IMyOtherOBject
(вместо того, чтобы хранить его как переменную-член) это предотвращает выполнение утверждений.
Мне кажется, что это проблема со сроком службы COM-объекта.Моя гипотеза заключается в том, что, поскольку я storing
COM-указатель, должна быть какая-то синхронизация между уничтожением COM-интерфейса, на который я указываю, и моим собственным указателем на него.
Мое понимание CComPtr
класс (который я использую) заключается в том, что он избавляет от многих головных болей, связанных с решением пожизненных проблем (т.е.зовущий AddRef()
и Release()
).Но, похоже, в моем случае это не работает.
Кто-нибудь может определить, что я, возможно, делаю неправильно?
Решение
Вместо того чтобы внедрять свой собственный глобальный синглтон, посмотрите на использование Глобальный интерфейс , доступный вместо этого используйте интерфейс.Это синглтон, который предоставляется операционной системой на уровне процесса.Любая из ваших библиотек DLL может помещать свои COM-объекты в таблицу, а другие библиотеки DLL могут восстанавливать их при необходимости.Все, что вам нужно было бы реализовать со своей стороны, - это способ для библиотек DLL обмениваться файлами cookie таблицы DWORD друг с другом.
Другие советы
Вы возвращаете ссылку на интеллектуальный указатель, который, возможно, не увеличивает количество ссылок.Извините, я бы проверил, но здесь уже поздно.Это моя догадка, и она соответствует тому, что вы описываете - загляните в конструкторы копирования для CComPtr.
Надеюсь, это поможет,
K
Дикий удар ножом в темноте:Возможно ли , что CGlobalSingleton
может быть уничтожен после CoUninitialize()
называется, при каких обстоятельствах?Если бы это было так, и m_pMyOtherObject
следовательно, был также уничтожен после неинициализации COM, это был бы еще один способ вызвать нарушение доступа, о котором упоминал Игорь.
Я подозреваю, что проблема заключается в вашем понимании семантики копирования / присваивания класса CComPtr;Я не особенно знаком с CComPtr, но, по моему опыту, умные указатели, как правило, работают не так, как вы могли бы от них ожидать.Сначала вам следует прочитать документацию для CComPtr и убедиться, что вы понимаете, как это работает (также не помешало бы взглянуть на исходный код).Вы также могли бы попробовать установить некоторые точки останова в элементах AddRef() и Release() CComPtr, чтобы посмотреть, что происходит во время и после вызова GetMyOtherObject(), особенно если вы временно сохраняете возвращаемое значение и оно выходит за пределы области видимости.
Звучит как m_pMyOtherObject
все еще работает, когда вы закрываете свое приложение.В дополнение к проблемам с конструктором копирования m_pMyOtherObject
должен либо быть CComPtr
или CGlobalSingleton
должен позвонить m_pMyOtherObject
's Release
способ уничтожения.
Отредактировано для наглядности.
Редактировать Просто провел быстрый тест и не столкнулся с какими-либо проблемами при использовании функции, возвращающей ссылку на CComPtr
.Хотя это немного необычно, это не вызвало никаких проблем с подсчетом ссылок.
Однако я хотел бы подробнее рассказать о том, что произойдет, если m_pMyOtherObject
это не умный указатель.В этом случае он никогда не будет выпущен.Позвольте мне показать вам, почему:
- Вы вызываете QueryInterface по некоторому указателю.Он вызовет AddRef для этого объекта.
- Вы возвращаете либо CComPtr& CComPtr&, либо голый указатель интерфейса.Это в значительной степени не имеет значения.Операции подсчета ссылок не выполняются (если только вы не присвоите возвращаемое значение другому CComPtr, который добавит к нему ссылку.Но поскольку этот CComPtr уравновесит его вызовом Release, это не имеет значения).
- В итоге вы получаете либо 1 вызов AddRef и 0 для Release, либо 2 вызова AddRef и 1 для Release.Другими словами, они несбалансированы, и у вас утечка ссылок.
Чтобы избежать этого, вам нужно структурировать свою программу следующим образом:
class CGlobalSingleton{
CComPtr<IMyOtherObject> m_spMyOtherObject;
IMyOtherObject* GetMyOtherObject()
{
// SNIP: Other code removed for clarity...
if (! m_spMyOtherObject)
{
//pUnknown gets AddRef'ed, but that's OK, m_spMyOtherObject will call release when CGlobalSingleton goes out of scope
hr = pUnknown->QueryInterface(IID_IMyOtherObject,
(void**) &m_spMyOtherObject);
}
return m_pMyOtherObject;
}
}