Открыть сервис

IUnknown

IUnknown — это базовый COM-интерфейс, определяющий фундаментальный механизм управления временем жизни объектов и поддержки запросов на получение других интерфейсов в компонентной объектной модели (COM) от Microsoft. IUnknown является обязательным для всех COM-объектов и служит корнем иерархии всех COM-интерфейсов. Его спецификация была опубликована в 1993 году в рамках стандарта COM, который лёг в основу технологий OLE, ActiveX и DirectX.

История и контекст

IUnknown был разработан в начале 1990-х годов как часть компонентной объектной модели Microsoft. Целью COM было создание стандарта для межпроцессного и межъязыкового взаимодействия программных компонентов. До появления COM разработчики использовали различные, часто несовместимые способы создания и использования повторно используемых объектов (например, DLL с экспортируемыми функциями). IUnknown решил ключевую проблему — как безопасно и единообразно управлять памятью объектов и как динамически определять возможности компонента во время выполнения.

Первоначально IUnknown применялся в OLE (Object Linking and Embedding) для создания составных документов. Позже, с появлением ActiveX и DirectX, интерфейс стал основой для сотен тысяч COM-объектов в Windows. В 1996 году спецификация IUnknown была передана в комитет ISO/IEC в рамках стандартизации COM.

Определение и методы

IUnknown состоит из трёх методов, которые должны быть реализованы любым COM-объектом. Эти методы объявлены в заголовочном файле unknwn.h и имеют следующую сигнатуру:

``cpp interface IUnknown { virtual HRESULT QueryInterface(REFIID riid, void **ppvObject) = 0; virtual ULONG AddRef(void) = 0; virtual ULONG Release(void) = 0; }; ``

QueryInterface

Метод QueryInterface (QI) позволяет клиенту запросить у объекта указатель на другой интерфейс, который этот объект поддерживает. Клиент передаёт уникальный идентификатор интерфейса (IID, GUID) и получает указатель на запрошенный интерфейс или код ошибки E_NOINTERFACE, если интерфейс не поддерживается. Этот механизм обеспечивает динамическое обнаружение возможностей объекта. Например, объект, реализующий интерфейс IDraw, может одновременно поддерживать ISave, и клиент может получить доступ к обоим через последовательные вызовы QueryInterface.

Правила реализации QueryInterface:

  • Если объект однажды вернул ненулевой указатель на интерфейс A, то последующий запрос того же интерфейса A должен всегда возвращать тот же указатель.
  • QueryInterface для IUnknown должен всегда возвращать один и тот же указатель для данного экземпляра объекта (правило идентичности).
  • Запрос интерфейса, который объект не поддерживает, возвращает E_NOINTERFACE.

AddRef и Release

Методы AddRef и Release реализуют подсчёт ссылок (reference counting) — механизм управления временем жизни COM-объекта. Каждый раз, когда клиент получает указатель на интерфейс (через QueryInterface, CoCreateInstance или копирование указателя), он должен вызвать AddRef, увеличивая внутренний счётчик ссылок. Когда клиент заканчивает работу с интерфейсом, он вызывает Release, уменьшая счётчик. Когда счётчик достигает нуля, объект уничтожает себя (вызывает delete this или освобождает память другим способом).

Базовые правила:

  • Каждый вызов AddRef должен быть сбалансирован соответствующим вызовом Release.
  • При получении указателя через QueryInterface или CoCreateInstance клиент не вызывает AddRef — эти функции уже увеличивают счётчик.
  • При копировании указателя (например, присваивание) клиент обязан вызвать AddRef на новом указателе.

Правила реализации и использования

Реализация IUnknown в COM-объекте должна строго следовать контракту. Нарушение правил подсчёта ссылок приводит к утечкам памяти или двойному освобождению. Основные требования:

  1. Потокобезопасность: Для внутрипроцессных (in-process) объектов методы AddRef и Release должны быть потокобезопасными (обычно реализуются с помощью InterlockedIncrement/InterlockedDecrement).
  2. Идентичность: Все вызовы QueryInterface для IUnknown на одном объекте должны возвращать один и тот же указатель.
  3. Целостность: Объект не должен удаляться, пока есть хотя бы одна внешняя ссылка.

В COM-клиентах (потребителях) принято использовать макросы SAFE_RELEASE или интеллектуальные указатели (например, CComPtr в ATL), чтобы автоматизировать вызовы AddRef/Release и избежать ошибок.

Пример использования

Типичный сценарий работы с COM-объектом на C++:

``cpp IUnknown *pUnk = NULL; HRESULT hr = CoCreateInstance(CLSID_MyComponent, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void**)&pUnk); if (SUCCEEDED(hr)) { IMyInterface *pMyInt = NULL; hr = pUnk->QueryInterface(IID_IMyInterface, (void**)&pMyInt); if (SUCCEEDED(hr)) { pMyInt->DoSomething(); pMyInt->Release(); } pUnk->Release(); } ``

Здесь CoCreateInstance возвращает указатель на IUnknown с уже увеличенным счётчиком ссылок. Клиент вызывает Release, когда заканчивает работу.

Значение и влияние

IUnknown стал краеугольным камнем COM и всех производных технологий Microsoft. Его модель подсчёта ссылок и динамического запроса интерфейсов была заимствована многими другими системами, включая:

  • XPCOM (Mozilla) — использует аналогичный интерфейс nsISupports с методами QueryInterface, AddRef, Release.
  • CORBA — имеет концепцию управления жизненным циклом объектов, хотя и с другой реализацией.
  • Java — интерфейс java.lang.ref.Reference и система сборки мусора частично решают те же задачи, но иначе.

В современных средах (C++/WinRT, .NET) COM и IUnknown остаются фундаментом для взаимодействия с системными API Windows. Например, все объекты WinRT (Windows Runtime) реализуют IInspectable, который наследует IUnknown и добавляет методы для получения информации о типе.

Критика и ограничения

Несмотря на широкое распространение, IUnknown имеет ряд недостатков:

  • Сложность ручного управления памятью: подсчёт ссылок требует дисциплины от программиста, ошибки приводят к утечкам или падениям.
  • Отсутствие проверки типов на этапе компиляции: QueryInterface работает с GUID, что может приводить к ошибкам, если IID указан неверно.
  • Проблемы с циклическими ссылками: если два объекта держат ссылки друг на друга, они никогда не будут удалены (требуется ручное разрывание цикла).
  • Потокобезопасность: хотя AddRef/Release должны быть потокобезопасными, сам QueryInterface и методы интерфейсов не обязаны быть таковыми, что может вызывать гонки данных.

Эти ограничения привели к появлению альтернатив, таких как умные указатели (CComPtr, _com_ptr_t) и более современные модели компонентов (например, .NET с автоматической сборкой мусора).

Источники

  • Microsoft COM Specification (1995)
  • Дон Бокс, «Сущность COM» (Essential COM, 1998)
  • MSDN Library: «IUnknown Interface»
  • ISO/IEC 14882:1998 (стандарт C++, раздел о COM)

BFOmetr — база данных и аналитика по компаниям России.

На главную BFOmetr →