На этом шаге мы рассмотрим процесс реализации методов объекта автоматизации.
Итак, мы описали свойства и методы нашего объекта и теперь должны приступить к их реализации. Для этой цели в окне редактора библиотеки типов следует щелкнуть на кнопке Refresh панели инструментов. В модуль реализации (сгенерированный ранее мастером, использованным при создании сервера) будут добавлены заготовки методов. Перед тем как начать реализацию кода, следует учесть, что доступ к одному экземпляру сервера может осуществляться несколькими клиентами одновременно (в раскрывающемся списке Instancing окна мастера объектов автоматизации выбран пункт Multiple Instance) и из разных потоков (в раскрывающемся списке Threading Model выбран пункт Free). При таком доступе необходима синхронизация обращений клиентов к общим переменным (содержимому поля Memo1, свойствам Width и Visible) и методам, с ними работающим. Поэтому сначала внесем изменения в модуль, в котором реализуется код формы. Для этого в секции private объявим переменную типа TRTLCriticalSection, а в секции publiс определим два метода, как показано ниже:
private { Private declarations } CS:TRTLCriticalSection; public { Public declarations } procedure LockForm; procedure UnlockForm; end;
Переменную CS: TRTLCriticalSection необходимо инициализировать. Сделаем это в обработчике события формы OnCreate. Инициализация резервирует системные ресурсы - поэтому по окончании работы их надо освободить. Это делается в обработчике события OnDestroy формы. Реализация кода для формы выглядит следующим образом:
procedure TForm1.LockForm; begin EnterCriticalSection(CS); end; procedure TForm1.UnlockForm; begin LeaveCriticalSection(CS); end; procedure TForm1.FormCreate(Sender: TObject); begin InitializeCriticalSection(CS); end; procedure TForm1.FormDestroy(Sender: TObject); begin DeleteCriticalSection(CS); end;
Смысл критических секций раскроем позднее - пока же будем просто иметь в виду, что без них при выполнении кода периодически будет появляться трудно уловимая и не всегда проявляющаяся ошибка.
Далее можно перейти к написанию кода в заготовках методов, объявленных в редакторе библиотеки типов (прежде всего сошлемся на модуль, который содержит описание формы):
unit Unit2; {$WARN SYMBOL_PLATFORM OFF} interface uses ComObj, ActiveX, AxCtrls, Classes, AutoServ_TLB, StdVcl; type TTest = class(TAutoObject, IConnectionPointContainer, ITest) private { Private declarations } FConnectionPoints: TConnectionPoints; FConnectionPoint: TConnectionPoint; FEvents: ITestEvents; { note: FEvents maintains a *single* event sink. For access to more than one event sink, use FConnectionPoint.SinkList, and iterate through the list of sinks. } public procedure Initialize; override; protected { Protected declarations } property ConnectionPoints: TConnectionPoints read FConnectionPoints implements IConnectionPointContainer; procedure EventSinkChanged(const EventSink: IUnknown); override; function Get_Text: WideString; safecall; function Get_Visible: WordBool; safecall; function Get_Width: Integer; safecall; procedure AddLine(const Line: WideString); safecall; procedure NewFile; safecall; procedure OpenFile(const FileName: WideString); safecall; procedure SaveFile(const FileName: WideString); safecall; procedure Set_Text(const Value: WideString); safecall; procedure Set_Visible(Value: WordBool); safecall; procedure Set_Width(Value: Integer); safecall; end; implementation uses ComServ, Unit1; procedure TTest.EventSinkChanged(const EventSink: IUnknown); begin FEvents := EventSink as ITestEvents; end; procedure TTest.Initialize; begin inherited Initialize; FConnectionPoints := TConnectionPoints.Create(Self); if AutoFactory.EventTypeInfo <> nil then FConnectionPoint := FConnectionPoints.CreateConnectionPoint( AutoFactory.EventIID, ckSingle, EventConnect) else FConnectionPoint := nil; end; function TTest.Get_Text: WideString; begin try Form1.LockForm; Result := Form1.Memo1.Text; finally Form1.UnlockForm; end; end; function TTest.Get_Visible: WordBool; begin try Form1.LockForm; Result := Form1.Visible; finally Form1.UnlockForm; end; end; function TTest.Get_Width: Integer; begin try Form1.LockForm; Result := Form1.Width; finally Form1.UnlockForm; end; end; procedure TTest.AddLine(const Line: WideString); begin try Form1.LockForm; Form1.Memo1.Lines.Add(Line); finally Form1.UnlockForm; end; end; procedure TTest.NewFile; begin try Form1.LockForm; Form1.SpeedButton1Click(nil); finally Form1.UnlockForm; end; end; procedure TTest.OpenFile(const FileName: WideString); begin try Form1.LockForm; if Length(FileName)>0 then Form1.Memo1.Lines.LoadFromFile(FileName) else Form1.SpeedButton2Click(nil); finally Form1.UnlockForm; end; end; procedure TTest.SaveFile(const FileName: WideString); begin try Form1.LockForm; if Length(FileName) > 0 then Form1.Memo1.Lines.SaveToFile(FileName) else Form1.SpeedButton3Click(nil); finally Form1.UnlockForm; end; end; procedure TTest.Set_Text(const Value: WideString); begin try Form1.LockForm; Form1.Memo1.Text := Value; finally Form1.UnlockForm; end; end; procedure TTest.Set_Visible(Value: WordBool); begin try Form1.LockForm; Form1.Visible := Value; finally Form1.UnlockForm; end; end; procedure TTest.Set_Width(Value: Integer); begin try Form1.LockForm; Form1.Width := Value; finally Form1.UnlockForm; end; end; initialization TAutoObjectFactory.Create(ComServer, TTest, Class_Test, ciMultiInstance, tmFree); end.
Код любого метода начинается с метода LockForm и заканчивается методом UnlockForm. При работе с критическими секциями важно, чтобы после каждого вызова процедуры EnterCriticalSection была выполнена обратная процедура - иначе корректная обработка следующего вызова процедуры EnterCriticalSection становится невозможной. По этой причине обращение к методу UnlockForm помещено в защищенный блок try...finally...end.
Скомпилируем и запустим сервер на выполнение. При этом он зарегистрируется в реестре (рисунок 1).
Рис.1. Запись о сервере автоматизации в реестре Windows
В действительности в разделах реестра НКЕY_LOCAL_MASHINE\SOFTWARE и HKEY_ CLASSES_ROOT содержится несколько записей, связанных с данным сервером и его интерфейсами, в том числе запись с информацией о местоположении сервера.
Если в дальнейшем отпадет необходимость в использовании созданного сервера автоматизации, рекомендуется запустить его с параметром командной строки /unregserver. В этом случае соответствующие записи будут удалены из реестра. Если же возникнет необходимость в переносе сервера автоматизации в другой каталог, можно после этого просто запустить его снова - записи в реестре обновятся.
Итак, мы создали настольное приложение, являющееся сервером автоматизации. Теперь, основываясь на информации о методах класса объекта автоматизации этого сервера, содержащейся в библиотеке типов, можно создавать управляющие этим сервером приложения-контроллеры с помощью довольно широкого спектра средств разработки (включая Delphi, C++Builder, Visual Basic, Visual C++, Visual Studio .NET, и др.).
Со следующего шага мы начнем рассматривать создание контроллера автоматизации.