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

COMиксы

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

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

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

И вот, на этом пути нам встретилось место, которое при всей его очевидности, оказалось весьма трудным для понимания - я имею в виду "объект типа" и "объект экземпляра". Мои попытки объяснить это с позиций ООП, видимо, оказались не очень удачными - место это всё равно остаётся непонятным. Поэтому сегодня мы просто нарисуем его в системе общепринятых обозначений и ещё раз попробуем рассмотреть почему возникает эта проблема.

Итак, на рис.7 показано графическое обозначение интерфейса (ныне оно общепринято во многих средствах разработки, причем это обозначение именно "интерфейса", а не "интерфейса COM"):

Графическое обозначение интерфейса

рис 7. Графическое обозначение интерфейса

Это кружок, возле которого подписано название интерфейса. Кружок соединяется линией с сущностью, которая реализует (implements) этот интерфейс, а сама эта сущность изображается прямоугольником, как показано на рис. 8:

Графическое обозначение реализации интерфейса

рис 8. Графическое обозначение реализации интерфейса

Внутри прямоугольника подписывается имя этой сущности. Заметьте! Я намеренно не употребляю терминов "компонент" и "компонентный класс". Мне это поставили на вид некоторые читатели, но я должен сказать - что такое "компонентный класс" я знаю. И то, что нарисовано на рис. 8 до "компонентного класса" в том смысле, как он понимается в COM, немного не дотягивает. Более правильно пока считать, что на рис. 8 изображён "элементарный COM-объект", как мы его определили в статье Фабрикант, но не капиталист.

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

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

Графическое обозначение реализации CLSID

рис 9. Графическое обозначение реализации CLSID

Здесь показано, что имеется сущность - она обозначена надписью CLSID - в составе которой имеются два "элементарных COM-объекта", один из которых реализует статические аспекты типа, а второй - нестатические. Оба этих элементарных COM-объекта экспонируют интерфейсы, причём видимы их интерфейсы снаружи того агрегата, который именуется CLSID. Если бы это было не так, то обозначение "внутреннего интерфейса" находилось бы внутри объемлющей рамки. Видно, что сущность, реализующая статические аспекты типа реализует IUnknown и IClassFactory, а сущность, реализующая нестатические аспекты типа реализует IUnknown, IInterface и IAnoherInterface.

Что есть сам COM-сервер в таких обозначениях, я думаю, и рисовать не нужно - это ещё один прямоугольник внутри которого располагается одна или более картинок рис. 9. Их интерфейсы должны быть видимы снаружи сервера. Все, как мне кажется, интуитивно понятно.

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

На картинке рис. 9 совершенно понятно видно, что термином CLSID обозначается именно совокупная реализация пары "элементарных COM-объектов". Т.е. в составе "статического COM-типа", он в англоязычной и переводной литературе именуется также термином "COM-класс", имеются реализации двух различныхIUnknown. Для чего? Возвращаемся к началу - потому, что у типа, как философской категории, есть статические аспекты. Которые применимы именно к типу, а не к его экземплярам. Один из таких аспектов мы рассматривали - порождение экземпляра есть задача именно типа. Но! COM - явление времени исполнения, а не времени компиляции. И в COM нет другого способа взаимодействия, кроме как "вызов метода интерфейса". Поэтому то, что в обычном и привычном "процессе разработки с участием компилятора" делает компилятор (а это именно компилятор вставляет в нужном месте команды, которые порождают экземпляр типа) здесь нам приходится явно делать самим. И оформить эти команды нам придётся как нечто, экспонирующее интерфейсы. В моём изложении оно именуется "объект типа", в англоязычной и переводной - "объект класса", а в языке SmallTalk (если не изменяет память, возможно что это ещё и в Simula 67 - дедушке современных языков ООП) называлось "метакласс". К "классу" в его привычном понимании, в парадигме C++, это отношения не имеет. В C++ такого понятия в явном виде нет. Всю функциональность метакласса компилятор C++ делает "по месту" и прозрачно для программиста.

У типа также есть и нестатические аспекты, именно они составляют "экземпляр". И реализация в их составе еще и статических аспектов - методически неправильна. Ведь и в C++ статическая переменная это именно одна переменная на все экземпляры данного класса, а не переменная в составе каждого экземпляра класса? Именно поэтому "поведение типа" и "поведение экземпляра типа" - разделено и реализовано на базе двух разных примитивов. При этом "главным", конечно, является "экземпляр". Именно он скрывается за термином, употребляемом в обыденном смысле - "COM-объект". Именно с ним вы имеете дело, когда клиенту передаётся "ссылка на объект". Но и проигнорировать существование "объекта типа" вы не можете - без него не получить экземпляра!

