Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
COM. Автоматизация - от простого к сложному. Часть I

Автор: Алексей Павлов
Cпециально для Королевства Delphi

"Одно лишь то, что что-то не делает того, что вы ему запланировали делать, не означает, что оно бесполезно".

Т. Эдисон

Содержание:

  1. Предисловие.
  2. Введение.
  3. Позднее и раннее связывание - что лучше?
  4. Создание внешнего сервера автоматизации и контроллера для работы с ним.
  5. Создание внутреннего сервера автоматизации.
  6. Создание контролера для управления внутренним сервером автоматизации.
  7. Обеспечение совместимости созданных серверов автоматизации с другими языками и средами разработки.
  8. Послесловие.
  9. Список использованной литературы.
  10. Архивы проектов.

1. Предисловие.

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

Хочу отметить, что даже если вы уже имели дело с автоматизацией в Delphi 2, то даже в этом случае вам стоит прочесть данную статью, т.к. методы, применяемые для создания автоматизации, коренным образом изменились, начиная с третьей версии Delphi.

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

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

По ходу статьи я всё же буду раскрывать некоторые термины, что бы избежать возможных разночтений, связанных с двояким пониманием одних и тех же терминов.

2. Введение.

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

Введём несколько терминов:

Автоматизация (automation или OLE-automation)
технология, позволяющая приложениям и библиотекам (DLL) предоставлять свои программируемые объекты с целью их использования в других приложениях.
Сервера автоматизации (automation servers)
приложения и библиотеки (DLL), предоставляющие свои программируемые объекты.
Контроллеры автоматизации (automation controllers)
приложения, получающие доступ к управлению программируемыми объектами, предоставляемыми серверами автоматизации.

Таким образом, из вышеприведенных определений становится вполне понятным, что контроллеры автоматизации (КА) способны "программировать" сервер автоматизации (СА) с помощью некоторого макроязыка, предлагаемого СА. Возможно, кто-то спросит: "А почему бы ни использовать обычные сообщения Windows, т.е. не определить в своём приложении несколько смещений от WM_USER и не написать соответствующие методы на каждое из событий?". Ответ очевиден: во-первых, с помощью сообщений можно будет построить только примитивное программируемое приложение, причем, увеличивая его (приложения) сложность на одну единицу, громоздкость кода возрастёт на сотню; во-вторых, при использовании сообщений типа WM_USER вам придётся переписывать (override) процедуру окна или делать подмену (subclassing) окна, и в том и в другом случае ваше приложение будет тратить неоправданно много времени на проверку приходящих сообщений на предмет их принадлежности к зарегистрированным вами WM_USER.

Объекты автоматизации (ОА) - это обычные COM-объекты, в которых помимо интерфейса IUnknown реализован интерфейс IDipatch. Интерфейс IDipatch определён в модуле System следующим образом:


type
  IDispatch = interface(IUnknown)
    ['{00020400-0000-0000-C000-000000000046}']
    function GetTypeInfoCount(out Count: Integer): HResult; stdcall;
    function GetTypeInfo(Index, LocaleID: Integer; out TypeInfo): HResult; stdcall;
    function GetIDsOfNames(const IID: TGUID; Names: Pointer;
      NameCount, LocaleID: Integer; DispIDs: Pointer): HResult; stdcall;
    function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
      Flags: Word; var Params; VarResult, ExcepInfo, ArgErr: Pointer): HResult; stdcall;
end;

Хочу сразу оговориться: если вы собираетесь воспользоваться средствами Delphi для реализации СА и КА, то вам вовсе не обязательно прямо сейчас вникать во все тонкости интерфейса IDipatch, т.к. Delphi как всегда максимально упрощает работу программиста, инкапсулируя автоматизацию (о преимуществах такого подхода, безусловно, можно спорить). Рано или поздно, так или иначе, но вы всё равно вернётесь к более глубокому рассмотрению интерфейсов и их реализаций, а сейчас этот параграф можно пропустить.

Итак, подробно рассмотрим основную функцию интерфейса IDipatch:

