Добрый день дорогие коллеги!
В этой заметке пойдет речь о создании внешнего COM-сервера и реализации в нем простой коллекции. Что я подразумеваю под коллецией. Под ней я подразумеваю,то что некий COM сервер имеет грубо говоря некий набор данных к которым нужно получить доступ. А так как набор данных может быть разным то нужно предоставить пользователям возможность получать эти данные проходом по коллеции. Классическим примером может служить COM-сервер MS Excel который предоставляет пользователям коллецию рабочих листов IWorkSheet. Что-то подобное в простом решении мы сейчас и реализуем.
Итак для примера создадим простой проект и поместим в него список ListBox. Заполнив его несколькими элементами.
Cоздадим новый объект автоматизации.
Cоздадим новый объект автоматизации.
В появившемся окне введем в поле CoClass Name имя класса реализующего данный сервер автоматизации (в нашем случае Main) и интерфейс IMain
После чего в появившейся бибилиотеки типов мы создадим дополнительно два интерфейса IDataItem - интерфейс элемента данных и IDataItemCollections - коллекция элементов данных. Для данных интерфейсов создадим также и классы реализующие их функциональнойсть для этого с помощь кнопки New CoClass создадим новые классы: DataItem и DataCollections
Эти классы свяжем с соответсвующими интерфейсами. Для это выберем нужный класс, на вкладке Implements щелкнем правой клавишей и в меню выберем Insert Interface. В появившемся окне свяжем наш интерфейс с классом.
После того как мы связали оба наших класса с нужными интерфейсами приступим к реализации методов наших интерфейсов.
В интерфейсе IMain создадим метод DataCollections, который будет предоставлять доступ к интерфейсу коллеции IDataCollections. После создания метода перейдем на вкладку Parameters и заполним ее так как показано на рисунке:
Внимание! Обратите внимание что параметр Collections возвращает указатель на указатель интерфейса IDataCollections. Т.е тип парамета должен быть IDataCollections**. Именно с двумя звездочками. Многие новички по первости путают это и пишут с одной звездочкой, указывая просто указатель на интерфейс. Если так делать, то потом в работе нашего сервера (при обращении клиента за интерфейсом) будут возникать ошибки cвязанные с доступом к памяти - access denied, т.к класс поддерживающий интерфейс сразу будет уничтожаться сервером после передачи интерфейса клиенту.
Стоит также отметить то, что среда Delphi, как бы провоцирет прикладного программиста на использование IDataCollections* при выборе из списка параметров.
Имейте это ввиду - если вы хотите из метода возвращать интерфейс всегда возвращайте УКАЗАТЕЛЬ НА УКАЗАТЕЛЬ ИНТЕРФЕЙСА
Стоит также отметить то, что среда Delphi, как бы провоцирет прикладного программиста на использование IDataCollections* при выборе из списка параметров.
Имейте это ввиду - если вы хотите из метода возвращать интерфейс всегда возвращайте УКАЗАТЕЛЬ НА УКАЗАТЕЛЬ ИНТЕРФЕЙСА
Далее перейдем к интерфейсу IDataItem и введем свойство для чтения Name, в которм мы будем вовзращать имя элемента из списка ListBox
Перейдем к интерфейсу IDataCollections и определим два свойства - Count и Items (в первом будет возращаться количество элементов в списке ListBox, во втором предоставляться доступ к интерфейсу IDataItem который будет возращать имя элемента из списка по индексу).
Далее на скриншотах показаны параметры этих методов:
Итак интерфейсы и методы в библиотеке типов мы описали, перейдем к реализации. Что бы долго не ходить, представлю исходный код сервера:
{ На заметку! Если вы хотите возвращать интерфейс из методов и свойств то его нужно объявлять как IDispatch ** или IMyInterface ** - т.е. как указатель на указатель интерфейса } unit COMRealizations; {$WARN SYMBOL_PLATFORM OFF} interface uses ComObj, ActiveX, DemoCollections_TLB, StdVcl, Dialogs; type TDemo = class(TAutoObject, IMain) protected procedure Test; safecall; function DataCollections: IDataCollections; safecall; end; {Элемент} TDataItem = class(TAutoObject, IDataItem) private FName : String; protected function Get_Name: WideString; safecall; end; {Коллекция} TDataCollections = class(TAutoObject, IDataCollections) Private protected constructor Create; {IDataCollections} function Get_Count: Integer; safecall; function Items(Index: Integer): IDataItem; safecall; end; implementation uses Main, ComServ; { TDataItem } { TDemo } function TDemo.DataCollections: IDataCollections; safecall; var DataCollections : TDataCollections; begin Result := nil; DataCollections := TDataCollections.Create; {создаем и возвращаем коллецию} Result := DataCollections; end; procedure TDemo.Test; begin ShowMessage('Test is TDemo Server COM' + #10#13 + frmMain.ListBox.Items.Text); end; { TDataCollections } constructor TDataCollections.Create; begin inherited; end; function TDataCollections.Get_Count: Integer; begin Result := frmMain.ListBox.Count; end; function TDataCollections.Items(Index: Integer): IDataItem; safecall; var Data : TDataItem; begin if (Index >= 0) and (Index <= frmMain.ListBox.Count) then begin Data := TDataItem.Create; Data.FName := frmMain.ListBox.Items.Strings[Index]; Result := Data; end else Result := nil; end; { TDataItem } function TDataItem.Get_Name: WideString; begin Result := FName; end; initialization TAutoObjectFactory.Create(ComServer, TDemo, CLASS_Main, ciMultiInstance, tmApartment); TAutoObjectFactory.Create(ComServer, TDataCollections, CLASS_DataCollections, ciMultiInstance, tmApartment); TAutoObjectFactory.Create(ComServer, TDataItem, CLASS_DataItem, ciMultiInstance, tmApartment); end.
Код тестового примера, показывающего простой доступ к данному серверу.
unit main; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ActiveX, ComObj, StdCtrls, DemoCollections_TLB; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } Srv : IMain; public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.Button1Click(Sender: TObject); var I : Integer; vData : IDataCollections; vDataItem : IDataItem; begin if Not Assigned(Srv) then begin Srv := CoMain.Create; if Assigned(Srv) then begin vData := Srv.DataCollections; if Assigned(vData) then begin ShowMessage('Кол-во элементов:' + IntToStr(vData.Count)); for I := 0 to vData.Count - 1 do begin vDataItem := vData.Items(I); if Assigned(vDataItem) then ShowMessage('Элемент:' + vDataItem.Name); end; end; end else ShowMessage('Srv is nil'); end; end; end.
Ну и напоследок, тест на скрипте VBS
Dim Srv, Collect, DataItem
Set Srv = CreateObject("DemoCollections.Main")
Set Collect = Srv.DataCollections
for I = 0 to Collect.Count-1
Set DataItem = Collect.Items(i)
MsgBox(DataItem.Name)
next
Вот в принципе и все - простая коллекция реализована.
В дальнейшем мы ее усложним добавив нумератор IEnumVariants.
Большое спасибо за таки статьи хочется сказать. Вот я и говорю :) Все понятно и наглядно.
ОтветитьУдалить