Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
14
Добавлен:
13.03.2016
Размер:
321.02 Кб
Скачать
    1. Интерфейсы в Delphi

Для объявления интерфейса в Delphi используется ключевое слово interface. Для создания нового GUID применяется сочетание клавиш Ctrl-Shift- G [3]. Родительским интерфейсом является IUnknown.

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

ITint=interface ['{4457B5CC-2082-46B3-92F1-BC7CB3CF44C4}'] procedure My_Msg; end;

Реализовать интерфейс можно только в классе:

TTint=class(TInterfacedObject,ITint) procedure MyMsg; procedure ITint.My_Msg=MyMsg; destructor Destroy; override; end;

Объявления переменных, код процедуры и деструктора:

Var Form1: TForm1; Tint:ITint; T:TTint; procedure TTint.MyMsg; begin MessageBox(0,'Test','Сообщение',0); end;

destructor TTint.Destroy; begin inherited; MessageBox(0,'Free',nil,0); end;

Для создания интерфейса сначала нужно получить объект соответствующего класса, применив, например, следующий код:

T:=TTint.Create; Tint:=T;

Или более коротко: Tint:=TTint.Create as ITint; после этого можно вызывать методы интерфейса: Tint.My_Msg;

При выходе переменной за область видимости ссылка на интерфейс будет уничтожена, поскольку Delphi вызывает методы интерфейса IUnknown неявно и автоматически. Если надо освободить интерфейс, не дожидаясь выхода за область видимости, можно применить Tint:=nil; при этом Delphi вызовет IUnknown._Release.

Использование T.Free вызовет ошибку, поскольку класс будет освобожден тогда, когда отпадет необходимость в его интерфейсах.

    1. Присоединение интерфейсов к формам

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

unit Unit2; interface type IMessageInterface=interface ['{C6DB380C-41CB-49C7-AAE8-FAA5C0E00469}'] procedure Msg_Name; end;

В модуле формы класс TForm3 наследует интерфейс:

unit Unit3; interface type TForm3 = class(TForm, IMessageInterface) private procedure Msg_Name; end; var Form3: TForm3; implementation procedure TForm3.Msg_Name; begin ShowMessage('Form3'); end;

Теперь возможен вызов методов интерфейса из других модулей, в которых видна форма Form3. Например, по щелчку на кнопке в модуле Unit1 ( Form1 ):

procedure TForm1.Button2Click(Sender: TObject); var IMI: IMessageInterface; begin if Assigned(Form3) and Form3.GetInterface(IMessageInterface,IMI) then IMI.Msg_Name; end;

Добавив этот интерфейс к другим компонентам приложения, можно единообразно вызывать их методы.

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

    1. Объекты com

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

Каждый объект реализован внутри некоторого сервера: в динамической библиотеке; в отдельном выполнимом приложении на том же компьютере; в удаленном коде, выполняющемся на компьютере, отличном от компьютера клиента. Сервер содержит код методов интерфейсов объекта, а также данные объекта пока он активен. С точки зрения клиента объект любого сервера выглядит совершенно одинаково и доступ к его методам осуществляется через указатели на интерфейсы.

Объект обязан (иначе это не COM-объект) иметь интерфейс IUnknown, в протокол которого входят три метода: QueryInterface – запрос указателя требуемого интерфейса, AddRef – увеличение счетчика ссылок, Release – уменьшение счетчика. Объекты поддерживают счетчики ссылок, чтобы знать, когда можно завершить свою работу (счетчик = 0 – объект не имеет клиентов). Все остальные интерфейсы являются наследниками IUnknown, а значит, имея указатель на любой интерфейс объекта, клиент может получить точку входа в другой необходимый ему интерфейс.

Каждый COM-объект является экземпляром некоторого класса, и каждый класс может иметь GUID – идентификатор класса (CLSID). Он нужен в основном для того, чтобы библиотека COM могла найти необходимый сервер в базе объектов (реестре) и создать экземпляр класса (объект). Под классом, в данном случае, понимается конкретная реализация некоторого набора интерфейсов. Понятно, что разные классы могут иметь совершенно одинаковые интерфейсы.

Большинство фирм не разрабатывают собственные интерфейсы, а используют существующие. Например, интерфейс IDispatch предназначен для автоматизации приложений.

  1. OLE-автоматизация

Любое приложение, которое хочет сделать доступными для других программ свои внутренние функции, (т.н. программируемое приложение), может осуществить это, предоставив клиентам объекты, реализующие интерфейс IDispatch. Это обычный COM-интерфейс, который реализован с помощью виртуальной таблицы указателей на его методы, но в его состав входит метод Invoke, который используется для вызова других (диспетчерских) методов. Клиент, которого в данном случае принято называть контроллером автоматизации, может посредством Invoke обращаться к необходимому методу и передавать параметры.

Конечно, современные программные инструментарии предоставляют в распоряжение программистов достаточно удобные средства для создания серверов и контроллеров. Рассмотрим примеры их создания.

    1. Создание сервера автоматизации в Builder C++

