![]() |
![]() ![]() ![]() ![]() ![]() |
![]() |
COM. Автоматизация - от простого к сложному. Часть IDelphi , Технологии , COM и DCOMCOM. Автоматизация - от простого к сложному. Часть I
Содержание:
1. Предисловие. В данной статье речь пойдёт об одной из COM-ориентированных технологий, которая занимает одно из ведущих мест при разработке программных средств, использующих технологию COM. Итак, разговор пойдёт об автоматизации. Хочу отметить, что даже если вы уже имели дело с автоматизацией в Delphi 2, то даже в этом случае вам стоит прочесть данную статью, т.к. методы, применяемые для создания автоматизации, коренным образом изменились, начиная с третьей версии Delphi. Я ориентировал данную статью на неискушённых в COM-программировании людей, попросту говоря, на новичков в данной области, поэтому я намеренно не вдавался в объяснения некоторых деталей, понимание которых может только запутать неискушённого читателя. По этой же причине (ориентированности на НЕпрофессионалов), некоторые вещи (очевидные для опытных людей) я разобрал с особой тщательностью. Прежде чем приступить к прочтению данной статьи, читателю, не знакомому с основными концепциями и терминами, используемыми при описании технологии COM, стоит хотя бы поверхностно с ними ознакомиться. По ходу статьи я всё же буду раскрывать некоторые термины, что бы избежать возможных разночтений, связанных с двояким пониманием одних и тех же терминов. 2. Введение. Начало разговора стоит начать, пожалуй, с рассказа о преимуществах автоматизации. Основным преимуществом является независимость от языка, т.е. контроллеры автоматизации могут управлять сервером независимо от языка, на котором был написан сервер (или клиент). Ваши программы могут быть "незаконченными", т.е. в них будет "заложена" возможность дополнения, расширения или даже изменения логики, интерфейса, возможностей. Таким образом, после незначительной работы другого программиста по программированию вашей программы (программированию в тех рамках, в которых вы это предполагали), ваш программный продукт сможет решать задачи, решения которых изначально не предполагалось. Конечно, имеются ещё немало плюсов, оценить которые вы сможете непосредственно работая с автоматизацией. Кроме того, стоит отметить, что автоматизация поддерживается на уровне операционной системы (Windows). Введём несколько терминов:
Таким образом, из вышеприведенных определений становится вполне понятным, что контроллеры автоматизации (КА) способны "программировать" сервер автоматизации (СА) с помощью некоторого макроязыка, предлагаемого СА. Возможно, кто-то спросит: "А почему бы ни использовать обычные сообщения Windows, т.е. не определить в своём приложении несколько смещений от WM_USER и не написать соответствующие методы на каждое из событий?". Ответ очевиден: во-первых, с помощью сообщений можно будет построить только примитивное программируемое приложение, причем, увеличивая его (приложения) сложность на одну единицу, громоздкость кода возрастёт на сотню; во-вторых, при использовании сообщений типа WM_USER вам придётся переписывать (override) процедуру окна или делать подмену (subclassing) окна, и в том и в другом случае ваше приложение будет тратить неоправданно много времени на проверку приходящих сообщений на предмет их принадлежности к зарегистрированным вами WM_USER. Объекты автоматизации (ОА) - это обычные COM-объекты, в которых помимо интерфейса IUnknown реализован интерфейс IDipatch. Интерфейс IDipatch определён в модуле System следующим образом:
Хочу сразу оговориться: если вы собираетесь воспользоваться средствами Delphi для реализации СА и КА, то вам вовсе не обязательно прямо сейчас вникать во все тонкости интерфейса IDipatch, т.к. Delphi как всегда максимально упрощает работу программиста, инкапсулируя автоматизацию (о преимуществах такого подхода, безусловно, можно спорить). Рано или поздно, так или иначе, но вы всё равно вернётесь к более глубокому рассмотрению интерфейсов и их реализаций, а сейчас этот параграф можно пропустить. Итак, подробно рассмотрим основную функцию интерфейса IDipatch:
Параметры и свойства:
Об остальных функциях интерфейса IDipatch предлагаю почитать в Help-е. Если вы дочитали до этого места, то наверняка сумеете вытерпеть ещё немного сухой теории, после которой мы перейдём к рассмотрению практической части, и вы сами убедитесь, что Delphi действительно делает разработку СА и КА делом простым и приятным ;) 3. Позднее и раннее связывание - что лучше?
Если вы разрабатываете объекты автоматизации с помощью встроенного в Delphi Automation Object Wizard (AOWizard), то вы автоматически получите ОА поддерживающий двойной интерфейс, т.е. вы сможете вызывать методы как с помощью метода Invoke(), так и с помощью потомков интерфейса IDipatch. Итак, пора уже переходить к практике и посмотреть, как всё это выглядит в коде. 4. Создание внешнего сервера автоматизации и контроллера для работы с ним. Прежде всего, стоит сказать, что собой представляет внешний СА. Внешний сервер автоматизации (LocalServer) - внешние СА являются выполняемыми файлами, которые могут создавать объекты автоматизации для использования их другими приложениями. Из названия понятно, что внешние СА выполняются в контексте своего собственного процесса. Как и все COM-объекты СА должны быть зарегистрированы, т.е. должны создавать такие же записи в реестре, как и все остальные COM-объекты, плюс два дополнительных параметра. Опять же, если вы реализуете СА в Delphi, то регистрация происходит автоматически при первом запуске СА при выполнении Application.Initialize(). Более подробное рассмотрение устройства и принципа работы внешних COM-серверов выходит за рамки данной статьи, поэтому предлагаю вам, при необходимости, заглянуть в соответствующую литературу. А теперь для иллюстрации технологии автоматизации создадим простенький внешний СА, в который поместим OLE-контейнер и добавим несколько методов к интерфейсу ОА. Итак, по шагам:
4.1). Запускаем Delphi и создаём новый проект, сохраняем его под именем Example1. На этом создание пользовательского интерфейса закончено и у вас должно получиться что-то похожее на это: ![]() (методы, обрабатывающие события от пунктов меню File, About, иконок открытия и сохранения не реализованы и введены только ради наглядности или, если угодно, как заготовки) Теперь добавим к нашему проекту ОА:
4.5). File -> New -> ActiveX 4.6). Выбираем иконку Automation Object: ![]()
4.7). После выбора данного пункта перед вами открывается первое окно мастера по созданию ОА, в котором вам предстоит ввести имя COM-класса создаваемого ОА, выбрать вариант использования ОА, выбрать потоковую модель и указать мастеру будет ли данный ОА поддерживать события. ![]() В пункте CoClass Name указываем имя нашего COM-класса - AutoServ1, в пункте Instancing указываем мастеру на то, что каждый экземпляр нашего сервера может создавать и экспортировать множество экземпляров OLE-объекта (естественно, этот пункт никак не связан с OLE-контейнером, который мы добавили в наш проект :) Так же в этом пункте можно указать значения Internal и Single Instance, первое означает, что создаваемый OLE-объект может быть использован только внутри приложения, т.е. внешние процессы не будут иметь к нему доступ и соответственно его не надо регистрировать в системе, второе означает, что каждый экземпляр сервера может экспортировать только один экземпляр OLE-объекта, т.е. если КА запрашивает другой экземпляр OLE-объекта, то запускается новый экземпляр приложения-сервера. Не стоит слишком долго ломать голову над данными определениями - просто потом сами попробуйте "поиграть" с этими параметрами и вы наглядно увидите в чём разница между ними и тогда эти определения станут для вас вполне очевидными. В пункте Threading Model выбираем разделяемую модель. События мы пока использовать не будем, а посему не выбираем Generate Event support code.
4.8). После нажатия на Ok мастер создаёт библиотеку типов для вашего проекта, в которой содержатся описания интерфейса и COM-класса; затем открывается окно редактора библиотеки типов, а к вашему проекту добавляется новый модуль, содержащий реализацию интерфейса автоматизации. Вот как должно выглядеть окно редактора библиотеки типов сразу после открытия: ![]()
4.9). Добавим необходимый метод к нашему интерфейсу; для этого в левой части окна выбираем IAutoServ1 и затем выбираем в панели инструментов активизировавшуюся иконку с зелёной стрелкой (New Method). Дадим ему имя NewOle. Затем добавим свойства. Для этого выбираем рядом расположенную иконку (New Property) и в её контекстном меню выбираем пункт Read | Write. Дадим им название Caption. Теперь перейдём к вкладке Parameters свойства Caption (выберем нижнее свойство, т.е. Write), для него установим тип BSTR - это строковый тип, используемый в автоматизации. После выбора типа BSTR для свойства Write, автоматически для свойства Read тип будет установлен BSTR*. Вот что у вас должно получиться: ![]()
4.10). Теперь нажимаем на иконку обновления (Refresh Implementation) и переходим к окну редактора кода, а конкретнее к автоматически сгенерированному модулю (Unit2.pas), видно, что редактор библиотеки типов автоматически добавил описание метода и свойства в класс TAutoServ1, который, как и все ОА является производным от базового класса TAutoObject, и создал каркасы для реализации метода, а так же для реализации процедур и функций чтения/записи свойства Caption. Заполним эти каркасы кодом:
В приведённом коде, думаю, всё понятно, стоит лишь объяснить, что представляет собой метод:
После компиляции получили файл Example1.exe, который является самостоятельным и законченным приложением. При нажатии на первую пиктограмму в панели инструментов открывается диалоговое окно "Вставка объекта", благодаря которому мы можем вставить в наш OLE-контейнер объекты различных типов. Обратите внимание на то, что раздел uses, автоматически созданного Unit2, содержит в себе Example1_TLB. Данный файл представляет библиотеку типов данного проекта в виде трансляции Object Pascal. Его содержимое вы можете посмотреть, выбрав View -> Units -> Example1_TLB. В данном модуле объявляется класс CoAutoServ1, конструктор которого выглядит следующим образом:
Т.е. для создания экземпляра объекта данного класса вам в своей программе достаточно вызвать метод CoAutoServ1.Create.Стоит пояснить использование механизма приведения типов в данном конструкторе. Дело в том, что функция CreateComObject() возвращает интерфейс IUnknown, который является родителем всех интерфейсов, при использовании механизма приведения типов происходит неявный вызов IUnknown.QueryInterface(), и в случае доступности интерфейса IAutoServ1 у созданного объекта с запрошенным clsid, возвращается уже собственно интерфейс IAutoServ1. Теперь создадим КА для управления внешним СА. Опять же, Delphi упрощает процесс создание КА. Сначала выберем способ связывания. Я буду рассматривать только раннее связывание, т.к. использование типа dispinterface или OleVariant хуже в плане производительности. Итак, начнём.
4.11). Создайте новый проект и нанесите на форму элементы, как показано на рисунке: ![]()
4.12). В раздел uses добавьте объявление модуля Example1_TLB (который должен находиться в том же каталоге, что и файлы только что созданного проекта, что бы не возникало путаницы, лично я размещаю проект СА и КА в одном каталоге, все файлы КА начинаются с cont_). В Private секции класса TForm1 опишем переменную типа IAutoServ1:
Процесс подсоединения к СА предельно прост:
Отсоединиться от сервера так же просто, для этого достаточно переменную FIntf установить равной nil. Естественно, при закрытии приложения КА любым из способов, соединение, созданное данным КА также освобождается. Если наш ОА используется в режиме Multiple Instance, то одновременно несколько КА могут работать с одним сервером автоматизации, из чего следует, что данный СА должен поддерживать счётчик клиентов и вести подсчёт ссылок (reference count) и когда число ссылок становится равным нулю - СА должен выгружать себя из памяти. Так оно и происходит - когда переменной интерфейса (в нашем случае FIntf) присваивается значение (FIntf:= CoAutoServ1.Create;), компилятор автоматически генерирует вызов метода _AddRef() интерфейса IUnknown (который, как вы помните, является базовым интерфейсом в технологии COM), тем самым увеличивая содержание счётчика ссылок. Когда переменная интерфейса принимает значение nil или выходит за область видимости, генерируется вызов метода _Release(), что уменьшает содержимое счётчика. Наглядно это можно увидеть, запустив сразу несколько КА примера 1 (не забудьте, что для регистрации внешнего СА в реестре системы после его создания, необходимо запустить его один раз "вручную", т.е. не через КА). Запускаем два КА и в каждом нажимаем на кнопку "Connect to automation server" - запускается один СА и теперь с ним одновременно могут работать оба запущенных КА. Попробуйте закрыть (или нажать на кнопку "Disconnect from automation server") один из КА - работа СА продолжается, и вы можете с ним работать с помощью оставшегося одного КА, но если вы закроете (или нажмёте на кнопку "Disconnect from automation server") последний из оставшихся КА, то СА выгрузит себя автоматически.
4.13). Реализация метода для события нажатия на кнопку Insert Object выглядит следующим образом:
Для кнопки Get Caption:
Для кнопки Change Caption:
Как вы уже, наверняка, заметили, работа с СА после подсоединения к нему не составляет никаких проблем, и все виды взаимодействий между КА и СА происходят через переменную интерфейса Fintf, работа с которой практически не отличается от работы с обычными переменными-указателями на экземпляры объектов классов. 5. Создание внутреннего сервера автоматизации. Внутренний сервер автоматизации (InProcServer) - это библиотека (DLL), которая может создавать ОА. Как и любой внутренний COM-сервер, внутренний СА должен экспортировать четыре стандартных функции:
Все эти функции реализованы в модуле ComServ, поэтому вам просто придётся добавить эти функции в раздел exports проекта. Кратко о назначении этих функций:
Более подробно узнать о тонкостях механизма создания и работы внутренних COM-серверов вы можете из литературы, приведённой в конце статье, я же рассмотрю конкретный пример создания внутреннего сервера автоматизации. Начнём:
5.1). Запускаем Delphi и создаём новую библиотеку (DLL), которую назовём Example2 (хотя можно использовать и любую из ранее созданных вами библиотек, превратив её во внутренний СА).
5.3). Для создания ОА повторим все шаги, проделанные нами в пунктах с 4.5 по 4.9 при разработке внешнего СА. Только в данном случае в качестве имени Com-класса выберем AutoServ2, для свойства - MyMessage, а для метода - ShowMyMessage.
Примечание:
лично у меня (Delphi Enterprise Version 6.0 (Build 6.163) + Win2k Prof SP2), в таком варианте на этапе сборки возникала ошибка "File no found '.TLB'", хотя файл на самом деле присутствовал. Явное указание имени файла решило проблему:
5.5). Перед тем как заполнить каркасы кодом создадим секцию private в автоматически созданном классе TAutoServ2 и объявим в этой секции строковую переменную, в которой будет храниться принятое сообщение. Заполним каркасы следующим образом:
Не забудьте в секцию uses добавить заголовочный файл Windows. После компиляции проекта вы получаете файл Example2.dll, который является внутренним СА. Созданный внутренний СА необходимо зарегистрировать в системе. Внутренние СА регистрируются не так как внешние. Для регистрации внутреннего СА служит функция DllRegisterServer(), если вы работаете в среде Delphi, то процесс регистрации упрощается: у вас должен быть открыт проект вашего внутреннего СА, если это сделано, то в меню Run выбираем Register ActiveX Server. После сообщения об удачном завершении регистрации вы можете использовать только что созданный внутренний СА. Теперь создадим КА для управления созданным СА. 6. Создание контролера для управления внутренним сервером автоматизации. 6.1). Как всегда - создаём новый проект, создаём форму, типа той, что на рисунке и сохраняем только что созданный проект в тот же каталог, где у вас находится внутренний СА под именем Cont_Example2. ![]() 6.2). Добавляем в раздел uses Example2_TLB и ComObj - теперь вы знаете, что для чего нужно. 6.3). В раздел private класса TForm1 добавляем переменную типа IAutoServ2 (хотя если вы её объявите как AutoServ2 - это не приведёт к ошибке, т.к. в Example2_TLB.pas мы видим следующее: AutoServ2 = IAutoServ2; - данная строка сопоставляет CoClass AutoServ2 его интерфейсу IAutoServ2 ). 6.4). Теперь реализуем необходимые методы.
Я не стал загромождать код комментариями, т.к., по моему мнению, в нём всё предельно ясно и понятно. Возможно, вы обратили внимание, что все обращения к внутреннему СА из КА я обрамляю конструкцией try … except; дело в том, что, во-первых, очень часто при работе с COM ошибки в коде программы приводят, во время выполнения готовой программы, к ошибкам связанным с отказом в доступе (Access Violation), и выводимая аналитическая информация, мягко говоря, не очень информативна, во-вторых, не следует забывать, что внутренний СА работает в контексте управляющего им приложения (т.е. в адресном пространстве КА), и крах СА с большой вероятностью приведёт к краху управляющего приложения, что, в свою очередь, может привести к потере важных данных. А, используя конструкцию try … except, вы можете избежать этих неприятностей или, по крайней мере, сгладить пост травматический шок у пользователя, вызванный потерей результатов напряжённой трёхчасовой работы ;) Вот, собственно, и всё - компилируем, запускаем, пробуем. 7. Обеспечение совместимости созданных СА с другими языками и средами разработки. Теперь поговорим о том, как сделать уже готовые сервера автоматизации доступными для "понимания" программами (КА), при написании которых могут использоваться различные языки программирования. Автоматизация решает проблему " взаимопонимания " с помощью механизма, позволяющего ассоциировать информацию о типе объекта с самим объектом автоматизации с помощью так называемых библиотек типов (type library). В этих библиотеках хранится информация обо всех интерфейсах, классах, типов данных и компонент сервера. Библиотека типов может быть добавлена как ресурс к вашему приложению-серверу (либо DLL) или находится во внешнем файле. Информация из библиотеки типов предоставляется сервером клиенту. Работая с мастерами по созданию COM-приложений в Delphi, вы получаете автоматически сгенерированную библиотеку типов и файл, содержащий преобразованную информацию из данной библиотеки в язык Object Pascal. Как вы помните, при написании КА мы включали в раздел uses название модуля, содержащего представление библиотеки типов разрабатываемого проекта в виде трансляции Object Pascal. Именно этот pas-файл позволяет вашему КА "понимать" интерфейс СА. Независимо от того находится ли файл библиотеки типов в приложении-сервере как ресурс или он "вынесен наружу", вы всегда сможете получить его трансляцию в той языковой среде, в которой вы разрабатываете КА. Для этого в каждой среде разработки есть свои средства. В частности с Delphi поставляется утилита Tlibimp, которая позволяет выполнять импорт существующей библиотеки типов в синтаксис Object Pascal, C++ и в IDL. Запустите эту утилиту без параметров и посмотрите на список опций, доступных при работе с этой утилитой. К примеру, у вас имеется внутренний СА в виде DLL (пусть будет Example2.dll, которую мы ранее разработали самостоятельно) и библиотека типов включена в него в качестве ресурса (как это сделано в нашем примере). В таком случае для того, что бы получить pas-файл, описывающий библиотеку типов данного СА, вам необходимо выполнить следующие действия:
В результате вы получите два файла: Example2_TLB.dcr и Example2_TLB.pas, которые вы можете использовать при разработке своего КА. То же и с tlb-файлами и exe-файлами:
Для импорта сразу в С++ и Object Pascal синтаксис выполните следующее:
В этом случае помимо pas-файла будут, соответственно, созданы cpp-файлы и файлы заголовков (h-файлы). 8. Послесловие. Опытные программисты, ранее уже имевшие дело с автоматизацией, или хотя бы понимающие, что она собой представляет, могут заметить, что в рассмотренных примерах автоматизации в "чистом виде" очень и очень немного. Действительно - это так, но не забывайте, что эта статья является первой частью гипотетического цикла. Целью этой части было дать в доступном виде начальные понятия и представления, которых так, порой, не хватает начинающим программистам, и отсутствием которых не редко страдают некоторые книги и статьи. Подводя итог можно сказать, что с простой частью автоматизации мы разобрались и научились создавать простенькие сервера и контроллеры автоматизации. Если данная тема (и статья соответственно) вызовет интерес общественности, то я продолжу рассказывать о применении технологии автоматизации. В следующих статьях я предполагал рассказать о более сложных технологиях, входящих в понятие "Автоматизация", таких как события автоматизации (события COM), события автоматизации при работе в Delphi, события с несколькими стоками, коллекции автоматизации и их реализация в Delphi и ещё кое-что интересное с точки зрения Delphi-разработчика. P.S. Отдельным пунктом выражаю благодарность всем участникам дискуссии на тему способов и механизмов связывания на форуме сайта delphi.mastak.ru. Всегда приятно, когда в обсуждении тех или иных вопросов преобладает обстоятельность и обоснованность, а не аляповатая убеждённость в непогрешимости собственных знаний или, что ещё хуже, наглое невежество и неуважение к оппонентам. 9. Список использованной литературы.
10. Архивы проектов. This is a detailed and technical article about creating automation servers (AS) using Delphi, a programming language. The article provides an overview of the concept of automation, explains how to create an in-process automation server (IS), and demonstra Комментарии и вопросыПолучайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта. :: Главная :: COM и DCOM ::
|
|||||||||||||||||||||||||||
©KANSoftWare (разработка программного обеспечения, создание программ, создание интерактивных сайтов), 2007 |