СТАТЬЯ
03.07.02

Предыдущая часть

Delphi и COM (Часть 4)

У Анатолий Тенцер
Статья была опубликована в "КомпьютерПресс", №5-2001

Сервер с библиотекой типов

Библиотека типов — это специальный двоичный ресурс, описывающий интерфейсы и методы, реализуемые COM-сервером. Кроме наличия библиотеки типов сервер должен поддерживать интерфейс IProvideClassInfo. В Delphi такой сервер реализуется путем наследования его от TTypedComObject. Для этого оставьте флажок Include Type Library в мастере создания COM-объекта включенным.

Создадим COM-сервер в виде EXE (разумеется, он может быть также создан и виде DLL).

Сначала создадим новый проект — File-New Application, а затем добавим в него COM-объект.

Если не отключать флажок Include Type Library, то мастер создаст уже не один, а два модуля. Первый из них напоминает созданный ранее.

unit Unit1;

     interface

     uses
          Windows, ActiveX, Classes, ComObj, Project1_TLB, StdVcl;

     type
          TTest1 = class(TTypedComObject, ITest1)
          protected
               {Declare ITest1 methods here}
          end;

     implementation

     uses ComServ;

     initialization
          TTypedComObjectFactory.Create(ComServer, TTest1, Class_Test1,
               ciMultiInstance, tmApartment);
     end.

Наиболее интересна строка: uses … Project1_TLB. Это автоматически сгенерированный интерфейсный модуль к нашему COM-объекту (аналогично TestInterface.pas в предыдущем примере). Он содержит описание всех необходимых для работы с сервером интерфейсов. В отличие от предыдущего примера, вам не придется редактировать его вручную. Для этого Delphi откроет редактор библиотеки типов:

Это специализированный редактор для описания интерфейсов COM-объектов. Вы должны описать все требуемые интерфейсы, методы и т.п. в этом редакторе, после чего можно нажать кнопку «Обновить» — и изменения будут автоматически внесены во все требуемые модули. Вам останется лишь дописать реализацию методов.

Добавим описание нового метода. Для этого щелкнем правой кнопкой мыши на интерфейсе ITest и выберем из контекстного меню опцию New->Method. Введем имя метода — ShowIt.

На закладке Parameters зададим параметр S и тип BSTR. После этого нажмем кнопку «обновить» и посмотрим, что произошло с исходными текстами нашей программы. В модуле Project1_TLB в описании интерфейса ITest1 появился метод ShowIt:

ITest1 = interface(IUnknown)
     ['{1302FB06-703F-11D4-84DD-825B45DBA617}']
     function ShowIt(const S: WideString): HResult; stdcall;
end;

А в модуле Unit1:

type
     TTest1 = class(TTypedComObject, ITest1)
          protected
          function ShowIt(const S: WideString): HResult; stdcall;
     end;

implementation

uses ComServ;

function TTest1.ShowIt(const S: WideString): HResult;
begin

end;

Нам остается лишь написать реализацию метода:

function TTest1.ShowIt(const S: WideString): HResult;
     begin
          MessageBoxW(0, PWideChar(S), NIL, 0)
          Result := S_OK; // Стандартный код успешного завершения
     end;

Для регистрации сервера достаточно один раз запустить его на компьютере клиента.

Перейдем к написанию приложения-клиента. При наличии модуля Project_TLB оно ничем не будет отличаться от предыдущего примера. Более интересен случай, когда мы имеем только исполняемый файл с сервером. Зарегистрируем этот сервер и выберем в меню Delphi IDE команду Project -> Import Type Library.

В открывшемся окне найдем строку с описанием библиотеки типов требуемого сервера.

Если включен флажок Generate Component Wrappers, то в импортированный модуль будет добавлен код для создания компонента Delphi, который можно поместить на форму — и он автоматически создаст требуемый COM-сервер и позволит обращаться к его методам. В противном случае будет сгенерирован модуль, содержащий описание всех имеющихся в библиотеке типов интерфейсов.