Для создания сервера можно разработать обычное приложение и затем добавить к нему описание класса COM-объекта, создаваемого этим приложением. Через этот объект будет осуществляться доступ к функциональности, которую нужно автоматизировать.

На рис. 1 представлено главное окно приложения-сервера «День недели».

Рис. 1

Приложение должно выводить на экран текущую дату (в Label2) и день недели (в Label3). В поле ввода (Edit1) можно ввести любую дату, а по щелчку на кнопке (Button1) получить день недели, соответствующий введенной дате.

В класс формы (Form1) главного окна приложения были добавлены следующие функции:

void __fastcall TForm1::day_this(int& n); void __fastcall TForm1::day_date(AnsiString str,int& n); void __fastcall TForm1::day_week(int n, AnsiString& str);

В модуль формы следует также добавить строку #include <windows.h>, которая обеспечит обращение к системным функциям.

Функция day_this возвращает номер дня недели (1 – воскресенье):

void __fastcall TForm1::day_this(int& n) { n=DayOfWeek(Date()); }

Функция day_date по заданной дате возвращает номер дня недели:

void __fastcall TForm1::day_date(AnsiString str,int& n) { TDateTime dtDate = StrToDate(str); n=dtDate.DayOfWeek(); }

Функция day_week по заданному номеру дня недели возвращает его название:

void __fastcall TForm1::day_week(int n, AnsiString& str) { char days[7][12] = {"Воскресенье","Понедельник", "Вторник","Среда", "Четверг","Пятница","Суббота"}; str=(AnsiString)(days[n-1]); }

Пусть при создании формы Form1 в окне появляется текущая дата:

void __fastcall TForm1::FormCreate(TObject *Sender) { int i; AnsiString str; str = DateToStr(Date()); Label2->Caption=str; day_this(i); day_week(i,str); Label3->Caption=str; }

При щелчке на кнопке Button1, по дате, введенной в Edit1, определяется день недели:

void __fastcall TForm1::Button1Click(TObject *Sender) {int i; AnsiString str; str=Edit1->Text; try { day_date(str,i); day_week(i,str); Label5->Caption=str; } catch(Exception &e) { Application->MessageBoxA (e.Message.c_str(), "Ошибка",MB_OK); } Label4->Caption=Edit1->Text; }

Сохранив проект, как обычное Windows-приложение, затем можно превратить его в сервер автоматизации. Для этого следует:

  • на странице ActiveX галереи объектов выбрать элемент Automation Object;

  • в появившемся диалоговом окне ввести имя класса (CoClassName); под этим именем данный класс COM-объектов будет зарегистрирован в реестре. В нашем примере введено имя класса: DayWeek;

  • в редакторе библиотеки типов (Type Library Editor) определить свойства и методы созданного класса;

  • на инструментальной панели редактора библиотеки типов нажать кнопку Refresh Implementation для генерации модуля с заготовками методов (в нашем примере сгенерирован модуль DayWeekImpl).

Обычно описания интерфейсов создаются на языке IDL (Interface Definition Language), а затем по ним строится библиотека типов. При работе в среде Builder C++ и Delphi достаточно в окне редактора библиотеки указать свойства и методы создаваемого COM-объекта. Объект (рис. 2) имеет диспетчерский интерфейс IDayWeek, в состав которого входят два метода (Today, Data_Day) и свойство Visible.

Выбрав кнопку Export To IDL, можно получить описание на языке IDL интерфейса IDayWeek, входящего в состав библиотеки типов “Project_DayWeek Library”:

Project_DayWeek.idl [ uuid(C55ED6A4-5583-4E88-8317-0539445A5F6E), version(1.0), helpstring("Project_DayWeek Library") ] library Project_DayWeek { importlib("stdole2.tlb"); [ uuid(C13BFB8F-C655-43CA-80D9-C53B659E5C85), version(1.0), helpstring("Dispatch interface for DayWeek Object"), dual, oleautomation ] interface IDayWeek: IDispatch { [ id(0x00000001) ] HRESULT _stdcall Today([out] BSTR * str ); [ id(0x00000002) ] HRESULT _stdcall Data_Day([in] BSTR dat, [out] int * n, [out] BSTR * week ); [ propget, id(0x00000003) ] HRESULT _stdcall Visible([out, retval] VARIANT_BOOL * Value ); [ propput, id(0x00000003) ] HRESULT _stdcall Visible([in] VARIANT_BOOL Value ); }; [uuid(3E979810-8A64-4EAE-9F1E-8C582079F4AF), version(1.0), helpstring("DayWeek Object") ] coclass DayWeek { [default] interface IDayWeek; }; };

Рис. 2

Заготовки модуля DayWeekImpl заполняются соответствующим кодом.

Метод Data_Day возвращает номер и название дня недели для заданной даты:

STDMETHODIMP TDayWeekImpl::Data_Day(BSTR dat, int* n, BSTR* week) { int i; AnsiString str; str=(AnsiString)dat; Form1->day_date(str,i); Form1->day_week(i,str); *n =i; *week= WideString(str.c_str()).c_bstr(); return S_OK; }

