Статьи Королевства Дельфи

       

Часть 2. Доступ к объектам пакета.


Попытаемся наладить связь между главной формой и пакетом. То есть, мы ставим себе задачу вызова некоторой (или некоторых) функции/процедур формы из пакета при условии того, что мы не знаем действительный тип этой формы и всего набора поддерживаемых ею функций и процедур. Для осуществления этого воспользуемся технологией COM. Точнее той ее части, которая поддерживается Delphi на уровне языка.

Для того, чтобы новичкам было все ясно, следует немного углубиться в понятие интерфейса. Я полагаю, что вы знакомы с понятием виртуальной таблицы методов (VMT). Именно она является источником и тремя составными частями ООП . Для поддержки COM в Delphi был введен новый, особый тип interface, который позволяет "поименовать" куски виртуальной таблицы методов. Способ этого именования достаточно уникален - 16-байтовое число , которое присваивается каждому такому куску. Есть мнение, что оно статистически уникально . Синтаксис данного типа следующий type IMyInitialize = interface ['{7D501741-B419-11D5-915B-ED714AED3037}'] // то самое 16-битное число в строковом // представлении. Получается // в Delphi нажатием клавиш Ctrl+g. procedure InitializeForm(const ACaption: String); // а это процедура интерфейса. end; Помимо прочего компоненты Delphi содержат метод, позволяющий получать этот кусок виртуальной таблицы. Причем этих методов два.

  • Первый имеет название QueryInterface. Для любителей COM он является привычным, так как используется в оном вдоль и поперек.
  • Второй называется GetInterface.
Разница этими методами в том, что у QueryInterface нужно проверять результат на S_OK, а у GetInterface на Boolean .

Теперь давайте прикрутим к нашей системе интерфейсы и покажем, как с ними работать. Для этого создаем новый модуль (он достаточно короткий, и я здесь привожу его полностью) unit CommonInterfaces; interface type IMyInitialize = interface ['{7D501741-B419-11D5-915B-ED714AED3037}'] procedure InitializeForm(const ACaption: String); end; IMyHello = interface ['{7D501742-B419-11D5-915B-ED714AED3037}'] function ShowHello(AText: String): String; end; implementation end. Далее, внесем изменения в наш пакет, учитывающий поддержку интерфейсов 1. для дочерней формы: type TfrmChild = class(TForm, IMyInitialize, IMyHello) // так наследуются интерфейсы Private { описание методов IMyInitialize } procedure InitializeForm(const ACaption: String); { описание методов IMyHello } function ShowHello(AText: String): String; end; Что мы тут сделали? Мы сказали, что TfrmChild является наследником TForm, но помимо методов TForm VMT класа TfrmChild содержит еще два цельных куска, один из которых идентичен VMT IMyInitialize, а второй VMT IMyHello. 2. для диалоговой формы: type TfrmDialogFrom = class(TForm, IMyInitialize) BitBtn1: TBitBtn; BitBtn2: TBitBtn; Label1: TLabel; private { описание методов IMyInitialize } procedure InitializeForm(const ACaption: String); end; Реализация этих методов проста, ее можно смотреть в архиве (Step2).


Соответственно (для вызова этих методов), немного корректируем главную форму… procedure TForm1.aOpenExecute(Sender: TObject); var frmClass: TFormClass; frm: TForm; MyInitialize: IMyInitialize; begin frmClass := TFormClass(GetClass('TfrmChild')); if not Assigned(frmClass) then begin MessageBox(Handle, PChar(Format(' Не найден класс %s', ['TfrmDialogFrom'])), 'Ошибка', MB_APPLMODAL+MB_ICONERROR+MB_OK); exit; end; frm := frmClass.Create(Self); // производим вызов метода интерфейса if frm.GetInterface(IMyInitialize, MyInitialize) then begin // интерфейс поддерживается формой, можно вызывать его методы MyInitialize.InitializeForm(Format('Дочернее окно ? %d', [Tag])); Tag := Tag + 1; end else raise Exception.CreateFmt('Интерфейс %s не поддерживается классом %s', ['ImyInitialize', frm.GetClassName]); end; procedure TForm1.aOpenDialogExecute(Sender: TObject); var frmClass: TFormClass; MyInitialize: IMyInitialize; begin frmClass := TFormClass(GetClass('TfrmDialogFrom')); if not Assigned(frmClass) then begin MessageBox(Handle, PChar(Format('Не найден класс %s', ['TfrmDialogFrom'])), 'Внимание!', MB_APPLMODAL+MB_ICONERROR+MB_OK); Exit; end; with frmClass.Create(Self) do try if GetInterface(IMyInitialize, MyInitialize) then begin // Интерфейс поддерживается фомой, вызываем его метод MyInitialize.InitializeForm(' Диалог'); end else raise Exception.CreateFmt('Интерфейс %s не поддерживается классом %s', ['ImyInitialize', frm.GetClassName]); case ShowModal of mrOk: MessageDlg('Ok!', mtInformation, [mbOk], 0); mrCancel: MessageDlg('Cancel!', mtInformation, [mbOk], 0); else MessageDlg('Неизвестная распальцовка!', mtInformation, [mbOk], 0); end; finally Free(); end; end; Полностью весь проект смотрите в архиве (Step2.zip).

Теперь что мы имеем.
  • Во-первых, мы не знаем действительный тип как дочернего, так и диалогового окон. Но между тем вызываем функции, входящие в его VMT.
  • Во-вторых, мы запросто можем поменять наш пакет на другой. Имена классов форм пакета особого значения не имеют - их можно сохранять в файле настроек или реестре и подгружать при инициализации основного приложения. Единственное, что необходимо неукоснительно соблюдать - дочернее и диалоговое окно ДОЛЖНЫ поддерживать необходимые интерфейсы.
  • В третьих, мы можем КАК УГОДНО изменять формы пакета (включая изменения самой виртуальной таблицы методов, естественно, не затрагивая описания интерфейсов) - общая система приложение-пакет останутся в рабочем состоянии.



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