Invoke()
при получении клиентом указателя IDipatch на СА, он (клиент) может вызвать данный метод для выполнения определённых методов на сервере.

Параметры и свойства:

  • DispID - содержит диспетчерский идентификатор (dispatch ID), это число, которое показывает какой метод должен быть вызван в сервере.
  • IID - на данный момент не используется (по имеющимся у меня на данный момент сведениям).
  • LocaleID - содержит информацию о языке.
  • Flags - определяет какого типа метод будет вызван в сервере: метод доступа к свойствам или обычный метод.
  • Params - содержит указатель на массив TDispParams, который содержит параметры, передаваемые этому методу.
  • VarResult - это указатель на переменную типа OleVariant, в которую будет записано возвращаемое значение вызываемого метода.
  • ExcepInfo - указатель на запись типа TExceptInfo, которая будет содержать информацию об ошибке, в случае, если метод Invoke() возвращает значение DISP_E_TYPEMISMATCH или DISP_E_PARAMNOTFOUND.
  • ArgErr - указатель на целое число (индекс некорректного параметра в массиве Params).

Об остальных функциях интерфейса IDipatch предлагаю почитать в Help-е.

Если вы дочитали до этого места, то наверняка сумеете вытерпеть ещё немного сухой теории, после которой мы перейдём к рассмотрению практической части, и вы сами убедитесь, что Delphi действительно делает разработку СА и КА делом простым и приятным ;)

3. Позднее и раннее связывание - что лучше?

Позднее связывание (late binding)
позднее связывание имеет место в том случае, если необходимый метод вызывается с помощью метода Invoke() интерфейса IDipatch, т.е. вызов метода невозможен до момента выполнения программы, т.к. требуемый адрес просто неизвестен (при вызове методов с помощью типа Delphi Variant или OleVariant мы тоже имеем дело с поздним связыванием).
Раннее связывание (early binding)
смысл раннего связывания заключается в том, что параметры DispID для вызываемых методов разрешаются ещё на этапе компиляции, что исключает временные накладки, связанные с вызовом метода GetIDsOfNames() перед вызовом одного из методов, как это делается при позднем связывании. При раннем связывании КА могут вызывать объекты автоматизации непосредственно с помощью виртуальных таблиц (vtable), т.е. не используя IDipatch. Invoke(), таким образом, доступ к требуемому методу происходит быстрее, чем при позднем связывании. Раннее связывание используют при вызове метода с помощью типа interface Delphi.

Если вы разрабатываете объекты автоматизации с помощью встроенного в Delphi Automation Object Wizard (AOWizard), то вы автоматически получите ОА поддерживающий двойной интерфейс, т.е. вы сможете вызывать методы как с помощью метода Invoke(), так и с помощью потомков интерфейса IDipatch.

Итак, пора уже переходить к практике и посмотреть, как всё это выглядит в коде.

4. Создание внешнего сервера автоматизации и контроллера для работы с ним.

Прежде всего, стоит сказать, что собой представляет внешний СА.

Внешний сервер автоматизации (LocalServer) - внешние СА являются выполняемыми файлами, которые могут создавать объекты автоматизации для использования их другими приложениями. Из названия понятно, что внешние СА выполняются в контексте своего собственного процесса. Как и все COM-объекты СА должны быть зарегистрированы, т.е. должны создавать такие же записи в реестре, как и все остальные COM-объекты, плюс два дополнительных параметра. Опять же, если вы реализуете СА в Delphi, то регистрация происходит автоматически при первом запуске СА при выполнении Application.Initialize(). Более подробное рассмотрение устройства и принципа работы внешних COM-серверов выходит за рамки данной статьи, поэтому предлагаю вам, при необходимости, заглянуть в соответствующую литературу.

А теперь для иллюстрации технологии автоматизации создадим простенький внешний СА, в который поместим OLE-контейнер и добавим несколько методов к интерфейсу ОА.

Итак, по шагам:

