Увеличивает ли IUnknown::QueryInterface() счетчик ссылок?

Если у меня есть IUnknown *ptr мне нужно позвонить Release() на каждом интерфейсе, который я получаю через ptr->QueryInterface() кроме звонка ptr->Release() когда я закончу с ptr?

Раньше я думал, что ответ "Да", но эта цитата из MSDN смутила меня:

Иногда вам может понадобиться получить слабую ссылку на объект (то есть вы можете получить указатель на один из его интерфейсов без увеличения счетчика ссылок), но это недопустимо сделать, вызвав QueryInterface с последующим Release,

Я не понимаю, почему это проблематично - если я позвоню ptr->QueryInterface() а затем позвоните Release на полученном указателе, разве счетчик ссылок на объект не должен быть положительным? Как это приводит к неверному указателю?

4 ответа

Решение

Документация верна. И вы должны следовать правилам подсчета ссылок - это включает в себя вызов Release на интерфейсах, полученных из QueryInterface в дополнение к после того, как вы создали объект.

Чтобы понять, почему вы не можете делать слабые указатели с Release - в вызове существует условие гонки QueryInterface а потом Release незамедлительно после.

  • Thread1 создает объект - счетчик ссылок 1
  • Thread2 звонки QueryInterface для слабой ссылки - счетчик ссылок 2
  • Thread1 освобождает объект - счетчик ссылок 1
  • Thread2 звонки Release для слабой ссылки - количество ссылок 0. Объект уничтожен.
  • Thread2 пытается использовать объект - ошибка.

Предупреждение должно предостеречь от вышесказанного - вероятно, некоторые программисты думают, что они могут ptr->QueryInterface() а затем позвоните Release на результирующий указатель ", а затем использовать объект...

IUnknown:: Метод QueryInterface

Получает указатели на поддерживаемые интерфейсы на объекте.

Этот метод вызывает IUnknown::AddRef для указателя, который он возвращает.

Прямо из ссылки IUnknown::QueryInterface по http://msdn.microsoft.com/en-us/library/ms682521%28v=vs.85%29.aspx

Актерство - не единственный сценарий; Я бы даже сказал, что многопоточность на самом деле не является основным сценарием: эти COM-правила относятся к Win16 до того, как упреждающая многопоточность была добавлена ​​в Windows.

Ключевой вопрос заключается в том, что для COM, счетчики ссылок являются дляинтерфейса, а не дляобъекта. Реализация COM свободна на самом деле реализовать подсчет ссылок, реализуя его для каждого объекта - это, пожалуй, самый простой способ сделать это в C++, особенно когда объект COM отображается на один объект C++ - но это не более, чем детали реализации, и код COM-клиента не может полагаться на это.

Есть много COM-объектов, которые могут генерировать интерфейсы на лету по мере необходимости, а затем уничтожать их, как только они больше не нужны. В этих случаях, если вы вызываете QI, чтобы получить один из этих интерфейсов, после вызова Release, память для этого интерфейса может быть освобождена, поэтому ее использование может привести к ошибке / аварии / и т.д.

Вообще говоря, вы должны рассматривать любой вызов ->Release() как потенциально освобождающий память за указателем.

(Кроме того, обратите внимание, что в COM на самом деле нет концепции слабых ссылок: есть счетные (сильные) ссылки, и все.)

Предложение избегать слабых ссылок не решает проблему гонки.

T1 operator new, create object, references: 1
T1     passes interface object reference to T2, thinking it can "share" ownership
T1     suspends
T2     resumes
T2 QueryInterface
T2     suspends before InterlockedIncrement, references: 1
T1     resumes
T1 Calls Release
T1     suspends between InterlockedDecrement and operator delete, references: 0
T2     resumes, InterlockedIncrement occurs, references 1
T2     suspends
T1     resumes, operator delete executes, references 1 !!!
T1     suspends
T2     resumes
T2 Any reference to the interface is now invalid since it has been deleted with reference count 1.

Это разрешимо на COM-сервере. Однако COM-клиент не должен зависеть от сервера, предотвращающего это состояние гонки. Поэтому COM-клиенты НЕ ДОЛЖНЫ совместно использовать объекты интерфейса между потоками. Единственный поток, которому следует разрешить доступ к объекту интерфейса, - это ОДИН поток, который в настоящее время "владеет" объектом интерфейса.

T1 НЕ должен был вызывать Release. Да, он мог вызвать AddRef до передачи объекта интерфейса в T2. Но это может не решить гонку, только перенести ее в другое место. Лучше всего всегда поддерживать концепцию "один интерфейсный объект, один владелец".

Если COM-сервер желает поддерживать концепцию, согласно которой два (или более) интерфейса могут ссылаться на некоторое общее внутреннее состояние общего сервера, COM-сервер должен объявить о контракте, предоставив метод CreateCopyOfInstance, и внутренне управлять конфликтами. Конечно, есть примеры серверов, которые справляются с таким "разветвлением". Посмотрите на интерфейсы постоянного хранения от Microsoft. Там интерфейсы НЕ "разветвляются". Каждый интерфейс должен принадлежать одному пользователю (поток / процесс / что угодно), а "разветвление" управляется внутренне сервером с помощью методов, предоставляемых COM-клиенты, чтобы контролировать некоторые аспекты конфликтных вопросов. Поэтому COM-серверы Microsoft должны учитывать условия гонки как часть своих контрактов со своими клиентами.

Другие вопросы по тегам