IUnknown :: QueryInterface () увеличивает счетчик ссылок? - PullRequest
7 голосов
/ 11 сентября 2011

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

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

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

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

Ответы [ 4 ]

7 голосов
/ 11 сентября 2011

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

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

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

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

4 голосов
/ 11 сентября 2011

IUnknown :: QueryInterface Method

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

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

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

2 голосов
/ 11 сентября 2011

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

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

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

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

(Также обратите внимание, что в COM на самом деле нет концепции слабых ссылок для начала: есть подсчет (Сильные) ссылки, и все тут.)

0 голосов
/ 14 марта 2013

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

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 должны учитывать условия гонки в рамках своих контрактов со своими клиентами.

...