4.1). Запускаем Delphi и создаём новый проект, сохраняем его под именем Example1.
4.2). На форму добавляем для красоты CoolBar1 на неё ToolBar1 и уже на неё ToolButton-ы.
4.3). Добавляем MainMenu1 и добавляем в него соответствующие пункты.
4.4). Я ещё добавил ImageList1 и ActionList1 для удобства.

На этом создание пользовательского интерфейса закончено и у вас должно получиться что-то похожее на это:

(методы, обрабатывающие события от пунктов меню 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. Заполним эти каркасы кодом:

procedure TAutoServ1.NewOle;
begin
  Form1.ActionList1.Actions[0].Execute;
end;

function TAutoServ1.Get_Caption: WideString;
begin
  Result:= Form1.Caption;
end;

procedure TAutoServ1.Set_Caption(const Value: WideString);
begin
  Form1.Caption:= Value;
end;

В приведённом коде, думаю, всё понятно, стоит лишь объяснить, что представляет собой метод:


Form1.ActionList1.Actions[0].Execute;


procedure TForm1.NewOleExecute(Sender: TObject);
begin
  try
    OleContainer1.InsertObjectDialog;
  except
    MessageBox(0, PChar('Ошибка при попытке внедрения OLE-объекта.'),
    PChar(Form1.Caption), MB_OK);
    Raise;
  end; {try}
end;

После компиляции получили файл Example1.exe, который является самостоятельным и законченным приложением. При нажатии на первую пиктограмму в панели инструментов открывается диалоговое окно "Вставка объекта", благодаря которому мы можем вставить в наш OLE-контейнер объекты различных типов.

Обратите внимание на то, что раздел uses, автоматически созданного Unit2, содержит в себе Example1_TLB. Данный файл представляет библиотеку типов данного проекта в виде трансляции Object Pascal. Его содержимое вы можете посмотреть, выбрав View -> Units -> Example1_TLB. В данном модуле объявляется класс CoAutoServ1, конструктор которого выглядит следующим образом:


class function CoAutoServ1.Create: IAutoServ1;
begin
  Result := CreateComObject(CLASS_AutoServ1) as IAutoServ1;
end;

Т.е. для создания экземпляра объекта данного класса вам в своей программе достаточно вызвать метод CoAutoServ1.Create.Стоит пояснить использование механизма приведения типов в данном конструкторе. Дело в том, что функция CreateComObject() возвращает интерфейс IUnknown, который является родителем всех интерфейсов, при использовании механизма приведения типов происходит неявный вызов IUnknown.QueryInterface(), и в случае доступности интерфейса IAutoServ1 у созданного объекта с запрошенным clsid, возвращается уже собственно интерфейс IAutoServ1.

Теперь создадим КА для управления внешним СА.

Опять же, Delphi упрощает процесс создание КА.

Сначала выберем способ связывания. Я буду рассматривать только раннее связывание, т.к. использование типа dispinterface или OleVariant хуже в плане производительности.

Итак, начнём.

4.11). Создайте новый проект и нанесите на форму элементы, как показано на рисунке:

4.12). В раздел uses добавьте объявление модуля Example1_TLB (который должен находиться в том же каталоге, что и файлы только что созданного проекта, что бы не возникало путаницы, лично я размещаю проект СА и КА в одном каталоге, все файлы КА начинаются с cont_). В Private секции класса TForm1 опишем переменную типа IAutoServ1:

private
  { Private declarations }
  FIntf: IAutoServ1;
public

Процесс подсоединения к СА предельно прост:


begin
  try
    FIntf:= CoAutoServ1.Create;
  except
    MessageBox(0, PChar('Не удалось подсоединиться к серверу.'),
    PChar(Form1.Caption), MB_OK);
    Raise;
  end; {try}
end;

Отсоединиться от сервера так же просто, для этого достаточно переменную 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 выглядит следующим образом:

procedure TForm1.Button1Click(Sender: TObject);
begin
  FIntf.NewOle;
end;

Для кнопки Get Caption:


procedure TForm1.Button3Click(Sender: TObject);
begin
  Form1.Label1.Caption := FIntf.Caption;
