СТАТЬЯ |
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. Отправить E-Mail http://www.interface.ru |
|
Ваши
замечания и предложения отправляйте
автору По техническим вопросам обращайтесь к вебмастеру Документ опубликован: 03.07.02 |