Далее необходимо определить, что вы собираетесь сделать с выбранной библиотекой:

Таким образом, для распространения и использования сервера не требуется ничего, кроме его исполнимого модуля. Но это не самое главное. Гораздо более важно, что вы можете импортировать и использовать в своей программе любой из имеющихся на компьютере COM-серверов. Естественно, что при передаче своей программы клиенту вы должны установить на его компьютере соответствующий COM-сервер.

Для примера используем в своем приложении процессор регулярных выражений VBScript. Импортируем библиотеку типов Microsoft VBScript Regular Expressions.

При этом будет создан файл VBScript_RegExp_TLB.pas.

Создадим форму и добавим следующий код для проверки вхождения текста, содержащегося в компоненте Edit1, в текст, содержащийся в компоненте Edit2:

uses
          VBScript_RegExp_TLB;

     procedure TForm1.Button1Click(Sender: TObject);
     var
          RE: IRegExp;
     begin
          RE := CoRegExp.Create;
          RE.Pattern := Edit1.Text;
          if RE.Test(Edit2.Text) then
                    Caption := 'TRUE'
          else
                    Caption := 'FALSE';
          end;

Это все! Мы получили в своем приложении поддержку регулярных выражений — такую же, как и та, что включена в скриптовые языки Microsoft (VBScript и JScript).

Создание Plug-In в виде COM-сервера

Попробуем теперь реализовать Plug-In к своей программе в виде COM-сервера и сравним код, полученный в этом случае, с кодом, полученным при «ручном» программировании. Вначале создадим модуль с описанием интерфейсов:

Unit PluginInterface;

     interface

     const
          Class_TAPI: TGUID = '{A132D1A1-721C-11D4-84DD-E2DEF6359A17}';


type
     IAPI = interface
     ['{64CFF1E0-61A3-11D4-84DD-B18D6F94141F}']
          procedure ShowMessage(const S: String);
     end;


     ILoadFilter = interface
     ['{64CFF1E1-61A3-11D4-84DD-B18D6F94141F}']
          procedure Init(const FileName: String);
          function GetNextLine(var S: String): Boolean;
     end;

implementation

end.

Обратите внимание, что метод ILoadFilter.Init больше не получает ссылки на внутренний API программы — он будет реализован в виде COM-объекта.

Создадим DLL c COM-сервером, реализующим ILoadFilter. Для этого создадим новую ActiveX-библиотекуи добавим в нее COM-объект TLoadFilter. Установим ThreadingModel в Single, поскольку использование сервера в потоках не предусмотрено. После этого реализуем методы интерфейса ILoadFilter:

unit Unit3;

     interface

     uses
          Windows, ActiveX, Classes, ComObj, PluginInterface;

     type
          TLoadFilter = class(TComObject, ILoadFilter)
          private
               FAPI: IAPI;
               F: TextFile;
               Lines: Integer;
               InitSuccess: Boolean;
          protected
     procedure Init(const FileName: String);
          function GetNextLine(var S: String): Boolean;
     public
          destructor Destroy; override;
     end;


const
          Class_LoadFilter: TGUID = '{A132D1A2-721C-11D4-84DD-E2DEF6359A17}';


implementation

uses ComServ, SysUtils;

Деструктор и метод GetNextLine аналогичны предыдущему примеру:

destructor TLoadFilter.Destroy;
     begin
          if InitSuccess then
               CloseFile(F);
          inherited;
     end;

function TLoadFilter.GetNextLine(var S: String): Boolean;
     begin
          if InitSuccess then begin
               Inc(Lines);
               Result := not Eof(F);
               if Result then begin
                    Readln(F, S);
                    FAPI.ShowMessage('Загружено ' + IntToStr(Lines) + ' строк.');
               end;
          end else
               Result := FALSE;
     end;

Метод Init имеет существенное различие — теперь ссылку на внутренний API программы мы получаем при помощи COM. Это освобождает нас от необходимости передавать ссылку в модуль расширения.

