Шаг 33.
Серверы и контроллеры автоматизации. Ранее и позднее связывание

    На этом шаге мы рассмотрим способы связывания сервера и контроллера.

    В рассмотренном на предыдущем шаге контроллере использовалось так называемое позднее связывание (late binding). В этом случае анализ существования методов и свойств объекта автоматизации не производится до момента обращения к ним на этапе выполнения. Поэтому при создании контроллера, ориентированного на позднее связывание, высока вероятность внесения незамеченных ошибок в названия методов и свойств.

    Контроллер автоматизации может быть создан и иным способом - путем импорта библиотеки типов. В этом случае в адресном пространстве контроллера создается набор классов для управления сервером, обладающих теми же самыми методами, что и подлежащий автоматизации объект. Это позволяет непосредственно обращаться к методам данных классов, а также выявлять на этапе компиляции ошибки в названиях методов сервера.

    Чтобы иметь возможность создать такой контроллер, следует в меню Delphi выбрать команду Project | Import Type Library.... Откроется диалоговое окно импорта библиотеки типов (рисунок 1).


Рис.1. Импорт библиотеки типов сервера

    В списке этого окна необходимо выбрать импортируемый сервер. Строка названия сервера соответствует значению в поле Help String окна редактора библиотеки типов для интерфейса AutoServ: ITypeLibrary. Обязательно требуется снять флажок Generate Component Wrapper (о нем будет сказано ниже). Для подтверждения выбора следует щелкнуть на кнопке Create Unit (по не Install). В результате будет создан файл с расширением *.pas в каталоге Delphi6\Imports. Этот файл следует включить в проект контроллера, чтобы сделать доступными описания классов для управления сервером (Delphi делает это автоматически).

    В этом случае код контроллера можно создать двумя способами. Один их этих способов реализует так называемое раннее связывание (early binding) клиента с сервером, то есть создание и использование в клиентском приложении таблицы виртуальных методов, полностью аналогичной таблице виртуальных методов сервера. Для этого в секции uses модуля реализации клиента нужно сослаться на импортированный модуль AutoServ_TLB. В модуле AutoServ_TLB объявляется класс ITest; а переменную FServ этого типа нужно объявить в секции private класса TForm1. Пример реализации методов контроллера автоматизации, созданного с применением раннего связывания, приведен ниже.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, AutoServ_TLB;

type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    Button6: TButton;
    Button7: TButton;
    Button8: TButton;
    Button9: TButton;
    Button10: TButton;
    Edit1: TEdit;
    CheckBox1: TCheckBox;
    OpenDialog1: TOpenDialog;
    SaveDialog1: TSaveDialog;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button7Click(Sender: TObject);
    procedure Button8Click(Sender: TObject);
    procedure CheckBox1Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
    procedure Button6Click(Sender: TObject);
    procedure Button9Click(Sender: TObject);
    procedure Button10Click(Sender: TObject);
  private
    { Private declarations }
    FServ: ITest;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

//Подключение к серверу.
procedure TForm1.Button1Click(Sender: TObject);
begin
  FServ := CoTest.Create;;
end;

//Отключение от сервера.
procedure TForm1.Button2Click(Sender: TObject);
begin
    FServ := Nil;
end;

//Получить текст от сервера.
procedure TForm1.Button4Click(Sender: TObject);
begin
  if Assigned(FServ) then
      Memo1.Text := FServ.Text;
end;

//Отправить текст на сервер.
procedure TForm1.Button3Click(Sender: TObject);
begin
  if Assigned(FServ) then
      FServ.Text := Memo1.Text;
end;

//Получение ширины от сервера.
procedure TForm1.Button7Click(Sender: TObject);
begin
  if Assigned(FServ) then
     Edit1.Text := IntToStr(FServ.Width);
end;

//Задание ширины окна сервера.
procedure TForm1.Button8Click(Sender: TObject);
begin
  if Assigned(FServ) then
       FServ.Width := StrToInt(Edit1.Text);
end;

