Шаг 32.
Серверы и контроллеры автоматизации. Создание контроллера автоматизации

    На этом шаге мы рассмотрим создание контроллера автоматизации.

    На главной форме будущего приложения-контроллера разместим компоненты TMemo, TEdit, TCheckBox, TOpenDialog, TSaveDialog, а также десять кнопок (рисунок 1).


Рис.1. Главная форма контроллера автоматизации

    Объявим переменную FServ типа Variant в секции private класса TForm1. Создадим обработчики событий, связанные со щелчками на кнопках (при этом следует сослаться на модуль ComObj, указав его в секции uses):

unit Unit1;

interface

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

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: Variant;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

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

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

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

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

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

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

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

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

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

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

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

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

    Поясним приведенный выше код.

    Для управления сервером автоматизации мы создали переменную типа Variant и вызвали функцию CreateOleObject(), содержащуюся в модуле ComObj. При выполнении функции CreateOleObject() она, вызвав несколько функций COM API, создаст экземпляр СОМ-объекта и вернет его интерфейс IDispatch внутри вариантной переменной. Этот объект содержит интерфейс СОМ-объекта (в данном случае нашего сервера автоматизации), методы которого мы хотим вызывать из приложения. Если исследовать реализацию функции CreateOleObject() в исходном тексте модуля ComObj, можно обнаружить, что она, в свою очередь, вызывает функцию CoCreateInstance() COM API, назначение которой - создать объект из исполняемого файла или DLL, то есть обратиться к фабрике классов. Переменная типа Variant может содержать разнообразные данные (строку, число и др., в том числе и интерфейс СОМ-объекта).

    Delphi в отличие от C++ позволяет обращаться к методам и свойствам объектов внутри вариантных переменных, при этом существование этих методов и свойств в общем случае может быть заранее неизвестно. Поэтому допустимый в Delphi код следующего вида вовсе не означает, что компилятор "знает" о существовании свойства Width вариантной переменной FServ:

   if VarType(FServ) = varDispatch then 
           FServ.Width:=StrToInt(Edit1.Text);

    Дело в том, что при создании контроллеров в Delphi компилятор не проверяет, имеется ли в действительности свойство (в данном случае Width) у объекта, хранящегося в переменной типа Variant (в отличие от объектов другого типа, например TForm), а просто воспринимает название свойства или метода как обычную символьную строку, не анализируя ее содержимое. Поэтому ошибка в названии свойства или метода компилятором опознана не будет и проявится лишь на этапе выполнения. Попробуйте изменить представленный выше код следующим образом:

  if VarType(FServ)=varDispatch then 
     FServ.WiGth:=StrToInt(Edit1.Text);

    В этом случае компилятор скомпилирует проект с ошибочным словом WiGth.

    На этапе выполнения приведенного кода меняется свойство Width объекта, содержащегося в адресном пространстве сервера, а не созданного контроллера.

    Соединение с сервером в данном проекте выполняется динамически, при щелчке на кнопке Подключиться. Разрыв соединения выполняется при щелчке на кнопке Отключиться. Здесь вариантной переменной просто присваивается неопределенное значение (unassigned). В этом заключается маленькая хитрость компилятора Delphi: при таком присвоении генерируется код, который проверяет, что находится в вариантной переменной, и если там имеется ссылка на интерфейс, то вызывается его метод Release. Поскольку сервер вызывается динамически, то перед вызовом каждого метода следует проверить, имеется ли в данный момент ссылка па сервер. Это достигается с помощью следующего оператора:

  if VarType(FServ) = varDispatch then ...

    После запуска контроллера при щелчке на кнопке Подключиться запускается сервер. При щелчках на кнопках Новый файл, Открыть файл и происходит соответственно очистка окна редактирования, загрузка текста в окно редактирования сервера из файла, сохранение текста в файле. Установка (снятие) флажка Видимость приводит к появлению (скрытию) сервера. Щелчок на кнопке Узнать ширину приводит к тому, что в текстовом поле отображается значение ширины окна сервера в пикселях. Если ввести в это поле другое число и щелкнуть на кнопке Задать ширину, ширина окна сервера станет равной введенному числу пикселей. Щелчок на кнопке Добавить строку приводит к тому, что в редактируемый текст компонента ТМеmо добавляется строка, находящаяся в этот момент во все том же текстовом поле. И наконец, при помощи кнопок Получить текст и Задать текст можно копировать целиком содержимое компонента ТМеmо (рисунок 2).


Рис.2. Совместное функционирование контроллера и сервера автоматизации

    На следующем шаге мы рассмотрим ранее и позднее связывание.




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