procedure TLoadFilter.Init(const FileName: String);
     begin
          FAPI := CreateComObject(Class_TAPI) as IAPI;
          {$I-}
          AssignFile(F, FileName);
          Reset(F);
          {$I+}
          InitSuccess := IOResult = 0;
          if not InitSuccess then
               FAPI.ShowMessage('Ошибка инициализации загрузки');
     end;

В конце модуля находится код, автоматически сгенерированный Delphi для создания фабрики объектов:

initialization
     TComObjectFactory.Create(ComServer, TLoadFilter, Class_LoadFilter,
          'LoadFilter', '', ciMultiInstance, tmSingle);
     end.

Компилируем DLL и регистрируем ее при помощи regsvr32.

Поскольку программа может поддерживать множество различных фильтров, организуем их подключение через INI-файл следующего вида:

[Filters]
               TXT={A132D1A2-721C-11D4-84DD-E2DEF6359A17}

Параметром строки служит CLSID сервера, реализующего фильтр. В нашем случае это содержание константы Class_LoadFilter. Для подключения дополнительных фильтров необходимо создать DLL с сервером, реализующим ILoadFilter, зарегистрировать ее в системе и добавить CLSID сервера в INI-файл.

Теперь можно приступить к написанию программы-клиента. Она аналогична используемой в предыдущем примере. Добавим в нее COM-сервер, реализующий внутренний API.

За исключением кода, сгенерированного COM, этот объект полностью аналогичен объекту, приведенному ранее. Константу Class_TAPI вынесем в модуль PluginInterface, чтобы сделать ее доступной для модулей расширения:

unit Unit2;

     interface

     uses
          Windows, ActiveX, Classes, ComObj, PluginInterface;

     type
          TTAPI = class(TComObject, IAPI)
          protected
               procedure ShowMessage(const S: String);
     end;

implementation

uses Forms, ComServ, Unit1;

{ TTAPI }

procedure TTAPI.ShowMessage(const S: String);
begin
     (Application.MainForm as TForm1).StatusBar1.SimpleText := S;
end;

initialization
     TComObjectFactory.Create(ComServer, TTAPI, Class_TAPI,
          'TAPI', '', ciMultiInstance, tmSingle);
end.

Теперь все готово к реализации функциональности клиента. В целях экономии места приведем лишь метод LoadData:

procedure TForm1.LoadData(FileName: String);
     var
          PlugInName: String;
          Filter: ILoadFilter;
          S, Ext: String;
     begin
          Memo1.Lines.Clear;
          Memo1.Lines.BeginUpdate;
          try
               Ext := ExtractFileExt(FileName);
               Delete(Ext, 1, 1);
               with TIniFile.Create(ExtractFilePath(ParamStr(0)) + 'plugins.ini') do
               try
                    PlugInName := ReadString('Filters', Ext, '');
               finally
                    Free;
               end;
               Filter := CreateComObject(StringToGUID(PlugInName)) as ILoadFilter;
               Filter.Init(FileName);
               while Filter.GetNextLine(S) do
                    Memo1.Lines.Add(S);
               finally
                    Memo1.Lines.EndUpdate;
               end;
     end;

Очевидно, что код метода стал гораздо более коротким и читабельным. COM взял на себя всю черновую работу по поиску, загрузке и и выгрузке DLL, поиску и созданию объектов.

Внимание! Поскольку в EXE и DLL используются длинные строки, не забудьте включить в список uses обоих проектов модуль ShareMem.

Продолжение

Дополнительную информацию Вы можете получить в компании Interface Ltd.

Обсудить на форуме Borland
Отправить ссылку на страницу по e-mail


Interface Ltd.
Тel/Fax: +7(095) 105-0049 (многоканальный)
Отправить E-Mail
http://www.interface.ru
Ваши замечания и предложения отправляйте автору
По техническим вопросам обращайтесь к вебмастеру
Документ опубликован: 03.07.02