end;

Для кнопки Change Caption:


procedure TForm1.Button2Click(Sender: TObject);
begin
  FIntf.Caption := Form1.Edit1.Text;
end;

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

5. Создание внутреннего сервера автоматизации.

Внутренний сервер автоматизации (InProcServer) - это библиотека (DLL), которая может создавать ОА.

Как и любой внутренний COM-сервер, внутренний СА должен экспортировать четыре стандартных функции:


function DllRegisterServer: HResult; stdcall;
function DllUnregisterServer: HResult; stdcall;
function DllGetClassObject(const CLSID, IID: TGUID; var Obj):
         HResult; stdcall;
function DllCanUnloadNow: HResult; stdcall;

Все эти функции реализованы в модуле ComServ, поэтому вам просто придётся добавить эти функции в раздел exports проекта.

Кратко о назначении этих функций:

DllRegisterServer
Данная ф-ия вызывается для регистрации библиотеки (DLL) вашего СА в системном реестре.
DllUnregisterServer
Удаляет все разделы и подразделы в системном реестре, созданные ф-ей DllRegisterServer.
DllGetClassObject
Получение фабрики класса конкретного COM-класса.
  • CLSID - идентификатор создаваемого COM-класса.
  • IID - содержит указатель на экземпляр интерфейса, который необходимо получить для объекта фабрики классов.
  • Obj - при удачном завершении работы ф-ии, данный параметр содержит указатель на интерфейс фабрики классов, обозначенный параметром IID и способный создавать COM-объекты с типом класса, определённым параметром CLSID.
DllCanUnloadNow
Проверяет можно ли выгрузить из памяти библиотеку (DLL) данного COM-сервера. Если библиотека не используется ни одним из COM-объектов, то ф-ия возвращает S_TRUE иначе S_FALSE.

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

Начнём:

5.1). Запускаем Delphi и создаём новую библиотеку (DLL), которую назовём Example2 (хотя можно использовать и любую из ранее созданных вами библиотек, превратив её во внутренний СА).

5.2). Добавляем четыре вышеописанных функции в раздел exports. Вот что должно получиться:

library Example2;

uses
  ComServ;

exports
  DllRegisterServer,
  DllUnregisterServer,
  DllGetClassObject,
  DllCanUnloadNow;

begin
end.

5.3). Для создания ОА повторим все шаги, проделанные нами в пунктах с 4.5 по 4.9 при разработке внешнего СА. Только в данном случае в качестве имени Com-класса выберем AutoServ2, для свойства - MyMessage, а для метода - ShowMyMessage.

5.4). После добавления свойства и метода нажимаем Refresh. К нашему проекту добавился файл Unit1.pas в котором содержатся каркасы, которые нам предстоит заполнить самостоятельно. Сам же модуль Example2 изменился следующим образом:

{$R *.TLB}

library Example2;

uses
  ComServ,
  Unit1 in 'Unit1.pas' {AutoServ2: CoClass};

exports
  DllRegisterServer,
  DllUnregisterServer,
  DllGetClassObject,
  DllCanUnloadNow;

begin
end.

Примечание:

лично у меня (Delphi Enterprise Version 6.0 (Build 6.163) + Win2k Prof SP2), в таком варианте на этапе сборки возникала ошибка "File no found '.TLB'", хотя файл на самом деле присутствовал. Явное указание имени файла решило проблему:

{$R Example2.TLB}

library Example2;

uses
  ComServ,
  Unit1 in 'Unit1.pas' {AutoServ2: CoClass};

exports
  DllRegisterServer,
  DllUnregisterServer,
  DllGetClassObject,
  DllCanUnloadNow;

begin
end.

5.5). Перед тем как заполнить каркасы кодом создадим секцию private в автоматически созданном классе TAutoServ2 и объявим в этой секции строковую переменную, в которой будет храниться принятое сообщение. Заполним каркасы следующим образом:


function TAutoServ2.Get_MyMessage: WideString;
begin
  Result:= MyMes;