//Установить видимость/невидимость.
procedure TForm1.CheckBox1Click(Sender: TObject);
begin
  if Assigned(FServ) then
        FServ.Visible := CheckBox1.Checked;
end;

//Открыть файл на сервере.
procedure TForm1.Button5Click(Sender: TObject);
begin
  if Assigned(FServ) then
    if OpenDialog1.Execute then
        FServ.OpenFile(OpenDialog1.FileName);
end;

//Сохранить файл на севере.
procedure TForm1.Button6Click(Sender: TObject);
begin
  if Assigned(FServ) then
    if SaveDialog1.Execute then
        FServ.SaveFile(SaveDialog1.FileName);
end;

//Создать новый файл на сервере.
procedure TForm1.Button9Click(Sender: TObject);
begin
  if Assigned(FServ) then
      FServ.NewFile;
end;

//Добавить строку на сервер.
procedure TForm1.Button10Click(Sender: TObject);
begin
  if Assigned(FServ) then
      FServ.AddLine(Edit1.Text);
end;

end.
Текст этого приложения можно взять здесь (10,8 Кб).

    В этом коде имеются следующие отличия от кода, созданного с применением позднего связывания. Прежде всего, для запуска сервера и получения ссылки на интерфейс используется метод CoTest.Create, который можно вызвать, не создавая экземпляра класса, поскольку при его описании в секции interface используется служебное слово class. Этот метод определен в модуле AutoServ_TLB, и при реализации других интерфейсов (не с именем Test) имя класса будет другим. Далее, вместо вариантной переменной используется переменная типа ITest. Класс ITest также объявлен в модуле AutoServ_TLB. Поскольку в объявлении этого класса присутствует описание методов, их параметров и свойств, то в приложении-контроллере следует вызывать методы переменной этого класса, и, таким образом, при создании кода приложения все ошибки, связанные с написанием имен методов, будут найдены уже на этапе компиляции (проверьте это!).

    Для проверки наличия ссылки на сервер используется функция Assigned(), которая проверяет, равна ли переменная, хранящая ссылку на интерфейс, значению nil. Интересен и способ отсоединения от сервера: переменной, в которой хранится ссылка на интерфейс, присваивается значение nil. Это тоже "обман" компилятора - реально при таком присвоении Delphi проверяет наличие ссылки на интерфейс и, если она есть, вызывает метод Release интерфейса.

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

    Есть еще и третий способ, основанный также на импорте библиотеки типов, но использующий позднее связывание:

TForm1=class(TForm)
  .    .    .    .
  private
    FServ :  ITestDisp;
  .    .    .    .
//Подключение к серверу.
procedure TForm1.Button1Click(Sender: TObject);
begin
  FServ := CreateOLEObject('AutoServ.Test') as ITestDisp;
end;

//Отключение от сервера.
procedure TForm1.Button2Click(Sender: TObject);
begin
  if VarType(FServ) = varDispatch then
    FServ := Unassigned;
end;
  .    .    .    .

    В этом случае контроллер автоматизации работает медленнее, чем при раннем связывании, однако на этапе компиляции производится синтаксическая проверка кода контроллера на предмет существования свойств и методов объекта автоматизации, к которым он обращается, причем для управления сервером используются свойства и методы классов, созданных при импорте библиотеки типов. Код реализации всех остальных методов полностью идентичен примеру с поздним связыванием.

    В данном коде следует отметить использование оператора as с интерфейсами:

  ...  := <ссылка на интерфейс> as <имя интерфейса>

    Синтаксис оператора такой же, как и в случае с классами. При использовании же его с интерфейсами Delphi вызывает метод <ссылка на интерфейс>.Query Interface с GUID для интерфейса <имя интерфейса>.

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

    Напомним, СОМ-сервер и СОМ-клиент могут быть написаны с использованием любых средств разработки, поддерживающих СОМ-техпологию. Поэтому в принципе не возбраняется написать сервер автоматизации в Delphi, а контроллер - в C++Builder или Visual Basic (или наоборот).

    На следующем шаге мы рассмотрим коллекции объектов.




Предыдущий шаг Содержание Следующий шаг