Поэтому, в составе реализации COM-типа (понимаемого уже строго в смысле ООП) имеют место быть две связанные реализации двух разных аспектов. Обе реализованы так, как положено в COM. Каждую со стороны клиента можно рассматривать как самостоятельный COM-объект и обращаться с ней как с COM-объектом - "защёлкивать" и освобождать ссылки и т.д. Но нужно и понимать: хотя по обращению с ними это - "объекты", на самом деле эти "объекты" просто предоставляют доступ к разным аспектам типа, понимаемого уже как обособленного нечто, характеризующегося своим поведением и состоянием. И именно весь тип именуется CLSID, а не какая-то его часть. Программисты всех стран - любите философию!

Почему в COM "главным" является "экземпляр", а не "объект типа"? Почему в противоположность тому же C++ статические аспекты типа в COM находятся в явном загоне и не известно общепринятой реализации использовавшей бы "статические методы"? Ответ на этот вопрос находится ... в философии ООП и связан он с тем, что считать "вселенной". В обычной компилируемой программе "вселенная" одна на программу. То, что подано на вход компилятору (в пределе - линкеру) и составляет всё, что известно о программе и ни байтом больше. Вне этих пределов ничего относимого к программе не существует. В COM - не так. В COM вселенная не замкнута, в COM то, что сейчас компилируется или исполняется заведомо составляет только маленький кусочек этой вселенной. И требование уникальности CLSID - это следствие именно такого положения вещей. Но ведь "тип" в философском смысле - тоже понятие вселенского уровня. В нашей вселенной не может быть двух разных одинаковых типов. Категория "понятие" - всегда уникальна.

Если бы при этом нашу вселенную и представлял один суперкомпьютер, то не было бы и противоречия, которое вы, вероятно, уже начинаете замечать... Как быть, когда имеет место взаимодействие хотя бы двух разных компьютеров? На каждом из них известен один и тот же CLSID, представляющий один и тот же тип. Соответственно, на каждом из них порождается свой ... экземпляр типа. Заметьте противоречие! Тип - один, а экземпляров типа - более одного. Со строгой философской точки зрения такого быть не должно. Но и технически реализовать это не представляется возможным - нет способа взять и разделить реализацию, показанную на рис. 9 так, чтобы одна половина исполнялась в одном процессе, а вторая - в другом.

Если сюда добавить весьма прозаические проблемы, порождаемые всякими ограничениями доступа (не всякому клиенту может быть позволено адресовать даже известный тип), политикой безопасности и т.д., то выяснится, что учёт понятия "владение объектом" оказывается проще в реализации, чем высоколобая попытка "владение" не учитывать. Представьте себе, как с философской стороны, нелепо должна выглядеть ситуация, когда, например, есть тип T, который известен во вселенной состоящей из трёх компьютеров X, Y и Z. Это, в переводе с философского на программистский, означает, что "реализация статических аспектов" данного типа T разделяется всеми тремя машинами, она - одна и та же или одинакова. Т.е. либо каждая из них имееет одинаковую с другими копию, либо эта копия каким-то чудесным образом вообще одна общая. А машине X запрещено кое-что из доступа к машине Z... Получается, что по причинам совсем от COM не зависящим вся стройность философии нарушается. Ситуация становится другой, когда адресуется "экземпляр типа T на машине Z", ведь "экземпляр" в отличие от "типа" не есть один на всю вселенную, их может быть много и они имеют владельцев. И случай, когда владелец запрещает доступ в этой ситуации - совсем не нарушение философии. Напротив, парадигма, в которой можно эффективно решать такого рода проблемы хорошо известна человечеству.

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

Ну вот, мы и рассмотрели всё, что можно было бы назвать "первым классом COM". Нам впереди осталось только написать пример, который бы проиллюстрировал рассказанное - сделать фабрику класса, немного переписать клиента. И, наконец, - с шиком вызвать из клиента функцию CoCreateInstance, как источник единственной (а вовсе не множественной!) ссылки на "объект COM". Что и будет сделано в следующей статье - на базе того самого известного нам уже "примитивного примера".

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

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

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

форум

технология COM

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


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