developing.ru - клуб программистов Здесь может быть и ваша реклама.
developing.ru >технология COM >
Михаил Безверхов
aspid@developing.ru

Интерфейс IUnknown - "...никогда не думай,что ты иная, чем могла быть иначе..."

Как быстро (для меня) бежит время - вот мы уже и на пороге IUnknown. Как медленно (для вас) бежит время - мы всего-то добрались только до порога IUnknown... Но, тем не менее, вот мы и на пороге "серьёзного COM".

Итак - интерфейс IUnknown является основным интерфейсом на котором зиждется COM. Его обязан реализовывать любой COM-объект, он обязан присутствовать в составе любого интерфейса, экспонируемого объектом. Но IUnknown - совершенно особый интерфейс, если иметь в виду не его видимость со стороны клиента, а его реализацию внутри объекта сервера. И основные особенности IUnknown мы и рассмотрим в этой статье.

Знакомство лучше начать с точной спецификации. Точная спецификация интерфейса IUnknown содержится в заголовочном файле C++ с именем <unknwn.h>:

MIDL_INTERFACE("00000000-0000-0000-C000-000000000046") IUnknown{ public:     virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid,void **  ppvObject) = 0;     virtual ULONG STDMETHODCALLTYPE AddRef(void) = 0;     virtual ULONG STDMETHODCALLTYPE Release(void = 0; };

Я немного сократил его определение за счёт конструкций языка C++ для нас пока не существенных, но не выплеснул вместе с водой ребенка - файл этот не написан руками, а сгенерирован компилятором MIDL c языка IDL - специального языка, на котором описываются интерфейсы. Поэтому компилятор туда вставляет всё что можно и для всех возможных случаев использования этого файла. Если хотите заглянуть в оригинал сами, то вот ссылка на его "стандартное" местоположение (если соглашаться на те пути и каталоги, которые при инсталляции Visual Studio предлагает по умолчанию инсталлятор) - "C:\Program Files\Microsoft Visual Studio\VC98\Include\UNKNWN.H".

Интерфейс IUnknown имеет "свой GUID":

{00000000-0000-0000-C000-000000000046}

который в программах на C++ адресуется ссылкой IID_IUnknown. Состоит интерфейс из трёх, подробно объяснявшихся в своей сущности в предыдущих рассылках, методов: QueryInterface,AddRef,Release - именно в таком порядке адреса их точек входа расположены в vtbl. Учитывая, что всякий интерфейс обязан начинаться с IUnknown, то, выходит, что первые три входа любой vtbl занимают эти методы и именно в таком порядке.

Зачем нужны методы IUnknown - подробно разбиралось ранее. Поэтому сейчас мы рассматриваем только как эти методы нужно реализовать. Начнем по порядку следования...

Метод QueryInterface. Предназначен для преобразования типа указателя на интерфейс - на вход принимается IID интерфейса и адрес переменной, куда нужно поместить указатель на интерфейс, именуемый данным IID. Если запрашиваемый интерфейс действительно реализуется данным объектом, то метод помещает указатель на него в предоставленную переменную. Если нет - метод возвращает код возврата E_NOINTERFACE и, по хорошему, должен вернуть NULL в предоставленной переменной (чего некоторые, особенно - старые, серверы делают не всегда, к сожалению). Обратите внимание - метод принимает на вход IID любого интерфейса (произвольный GUID), а возвращает указатель на него, если интерфейс реализуется объектом. Т.е. метод QueryInterface любого интерфейса данного объекта должен знать все интерфейсы, реализуемые данным объектом. Это требование не так трудно и выдержать, оно не означает, что для каждого интерфейса этого объекта нужно писать свою реализацию этого метода. Оно означает, что реализации всех интерфейсов могут воспользоваться одной реализацией метода QueryInterface - единой для всего данного COM-объекта.

Метод AddRef. Предназначен для продвижения вперед на единицу счетчика ссылок. Должен возвращать новое значение этого счётчика ссылок - от 1 до n, но пользоваться этим значением можно только для отладки. Microsoft сообщает, что иногда это значение может быть "нестабильно". Что означает "нестабильно"- я не знаю. Видимо имеется в виду то, что в многопоточной среде значение счётчика совершенно точно известно только самому этому счётчику, который защищён от одновременного доступа нескольких потоков сразу. А вот актуальность текущего значения счётчика, выданного в любой поток может быть сразу же уничтожена параллельным потоком. Фактически смысл этого счётчика на клиентской стороне действительно имеется только при отладке, ничего другого на этом значении построить не удаётся.

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

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

А вот дальше нужно немного призадуматься... Что на самом деле означает фраза: "метод должен освободить ресурсы"? Для простых по конструкции и типовых объектов это, понятно, равносильно удалению самого объекта полученного сервером по операции new. Но для сложных объектов это может быть и не так - некоторые интерфейсы (они называются tear-off, "отрывными") сложный объект может реализовывать "по отдельности", т.е. не получать для них ресурсы при создании самого объекта, а получать ресурсы только тогда, когда кто-то попросит ссылку именно на этот интерфейс. В таком случае, методы AddRef и Release будут вынуждены обслуживать уже два и более счётчиков ссылок - общий для всего объекта и частные именно для таких интерфейсов в составе объекта. И так же дробно, как объект получал ресурсы, Release будет их и освобождать. При этом под термином "ресурсы" я понимаю не только динамическую память. Например, реализация интерфейса может открывать файл, устанавливать сетевое соединение и т.д. Эти ресурсы тоже должны освобождаться при потере всех ссылок на интерфейс.

Для некоторых объектов (а мало ли, вам понадобится и такие объекты реализовывать?), наподобие объекта CBanzai из примера №1, который является статическим объектом уровня модуля, "самоликвидатор" не нужен. Но счётчик ссылок для такого объекта вести придётся и клиент и для такого указателя будет вызывать AddRef/Release, хотя они в данном случае ничего и не делают. Принцип должен выдерживаться - клиент не знает, как реализован объект сервера. Клиент знает только интерфейс, посредством которого он взаимодействует с объектом. И должен соблюдать протокол.

Вы видите - всё достаточно просто, рационально, разумно. Я, например, не вижу (это после пяти-то лет пользования COM и написания программ с его использованием :) ничего непонятного - на существование всякой конструктивной особенности мы нашли причину от которой нам избавиться не удалось - именно поэтому нам такую конструкцию и приходится реализовывать.

В сущности, теперь мы можем написать и второй пример - пример, в котором конструктивные недостатки объектов первого примера будут исправлены программным механизмом, который мы только что изучили. Это сделает наши объекты действительно "настоящими объектами COM", но, к сожалению пока ещё не позволит избавиться от эмулятора CoCreateInstance - мало иметь COM-объекты, надо, чтобы они обитали и в "настоящем COM-сервере". А от "настоящего COM-сервера" мы пока знаем только одну из четырех реализуемых и экспонируемых им системе функций - DllGetClassObject. Поэтому, сразу после того, как мы изучим функционирование примера мы перейдём к следующей теме - конструкции COM-сервера. Подобающую же случаю изучения "настоящего COM-объекта" философию мы отложим на потом.

предыдущий выпускархив и оглавлениеследующий выпуск
Авторские права © 2001 - 2004, Михаил Безверхов
Публикация требует разрешения автора

разделы сайта

форум

технология COM

оптимизация сайтов


© 2000-2004 Клуб программистов developing.ru