Метод Today возвращает текущую дату и название дня недели:

STDMETHODIMP TDayWeekImpl::Today(BSTR* str) { AnsiString s; s=Form1->Label2->Caption + " " + Form1-> Label3->Caption; *str= WideString(s.c_str()).c_bstr(); return S_OK; }

Два следующих метода get_Visible и set_Visible отвечают за чтение и установку свойства Visible, которое позволяет сделать видимым или невидимым приложение-сервер:

STDMETHODIMP TDayWeekImpl::get_Visible(VARIANT_BOOL* Value) { try { *Value=Form1->Visible; } catch(Exception &e) { return Error(e.Message.c_str(), IID_IDayWeek); } return S_OK; }; STDMETHODIMP TDayWeekImpl::set_Visible(VARIANT_BOOL Value) {try { Form1->Visible=Value; } catch(Exception &e) { return Error(e.Message.c_str(), IID_IDayWeek); } return S_OK; };

При описании параметров методов сервера автоматизации используются типы данных, принятые в IDL. Часто используются типы данных, которым легко можно найти аналог в языке программирования (short - двухбайтовое целое число со знаком, long - четырехбайтовое целое число со знаком, single - четырехбайтовое действительное число, double - восьмибайтовое действительное число, BSTR - двоичная строка, VARIANT_BOOL - true= 1, false = 0). Можно использовать также и другие типы данных.

После компиляции и запуска сервера на выполнение он зарегистрируется в реестре. При необходимости удаления записей рекомендуется запустить его с ключом /unregserver. При переносе сервера автоматизации в другой каталог, можно запустить его снова, при этом записи в реестре обновятся. Записи делаются в разделах реестра HKEY_LOCAL_MASHINE\SOFTWARE и HKEY_CLASSES.

После создания сервера автоматизации, основываясь на информации, содержащейся в библиотеке типов, можно создавать приложения, управляющие этим сервером, т.е. контроллеры автоматизации. Это можно сделать с помощью различных инструментариев, включая Visual C++, Delphi, C++Builder, VBA и т.д.

    1. Контроллер на C++Builder (позднее связывание)

Приложения, управляющие серверами автоматизации, называются контроллерами автоматизации. Создадим контроллер, управляющий сервером «День недели», основываясь на информации о методах класса его объекта автоматизации.

На рис. 3 представлено окно контроллера, содержащее кнопки для установки и разрыва связи с объектом, флажок видимости сервера и кнопки управления свойством Visible, а также кнопки для получения данных, метки для отображения данных в окне, поле Edit1 для ввода даты. После запуска контроллера (при нажатии кнопки Connect) запускается сервер. При нажатии кнопки Disconnect он выгружается. Кнопка SetVisible проявляет и скрывает окно сервера в зависимости от наличия флажка на компоненте CheckBox1, но и будучи невидимым, сервер продолжает выполнять свои функции. Нажатие кнопки GetVisible приводит флажок в соответствие значению свойства Visible сервера.

Рис. 3

Чтобы общение с COM-объектом сервера стало возможно, в модуль следует включить строку #include <ComObj.hpp>.

Для управления сервером следует создать переменную Serv типа Variant, во время работы она будет содержать указатель на интерфейс IDispatch COM-объекта. Для создания экземпляра COM-объекта можно использовать функцию CreateOleObject из модуля ComObj библиотеки VCL: Variant Serv;

Serv=CreateOleObject("Project_DayWeek.DayWeek");

Эта функция создаст IDispatch и вернет указатель на него. IDispatch дает доступ к диспетчерскому интерфейсу объекта сервера, методы которого мы хотим вызывать. Для создания объекта CreateOleObject вызывает функцию CoCreateInstance, являющуюся частью спецификации COM Windows API.

Прерывание связи контроллера с сервером:

if (VarType(Serv)==varDispatch) Serv=Unassigned;

Выражение VarType(Serv)==varDispatch проверяет, содержит ли вариантная переменная Serv указатель на диспетчерский интерфейс. Эта проверка производится перед любым обращением к объекту сервера. Обращение к процедурам сервера осуществляет метод OleProcedure, первым параметром которого является имя метода диспетчерского интерфейса, а остальные – фактические параметры этого метода. Примеры таких вызовов:

WideString s, str1, str2; int n; // Вызов метода Today: if (VarType(Serv)==varDispatch) Serv.OleProcedure("Today",&s); // Вызов метода Data_Day: str1=Edit1->Text; if (VarType(Serv)==varDispatch) Serv.OleProcedure("Data_Day",str1,&n,&str2);

За работу со свойствами COM-объекта отвечают методы OlePropertyGet и OlePropertySet. Примеры работы со свойством Visible нашего сервера:

// Установить значение свойства по заданию пользователя: if (VarType(Serv)==varDispatch) Serv.OlePropertySet("Visible",CheckBox1->Checked); // Запросить у сервера значение свойства: if (VarType(Serv)==varDispatch) CheckBox1->Checked=Serv.OlePropertyGet("Visible");