На этом шаге мы рассмотрим создание контроллера автоматизации.
На главной форме будущего приложения-контроллера разместим компоненты 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.
Поясним приведенный выше код.
Для управления сервером автоматизации мы создали переменную типа 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. Совместное функционирование контроллера и сервера автоматизации
На следующем шаге мы рассмотрим ранее и позднее связывание.