end;

procedure TAutoServ2.Set_MyMessage(const Value: WideString);
begin
  MyMes:= Value;
end;

procedure TAutoServ2.ShowMyMessage;
begin
  MessageBox(0, PChar(MyMes), PChar('Your message is...'), MB_OK);
end;

Не забудьте в секцию 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). Теперь реализуем необходимые методы.


procedure TForm1.Button1Click(Sender: TObject);
begin
  try
    InProc := CreateComObject(CLASS_AutoServ2) as IAutoServ2;
    //  InProc:= CoAutoServ2.Create;  // - можно создать экземпляр СА и таким образом
  except
    MessageBox(0,
      PChar('Невозможно соединиться с внутренним сервером автоматизации.'),
      PChar(Form1.Caption), MB_OK);
    raise;
  end; {try}
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  try
    InProc := nil;
  except
    MessageBox(0,
      PChar('Невозможно отсоединиться от внутреннего сервера автоматизации.'),
      PChar(Form1.Caption), MB_OK);
    raise;
  end; {try}
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  try
    InProc.MyMessage := Edit1.Text;
    MessageBox(0, PChar('Ваше сообщение принято.'), PChar('InProc Server.'),
      MB_OK);
  except
    MessageBox(0,
      PChar('Не удалось выполнить присвоение переменной MyMessage сервера.'),
      PChar(Form1.Caption), MB_OK);
    raise;
  end; {try}
end;

procedure TForm1.Button4Click(Sender: TObject);
begin
  try
    InProc.ShowMyMessage;
  except
    MessageBox(0,
      PChar('Не удалось выполнить процедуру ShowMyMessage сервера.'),
      PChar(Form1.Caption), MB_OK);
    raise;
  end; {try}
end;

Я не стал загромождать код комментариями, т.к., по моему мнению, в нём всё предельно ясно и понятно.

Возможно, вы обратили внимание, что все обращения к внутреннему СА из КА я обрамляю конструкцией 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-файл, описывающий библиотеку типов данного СА, вам необходимо выполнить следующие действия:


tlibimp.exe -P+ Example2.dll

В результате вы получите два файла: Example2_TLB.dcr и Example2_TLB.pas, которые вы можете использовать при разработке своего КА. То же и с tlb-файлами и exe-файлами:


tlibimp.exe -P+ Example1.tlb

Для импорта сразу в С++ и Object Pascal синтаксис выполните следующее:


tlibimp.exe -C+ Example1.tlb

В этом случае помимо pas-файла будут, соответственно, созданы cpp-файлы и файлы заголовков (h-файлы).

8. Послесловие.

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

Подводя итог можно сказать, что с простой частью автоматизации мы разобрались и научились создавать простенькие сервера и контроллеры автоматизации. Если данная тема (и статья соответственно) вызовет интерес общественности, то я продолжу рассказывать о применении технологии автоматизации. В следующих статьях я предполагал рассказать о более сложных технологиях, входящих в понятие "Автоматизация", таких как события автоматизации (события COM), события автоматизации при работе в Delphi, события с несколькими стоками, коллекции автоматизации и их реализация в Delphi и ещё кое-что интересное с точки зрения Delphi-разработчика.

P.S.

Отдельным пунктом выражаю благодарность всем участникам дискуссии на тему способов и механизмов связывания на форуме сайта delphi.mastak.ru. Всегда приятно, когда в обсуждении тех или иных вопросов преобладает обстоятельность и обоснованность, а не аляповатая убеждённость в непогрешимости собственных знаний или, что ещё хуже, наглое невежество и неуважение к оппонентам.

9. Список использованной литературы.

  • Microsoft Win32 Software Development Kit.
  • Delphi-help, "Developing COM-based applications".
  • Стив Тейксейра и Ксавье Пачеко, "Delphi5. Руководство разработчика. Том 2. Разработка компонентов и работа с базами данных".

10. Архивы проектов.

Проект Delphi World © Выпуск 2002 - 2004
Автор проекта: ___Nikolay