Delphi и COM
f65d50f6

Использование интерфейсов для реализации Plug-In


Еще более удобно использовать интерфейсы для реализации модулей расширения программы (Plug-In). Как правило, такой модуль экспортирует ряд известных главной программе методов, которые могут быть из него вызваны. В то же время зачастую ему необходимо обращаться к каким-либо функциям вызывающей программы. И то и другое легко реализуется при помощи интерфейсов.

В качестве примера реализуем несложную программу, использующую Plug-In для загрузки данных.

Объявим интерфейсы модуля расширения и внутреннего API программы.

unit PluginInterface; interface 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; API: IAPI);     function GetNextLine(var S: String): Boolean;   end; implementation end.

Этот модуль должен использоваться как в Plug-In, так и в основной программе и гарантирует использование ими идентичных интерфейсов.



Plug-In представляет собой DLL, экспортирующую функцию CreateFilter, возвращающую ссылку на интерфейс ILoadFilter. Главный модуль сначала должен вызвать метод Init, передав в него имя файла и ссылку на интерфейс внутреннего API, а затем вызывать метод GetNextLine до тех пор, пока он не вернет FALSE.

Рассмотрим код модуля расширения:

library ImpTxt; uses   ShareMem, SysUtils, Classes, PluginInterface; {$R *.RES} type   TTextFilter = class(TInterfacedObject, ILoadFilter)   private     FAPI: IAPI;     F: TextFile;     Lines: Integer;     InitSuccess: Boolean;     procedure Init(const FileName: String; API: IAPI);     function GetNextLine(var S: String): Boolean;   public     destructor Destroy; override;   end; { TTextFilter } procedure TTextFilter.Init(const FileName: String; API: IAPI); begin   FAPI := API;   {$I-}   AssignFile(F, FileName);   Reset(F);   {$I+}   InitSuccess := IOResult = 0;   if not InitSuccess then     API.ShowMessage('Ошибка инициализации загрузки'); end;

Метод Init выполняет две задачи: сохраняет ссылку на интерфейс API главного модуля для дальнейшего использования и пытается открыть файл с данными. Если файл открыт успешно – выставляется внутренний флаг InitSuccess.

function TTextFilter.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;

Метод GetNextLine считывает следующую строку данных и возвращает либо TRUE, если это удалось, либо FALSE — в случае окончания файла. Кроме того, при помощи API, предоставляемого главным модулем, данный метод информирует пользователя о ходе загрузки.

destructor TTextFilter.Destroy; begin   FAPI := NIL;   if InitSuccess then     CloseFile(F);   inherited; end;

В деструкторе мы обнуляем ссылку на API главного модуля, уничтожая его, и закрываем файл.

function CreateFilter: ILoadFilter; begin   Result := TTextFilter.Create; end;

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

exports   CreateFilter;  // Функция должна быть экспортирована из DLL begin end.

Теперь полученную DLL можно использовать из основной программы.

type   TAPI = class(TInterfacedObject, IAPI)     procedure ShowMessage(const S: String);   end; { TAPI } procedure TAPI.ShowMessage(const S: String); begin   with (Application.MainForm as TForm1).StatusBar1 do begin     SimpleText := S;     Update;   end; end;

Класс TAPI реализует API, предоставляемый модулю расширения. Функция ShowMessage выводит сообщения модуля в Status Bar главной формы приложения.

type   TCreateFilter = function: ILoadFilter; procedure TForm1.LoadData(FileName: String); var   PluginName: String;   Ext: String;   hPlugIn: THandle;   CreateFilter: TCreateFilter;   Filter: ILoadFilter;   S: String; begin

Подготавливаем TMemo к загрузке данных:

  Memo1.Lines.Clear;   Memo1.Lines.BeginUpdate;

Получаем имя модуля с фильтром для выбранного расширения файла. Описания модулей хранятся в файле plugins.ini в секции Filters в виде строк формата:

 <расширение> = <имя модуля>, например:

[Filters] TXT=ImpTXT.DLL     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;

Теперь попытаемся загрузить модуль и найти в нем функцию CreateFilter:

    hPlugIn := LoadLibrary(PChar(PluginName));     try       CreateFilter := GetProcAddress(hPlugIn, 'CreateFilter');       if Assigned(CreateFilter) then begin

Функция найдена, создаем экземпляр фильтра и инициализируем его. Поскольку внутренний API реализован тоже как интерфейс — нет необходимости сохранять ссылку на него.

        Filter := CreateFilter;         try           Filter.Init(FileName, TAPI.Create);

Загружаем данные при помощи созданного фильтра:

          while Filter.GetNextLine(S) do             Memo1.Lines.Add(S);

Перед выгрузкой DLL из памяти необходимо обязательно освободить ссылку на интерфейс Plug-In, иначе это произойдет по выходе из функции и вызовет Access Violation.

        finally           Filter := NIL;         end;       end else raise Exception.Create('Не могу загрузить фильтр');

Выгружаем DLL и обновляем TMemo:

    finally       FreeLibrary(hPlugIn);     end;   finally     Memo1.Lines.EndUpdate;   end; end;

Таким образом, реализовать модули расширения для загрузки данных из форматов, отличающихся от текстового, и единообразно работать с ними несложно.

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

 

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



Содержание раздела