Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Что такое "технология COM" и как с ней бороться?


Служба Рассылок Subscribe.Ru

М. Безверхов
vasilisk@clubpro.spb.ru

Что такое "технология COM" и как с ней бороться?     №37


От железа к COMпозитным материалам

В сегодняшнем номере рассылки мы построим пример №4 - наверное, последний из "элементарных примеров". Его размер, по сравнению с примерами №№ 1,2,3 увеличился почти вдвое, что связано с тем, что мы вдвое умножили сущности, находящиеся в рассмотрении. Поскольку то, что мы рассматривали до сих пор - азбука, я не считал возможным использовать какие-то сервисные средства. Сейчас я начинаю над этим задумываться - код угрожающе растёт. Но, с другой стороны, ценность этих примеров как раз в том и состоит, что они показывают как обстоит дело внутри сервисных средств и что именно сервисные средства инкапсулируют в себе... словом, я пока ничего не решил.

Итак, пример, который находится здесь, иллюстрирует "концепцию фабрики класса", а более точно - код показывает, как могут быть взаимосвязанно реализованы статические и нестатические аспекты типа. Построен он на базе примера №3 - там был сделан почти настоящий COM-сервер. Я просто сделал еще два "элементарных COM-объекта", которые к существовавшим у нас типам Banzai и Hello стали реализовывать "фабрики класса", а сами существовавшие у нас сущности стали "объектами экземпляров". Сейчас можно утверждать, что в данном примере получился простой, очень даже примитивный (на грани того, что из него больше ничего не выкинуть), но - самый настоящий COM-сервер, реализующий два статических типа. Эффективность кода примера - не то обстоятельство, на которое нужно сейчас обращать внимание. Во многих случаях можно было сделать короче (чего только стоят четыре оконных процедуры в клиенте, когда можно было обойтись двумя). Но "короче" и "очевиднее" вещи, во многом, противоположные. Поэтому, там где можно "коротко" или "длинно" предпочтение отдавалось "понятно", хотя в реальных программах, вы, конечно, многое оптимизируете.

Клиент, в целом, остался тот же - к "маленькому окну" привязывается COM-объект и посредством графического интерфейса можно наглядно выяснить существующие отношения между объектами. Добавилось новое - в соответствии с тем, что у нас теперь есть не только "объекты экземпляра", но и "объекты типа" мы имеем не два, а четыре "маленьких окна" - на каждый тип есть окно "статического типа" и окно "экземпляра". При этом "окно экземпляра" функционально осталось точно таким же, каким оно было в примере №3 - ведь функциональность самих объектов экземпляра не изменилась. А вот порождаются "объекты экземпляра" теперь по-другому - через "объект статического типа". Поэтому в "окне статического типа" показываются кнопки для вызова методов интерфейса IUnknown (как вы помните - это совсем не тот IUnknown, который реализован в "объекте экземпляра") и для вызова метода CreateInstance. Тем, кто интересуется как создать немодальный диалог и передать ему параметр и пр. - клиентская часть примера тоже может быть полезна. К сожалению, именно такой код и составляет большую часть примера. Код, вызывающий COM и обрабатывающий вызовы методов находится в численном меньшинстве. Что, может быть, и не так плохо - это наглядное подтверждение, что затраты собственно клиента на обращение с COM-объектом даже "напрямую" не так уж и велики, как это часто кажется.

В прошлых примерах мы рассматривали интересное явление - объект, который экспонирует интерфейс, может быть для программы-владелицы порожден и в динамической и в статической памяти. Для программы-клиента эту разницу заметить невозможно, а вот программа-сервер в одном случае будет "шлёпать" всякий раз новые экземпляры объектов, а в другом всем будет выдавать одну и ту же ссылку. Это - в чистом виде артефакт реализации, он к COM не имеет никакого отношения, и у нас объект Banzai порождался в статической памяти, а объект Hello - в динамической как пример того, что это возможно. Некогда нАчатая, в данном примере эта особенность углУблена - в реализации статического типа Banzai тот элементарный COM-объект, что реализует статические аспекты типа (фабрика класса) размещается в динамической памяти, а сам "объект экземпляра" - в статической. Для статического типа Hello сделано наоборот - "фабрика класса" является объектом в статической памяти, а экземпляры она размещает в динамической. Мне не хватает ещё двух статических типов, чтобы реализовать оставшуюся пару сочетаний, но я думаю, - как это можно сделать уже видно. Так что правильное представление о реализации всего спектра у вас сформИруется. Я надеюсь.

