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

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


Служба Рассылок Subscribe.Ru проекта Citycat.Ru

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

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


Под флагом дуализма...

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

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

Если вы помните, некогда в нашей же рассылке говорилось, что понятие "объект" - довольно неточное в столь точной области, как computer science. Что гораздо корректнее и правильнее оперировать парой понятий - "статический тип" и "экземпляр статического типа". Только тогда мы прошли мимо ответа на важный экзистенциальный вопрос - а существует ли понятие "объект" вообще и как оно соотносится с названными понятиями?

Будучи программистом-практиком я, признаюсь, тоже над этим сильно не задумывался. COM - довольно стройная теория, т.е. обладая знаниями как выглядит система всегда можно заподозрить в каком месте следовало бы копнуть поглубже, а в каком это бессмысленно. Правило "наследования любого интерфейса от интерфейса IUnknown" - довольно однозначное, правило "реализация всех IUnknown" - единая для всех интерфейсов" - тоже, и для меня очень удивительно было обнаружить, что это - не работает. Выясняя причину и исследуя поведение "зарубежных аналогов" я натолкнулся на то, что... интерфейсов IUnknown в составе объекта должно быть не иначе, как две штуки. Почему - я понял позднее, когда до меня дошёл этот самый "важный экзистенциальный вопрос". А в этом номере рассылки я это и объясню. Заранее прошу простить меня за некоторую возможную занудность - тема действительно экзистенциальная и, бывает, что даже знающие практики C++ не сразу в неё "въезжают", а что уж говорить про наше заочное общение! Я понимаю, что для кого-то что-то из излагаемого ниже может быть всё равно непонятно. Не стесняйтесь задать вопросы об этом, поскольку наверняка в нашей аудитории найдется немало программистов, тоже оплативших это знание своей жизнью отданной отладке...

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

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

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

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

В этом-то, в том, что "реализация статического типа" является некоторой отдельной, обособленной, областью памяти и лежит секрет понимания "необходимости двух IUnknown": области памяти "типа" и "экземпляра" - разные области. Физически разные. Если вы помните, то IUnknown - интерфейс, который обслуживает действительно "экзистенциальные потребности" - он управляет временем жизни данного объекта. А что есть "объект" в понимании "его" IUnknown? Область памяти, занимаемая данными экземпляра. Что уничтожает IUnknown при освобождении последней ссылки? Область памяти, занимаемую данными экземпляра. Почему реализация IUnknown должна быть обязательно одна для всех интерфейсов данного объекта? Потому, что реализация всех интерфейсов обслуживается одной областью данных экземпляра...

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

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

А если понятно, то это - хорошо. Потому, что в мире для любого предмета всегда найдется хотя бы парочка-тройка "проклятых вопросов". Для Руси это всегда были вопросы "Что делать?", "Кто виноват?" и "Кому на Руси жить хорошо?"... Для найденного нами решения таковые тоже существуют. Как теперь нужно интерпретировать CLSID, если у нас, фактически две разных сущности, претендующих на название "объект"? К чему применять пару CLSID - IID если теперь у нас два "одинаковых разных" базовых интерфейса IUnknown? И, главное, как такую прелесть хотя бы "записать на C++"?!

Давайте подумаем имеем ли мы противоречие... и в чём именно. Во-первых, к чему относится CLSID, т.е. какую философскую сущность он выражает? CLSID именует статический тип. Обе наши протяжённости, хоть и являются разными, но безусловно относятся к одному статическому типу, т.е. вводить второй CLSID - неверное решение, не определяем же мы в C++ два разных класса - один для статических членов, а другой - для нестатических? Но как тогда применять IID, если у нас есть два IUnknown?.. А кто сказал, что наши разные сущности (протяженности) взаимозаменяемы и что они используются одновременно?

Говоря это я имею в виду следующее. Помнится, мы как-то говорили, что статический тип "отличается от других" именем, а экземпляр - адресом. Иными словами, имя никак не позволяет нам "достать" экземпляр, для этого-то как раз нужно иметь адрес. Но имя - не адрес, поэтому для любого статического типа внутри него мы обязательно должны иметь метод, который умеет "выдавать ссылки" на экземпляры этого типа - в C++ это оператор new, в COM - функция сервера DllGetClassObject... CLSID - это имя, следовательно, пара CLSID-IUnknown должна именовать IUnknown самого "объекта типа", а совсем не IUnknown "объектов экземпляров"... И уж затем мы должны обратиться к какому-то специальному механизму, реализованному в "объекте типа", который "наделает" нам столько ссылок на экземпляры, сколько нам будет нужно. И обратите внимание - при этом, если мы знаем ссылку на "объект типа", то мы можем уже и обращаться к нему как к любому другому COM-объекту, т.е. через интерфейсы. Таким образом, вся проблема вырождается в необходимость создавать экземпляры объектов не так, как мы их создавали все предыдущие рассылки, а с использованием "промежуточной ступени". В целом, если нам нужна ссылка на экземпляр статического типа, то нам нужно проделать следующую последовательность действий:

  1. Используя CLSID-IUnknown получить адрес интерфейса IUnknown "объекта типа";
  2. Запросить у этого IUnknown адрес специального интерфейса, реализуемого "объектом типа", который (интерфейс) умеет создавать экземпляры этого статического типа (интерфейс создателя экземпляров);
  3. Вызвать специальный метод этого интерфейса создателя экземпляров, передать ему IID того интерфейса, которого мы хотим добиться от "объекта экземпляра" (если это будет IID == IUnknown, то это будет уже "другой IUnknown");
  4. Получить возвращённую ссылку на интерфейс и вызывать методы уже "объекта экземпляра"...

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

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

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


Ну и в самом конце - немного рекламы. Уважаемые читатели, особенно - новоприсоединившиеся. Наш сайт поддержки рассылки переехал по адресу http://clubpro.spb.ru. Адреса http://about.al.ru больше не существует. К сожалению, у меня нет возможности изменить взаимные ссылки номеров рассылки, которые хранятся в архивах "Городского Кота". Но весь архив номеров рассылки есть и на сайте, он находится здесь. А ещё можно просто нажать среднюю ссылочку из тех, что вы видите сейчас ниже...

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

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

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

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


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


"); // -->

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

В избранное