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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

форум

технология COM

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


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