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

Типа библиотека...

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

На самом деле "большая проблема", к которой мы подходим, и которую в предыдущей статье смогли увидеть в виде частного решения проблемы связи компонентов из разных процессов, формулируется совсем иначе. Собственно, ради поиска технического решения именно этой "большой проблемы" и появились технологии компонентного программирования, наподобие COM. При этом COM и CORBA, JavaBeans - весьма раскрученные имена, находящиеся у всех на слуху. А сколько существует безвестных, частных в пределах конкретного проекта, но совершенно похожих на COM (в смысле, что они являются какими-то решениями той же проблемы) решений? Проблема эта описывается, обычно, двумя обстоятельствами:

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

Налицо ситуация, когда не может существовать одной головы, которая бы могла знать все детали разрабатываемого проекта на всех уровнях, а стало быть, возникают три потенциальных источника неэффективности:

  1. Задача может не решаться эффективно на каком-то одном языке. Может оказаться так, что реализация её частей на разных языках - окажется гораздо эффективнее;
  2. Объём проекта настолько велик, либо проект настолько растянут во времени, что попытки "постичь его целиком" и на основе этого знания оптимизировать - заранее обречены на провал;
  3. В командной разработке может сложиться ситуация, когда одни программисты, сами того не желая, создают бесплодную работу для других - изменят из самых лучших побуждений формат какой-нибудь структуры, а все пользователи этой структуры - будут переделывать свой код. А затем - в полном объёме тестировать.

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

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

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

Рассматривая все перечисленные проблемы по отдельности трудно увидеть, что они являются просто разными сторонами одного и того же явления. Но рассматривая их вместе можно заметить, что все они могут быть решены в одном месте и одним средством. И средство это должно:

  1. позволять разработчику абстрактно, т.е. без оглядки на какую-то реализацию, описать интерфейс (набор интерфейсов) или компонент;
  2. порождать какие-то изоморфные описанию из п.1 струкуры, которые бы описывали механизм связи компонентов, реализуемый операционной системой;
  3. порождать какие-то изоморфные описанию из п.1 струкуры, которые бы описывали интерфейсы "в понятном двоичному компоненту" виде;

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

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

На уровне программиста-разработчика оно представляет собой Interface Definition Language (IDL) - специальный язык на котором абстрактно можно описать интерфейсы, компонент, как набор интерфейсов, реализуемых вместе, и сервер, как набор компонентов, располагающихся вместе. На уровне средства разработки оно представляет собой специальный компилятор MIDL, который позволяет откомпилировать составленное программистом описание и получить изоморфные ему структуры - реализацию proxy и stub и файл в специальном формате, содержащий описания IDL в двоичном, "понятном компоненту" виде. Этот файл носит название "библиотека типов" (Type Library) и совершенно однозначно и одинаково воспринимается всем двоичными компонентами (если, конечно, разработчик компонента такое восприятие включил - в наших примерах до сих пор мы обходились без библиотеки типов).

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

IDL представляет собой специальный язык, который позволяет описать библиотеку типов. IDL является изобретением Microsoft в такой же степени, в какой является её изобретением и GUID, т.е. он просто был приспособлен Microsoft для собственных нужд. Ранее Microsoft поддерживала ODL - Object Definition Language, который являлся своего рода подмножеством IDL. Делал из ODL-файла библиотеку типов компилятор Mktyplib.exe, он и сейчас поставляется в составе VC++ 6.0.

Идея IDL (в применении к компонентной разработке в COM) состоит в том, что необходимо дать разработчику возможность естественным для него образом описать конструкции, составляющие Type Library. И при этом - не дать возможности описать что либо сверх просто абстрактного определения "присоединительных размеров" сервера. Это невозможно сделать ни на одном языке программирования, поскольку, например, интерфейс это не только vtbl, но и ассоциированный с ним GUID. А описание компонента в виде хотя бы класса в C++, явно некорректно - компонент может содержать не только классы, реализующие интерфейсы, но и "просто классы", т.е. нарушается абстрагированность от языка реализации. Наличие же IDL, как говорилось выше, эти проблемы решает - компилятор с него произведёт все необходимые для данного языка программирования конструкции совершенно автоматически, а программист к этим конструкциям может что-то добавить, если сочтёт нужным.

Как выглядит IDL можно посмотреть в одном из стандартных файлов, поставляемых вместе со средой программирования. В каталоге C:\Program Files\Microsoft Visual Studio\VC98\Include находится изрядное количество файлов с расширением *.idl, в которых описаны не то что библиотеки, а целые пласты всевозможных интерфейсов. Мы только отметим, что IDL чем-то по своей структуре похож на C/C++. Видимо, потому, что для разработки компонентов на этих языках первично и предназначался. Если же вы программируете в другой среде, скажем, в Visual Basic, то знание IDL для вас может оказаться излишним, т.к. компилятор с VB умеет создавать библиотеку типов в двоичном виде сам. А вот конструкции IDL, соответствующие той или иной конструкции COM мы отныне будем указывать в нашей серии - я не вижу более удобного способа изучить IDL, как одновременно с изучением какого-то явления в COM увидеть как оно записывается на языке IDL. IDL в COM столь же всепроникающая материя, как и системный реестр.

Компилируя IDL-файл MIDL выдаёт в числе прочих результатов файл с расширением *.tlb - это в точности исходное описание типов, но уже - в упакованном двоичном формате. Оно является стандартным описанием для всех компонентов COM, т.е. все средства разработки, которые поддерживают COM обязаны этот формат понимать. Это и позволяет говорить о том, что сервер, разработанный на одном языке можно будет использовать в паре с клиентом, разработанным на другом языке. Другим употребительным расширением для файла библиотеки типов является *.olb, да к тому же библиотека типов может существовать в виде отдельного файла, а может быть включена в состав ресурсов модуля (*.exe, *.dll) - по своей сущности библиотека типов является просто обособленным набором двоичных структур в некотором стандартном для среды COM формате.

Эта "стандартность" и прямое соответствие конструкций IDL двоичным структурам позволяет произвести обратное преобразование - из двоичного формата восстановить исходный IDL-файл, что, например, умеет делать утилита Oleview.exe, поставляемая вместе с Visual-средой. Просматривая в ней библиотеку типов можно увидеть, что утилита эта восстанавливает исходные предложения IDL, да ещё умеет это делать в нескольких форматах. К сожалению, это востановление приносит не точно "исходный IDL", а только лишь восстановленное до IDL содержимое данной библиотеки типов. Разница становится понятной, если попытаться откомпилировать восстановленный текст. Компилироваться он будет не всегда, в некоторых случаях MIDL заявит, что некоторого типа (который в воссстановленной IDL упомянут) он не обнаруживает. Такое происходит потому, что компилятор генерирует-то "библиотеку типов", но производит синтаксическую проверку "единицы компиляции". Поэтому, если в "единицу компиляции" включено ещё что-то предложениями #include, то в "единице компиляции" оно присутствовать будет, но в библиотеке типов - нет. В целом это - одолимая проблема, к тому же двоичный формат библиотеки типов понимается не только MIDL и Oleview, так что восстанавливать библиотеку типов до IDL приходится только для понимания её самому программисту.

Библиотека типов - своеобразный компонент, или, точнее - конструкция среды компонентной разработки. Она "применяется автоматически" только в высокоуровневых средах разработки, таких как Visual Basic. Программист на C/C++ должен предпринимать специальные действия внутри собственной программы, чтобы можно было эту библиотеку типов использовать. О том, как это делается - в статье, реализующей очередной пример.

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

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

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

форум

технология COM

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


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