Важно, что мы, наконец, можем рассмотреть и действие функции API CoCreateInstance - именно она, получая CLSID-IID выдаёт клиенту ссылку на экземпляр объекта. Именно она чаще всего упоминается в литературе как "источник ссылки" на COM объект. Но функция эта - с лукавинкой. Ничего не зная о том, что такое COM, имея о нём поверхностное представление как просто о двоичной реализации ООП, "естественно" придти к заключению, что именно она и есть некий аналог оператора new. Сейчас, уже зная о COM вполне достаточно, вы никогда не придёте к такому выводу! На самом деле, принимая на вход CLSID-IID, CoCreateInstance последовательно вызывает GoGetClassObject с аргументами CLSID-IClassFactory и у полученного "объекта типа" запрашивает ссылку на тот интерфейс, что указан IID. "Объект типа" после этого освобождается. Для внешнего наблюдателя складывается впечатление, что он и не создавался. Но это - не так. Если трассировать всё события, то это можно увидеть. А наш пример эти события как раз и трассирует, так что, как работает CoCreateInstance "изнутри" вы сможете увидеть воочию.

О сказанном предвижу вопрос "почему?"... Всё имеет вполне рациональное объяснение. Во-первых, в очень многих случаях "объект типа" как раз и создаётся только для того, чтобы получить единственную ссылку на "объект экземпляра". И для большего - не нужен. Задача типовая. Значит, переложив её на систему, можно хоть немного облегчить клиента... Облегчение это маленькое, но ведь и клиентов, которые выполняют именно такую последовательность действий - много. Так что совокупная экономия может быть существенной. Во-вторых, и это пока для нас неочевидно (не дошли ещё по порядку изложения) запрос на ссылку на экземпляр может поступить из другого процесса, а то и с другой машины. И "перегонка" указателя на промежуточный и сугубо локальный "объект типа" от сервера к клиенту с полномасштабным маршалированием - ровно вдвое снижает эффективность вызова. Поэтому исполнение "типовой последовательности действий" локально опять выгодно. Вот почему функция CoCreateInstance присутствует в системе. Но, конечно, если вам нужно получить не одну, а несколько ссылок на экземпляры одного и того же статического типа, серия вызовов CoCreateInstance становится менее выгодной, чем раздельное получение ссылки на объект типа по CoGetClassObject и явный вызов IClassFactory::CreateInstance. Чем когда пользоваться должно быть понятно.

Наш пример, в той же технологии, как и пример №3 показывает транспаранты о том, что внутри сервера произошло событие. Я удалил транспаранты, связанные с работой DllRegisterServer и других экспортируемых функций, но оставил транспарант, который показывает, когда удаляется блок памяти - вы должны увидеть какая внутренняя работа совершается при вполне внешне "невинных" действиях. А как загружается и выгружается сервер вы видели из прошлого примера.

На что следует обратить внимание! Я понимаю, что к хорошему быстро привыкаешь и конечно мог бы совместить вызов Release с крестиком закрытия окна. Но намеренно оставил всё так, как продолжается, начиная с примера №2 - вы должны сами явно вызывать Release. Сделано это для того, чтобы вы могли увидеть как связаны элементарные COM-объекты, которые реализуют статические и нестатические аспекты типа (ещё и поэтому "фабрика класса" статического типа Banzai сделана не "как обычно"). Например, вы можете получить ссылку на объект типа, создать экземпляр, уничтожить объект типа и продолжать использовать экземпляр...

Для облегчения понимания "кто есть who" в состав диалогов введены дополнительные поля - они показывают численное значение адреса интерфейса COM-объекта, который "привязан" к этому "маленькому окну". Сделано это очень просто - желающие убедиться могут заглянуть в код, который обрабатывает событие WM_INITDIALOG.

Обратите внимение на то, каково будет это численное значение когда вы создаёте новый экземпляр объекта или просто клонируете ссылку. Обратите особенное внимание - когда адрес меняется? Например, для статического типа Hello "объект типа" реализован в статической памяти сервера, поэтому сколько бы вы ни запрашивали ссылок на "объект типа" сервер всё время будет возвращать одно и то же численное значение. А вот "объект экземпляра" у этого типа реализован в динамической памяти сервера, поэтому каждое новое создание экземпляра будет давать и новый адрес ссылки. Клонирование ссылки, естественно, нового объекта не создаёт - поэтому при клонировании любой ссылки адрес не будет изменяться, как бы объект ни был реализован.

Посмотрите, что произойдёт, если в любом месте у любого объекта на один раз больше вызвать Release, нежели AddRef. Не забывайте только, что "объект типа" статического типа Hello и "объект экземпляра" статического типа Banzai реализованы как статические объекты сервера, т.е. на них "Release не действует".

А со следующего выпуска мы начинаем новый, большой раздел - мы пересекаем границу процесса.

предыдущий выпуск

архив и оглавление

следующий выпуск


Авторские права © 2001, М. Безверхов
Публикация требует разрешения автора.


"); // -->

http://subscribe.ru/
E-mail: ask@subscribe.ru
Отписаться
Убрать рекламу
Рейтингуется SpyLog

В избранное