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-объекте должна строго следовать контракту. Нарушение правил подсчёта ссылок приводит к утечкам памяти или двойному освобождению. Основные требования:
- Потокобезопасность: Для внутрипроцессных (in-process) объектов методы AddRef и Release должны быть потокобезопасными (обычно реализуются с помощью InterlockedIncrement/InterlockedDecrement).
- Идентичность: Все вызовы QueryInterface для IUnknown на одном объекте должны возвращать один и тот же указатель.
- Целостность: Объект не должен удаляться, пока есть хотя бы одна внешняя ссылка.
В 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 →