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

COM в эпоху неолита

Ну вот, теперь можно сказать - теоретически мы знаем все базовые структуры и необходимые механизмы для того, чтобы организовать взаимодействие двоичных компонентов. Знаем почему они требуются, из каких соображений они именно такие, а не какие-то другие. Самое время сочинить первый пример - проверить изложенную теорию практикой. И мы в этой статье его сочиним.

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

Общий замысел примера. Делаем два разных программных проекта и в каждом собираем исполняемый модуль. Один будет клиент, другой - сервер. Организуем их взаимодействие - клиент запросит ссылку на объект сервера и будет вызывать его методы. Если наша теория верна, то между ними действительно будет требоваться всего только один файл в исходном тексте - описание интерфейсов, которыми взаимодействуют между собой клиент и сервер. Этот файл должен быть одинаков для обоих проектов. Все остальные файлы - должны получиться сугубо локальными для каждого модуля. Сервер вообще не должно интересовать, кто у него клиент, а клиент не должен знать "что у сервера внутри". После сборки модулей "приводим их в соприкосновение" и ... всё должно работать, т.е., по крайней мере - не обрушиваться, а демонстрировать, что изложенные механизмы-то работают.

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

Ход конструирования. Поймём, чего мы хотим. Мы хотим, чтобы клиент вызвал объект сервера. При этом клиент будет вызывать метод объекта сервера и показывать результаты её работы нам, а сервер будет исполнять эту работу по запросу клиента. Первое, что мы в таком случае должны определить - как будут взаимодействовать клиент и сервер. Т.е. самым первым шагом мы должны сконструировать именно интерфейс между клиентом и сервером. Не объекты, не их реализации, а - именно интерфейс. Я предлагаю в качестве примера простой интерфейс - метод Show, который заставит сервер показать какой-то транспарант и метод Sound, который заставит сервер произвести какой-то звук. Этот интерфейс описывается простым классом:

class NeoInterface{ public:     int Show (HWND hWhere);     int Sound(); };

Для того, чтобы это был действительно интерфейс, как мы ранее выяснили, его нужно сделать виртуальным чисто абстрактным классом:

class NeoInterface{ public:     virtual int Show (HWND hWhere) = 0;     virtual int Sound() = 0; };

Далее, в составе сервера определим два статических типа, которые он будет в состоянии предоставлять клиенту - тип Banzai и тип Hello, оба этих типа будут произведены от одного и того же интерфейса. Поэтому методы, которые они покажут (на профессиональном сленге COM-программистов надо сказать - "экспонируют") клиенту у них будут одинаковы, но делать эти методы будут несколько разные действия: объект типа Banzai будет показывать транспарант с надписью "Банзай", а объект типа Hello - с надписью "Хелло". Соответственно, и звуки которые будет производить метод Sound в составе разных объектов тоже будут разными.

Клиент создаст (запросит сервер о создании) по одному экземпляру объектов каждого типа и вызовет их методы с демонстрацией нам того, что методы работают.

Как было рассмотрено в статье Клиенту - клиентово, а серверу - серверово мы перенумеруем интерфейс (абстрактный тип) и статические типы, причём сделаем это "цивилизованно" - перенумеруем их GUIDами, а не обычными номерами. Для нас сейчас это неважно - чем, но, если невзначай можно воспользоваться плодами цивилизации, то почему бы и не продемонстрировать, как можно их использовать?

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

Готовый код примера можно выкачать здесь. Пример написан на чистом C++ без применения MFC или ATL и должен работать везде. Пример состоит из двух проектов: NeoClnt - проекта клиента и NeoSrv - проекта сервера. Их следует последовательно собрать, а затем скопировать модуль NeoSrv\Debug\NeoSrv.dll в каталог NeoClnt\Debug и запустить модуль NeoClnt.exe. Модуль покажет диалог с четырьмя кнопками - вызов методов объекта Banzai и вызов методов объекта Hello.

Соберите модули. Исследуйте их функционирование. Изучите исходные тексты - они хорошо откомментированы "по месту", поэтому почему проект сделан именно так, а не иначе здесь говорить излишне - комментарии "почему так" сделаны следующей статьёй. Запустите dumpbin.exe и исследуйте таблицу EXPORTS модуля NeoSrv.dll

Обратите внимание, что нам действительно удалось сделать компонентное приложение. Модуль NeoSrv.dll не экспортирует никаких функций, кроме DllGetClassObject - методы наших объектов вызываются без линковки.  Обратите особое внимание, что при сборке модуля NeoClnt.exe нам не потребовалась библиотека NeoSrv.lib - если бы у нас статически линковались вызовы из DLL нам бы она требовалась, а клиент без нее не мог бы быть собран. И она - была бы разной для разных версий DLL, её приходилось бы всякий раз перелинковывать к клиенту. Обратите внимание, что вы совершенно свободны в изменении содержимого NeoSrb.dll без пересборки клиента - до тех пор, пока вы не трогаете описание интерфейса, использованного при создании клиента, и, естественно, пока вы не изменяете  нумерацию статических типов и самого интерфейса. Вам нужно только всякий раз помещать новую версию DLL на то место, где располагалась версия старая - это дефект "живописи эпохи неолита".

А ведь к "настоящему COM" мы ещё и не приступили! Для настоящего COM нам не хватает ещё чуть-чуть... чего именно мы установим после анализа того, что обнаружили в данном примере. Анализ - в следующей статье.

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

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

форум

технология COM

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


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