
- •Разработка интегрированных прикладных программ
- •Введение
- •Интерфейсы в Delphi
- •Присоединение интерфейсов к формам
- •Объекты com
- •Контроллер позднего связывания (Delphi)
- •Контроллер на основе библиотеки типов (Delphi)
- •Контоллер на vba
- •Управление офисными приложениями
- •Ms Excel и компоненты библиотеки vcl
- •Работа с ячейками таблицы
- •Серверы ms Office и позднее связывание
- •Работа с серверами из Delphi (позднее связывание)
- •Элементы управления ActiveX
- •Создание ActiveX на основе компонентов vcl
- •Тестирование ActiveX
- •Тестирование с использованием vba
- •Создание активных форм
- •Добавление свойств и методов
- •Динамическое создание элементов управления ActiveX
- •Компонент oleContainer
- •Создание и сохранение нового объекта (внедрение)
- •Создание связанного объекта
- •Работа с сервером
- •Библиографический список
- •Оглавление
Интерфейсы в 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 вызовет ошибку, поскольку класс будет освобожден тогда, когда отпадет необходимость в его интерфейсах.
Присоединение интерфейсов к формам
С помощью механизма интерфейсов от различных программных компонент можно добиться одинаковой функциональности. Для этого достаточно реализовать интерфейс в компоненте, а в вызывающем модуле проверить его наличие. В следующем примере интерфейс описан в отдельном модуле, а разрабатываемый класс формы наследует и реализует его:
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. Но, к сожалению, доступ к разработанному нами объекту-форме возможен только из того же самого приложения, где этот объект создан.
Объекты com
В отличие от случая, рассмотренного в предыдущем примере, COM-объект предоставляет свою функциональность только посредством механизма интерфейсов. Этим достигается универсальность в общении приложений-клиентов с любыми COM-объектами.
Каждый объект реализован внутри некоторого сервера: в динамической библиотеке; в отдельном выполнимом приложении на том же компьютере; в удаленном коде, выполняющемся на компьютере, отличном от компьютера клиента. Сервер содержит код методов интерфейсов объекта, а также данные объекта пока он активен. С точки зрения клиента объект любого сервера выглядит совершенно одинаково и доступ к его методам осуществляется через указатели на интерфейсы.
Объект обязан (иначе это не COM-объект) иметь интерфейс IUnknown, в протокол которого входят три метода: QueryInterface – запрос указателя требуемого интерфейса, AddRef – увеличение счетчика ссылок, Release – уменьшение счетчика. Объекты поддерживают счетчики ссылок, чтобы знать, когда можно завершить свою работу (счетчик = 0 – объект не имеет клиентов). Все остальные интерфейсы являются наследниками IUnknown, а значит, имея указатель на любой интерфейс объекта, клиент может получить точку входа в другой необходимый ему интерфейс.
Каждый COM-объект является экземпляром некоторого класса, и каждый класс может иметь GUID – идентификатор класса (CLSID). Он нужен в основном для того, чтобы библиотека COM могла найти необходимый сервер в базе объектов (реестре) и создать экземпляр класса (объект). Под классом, в данном случае, понимается конкретная реализация некоторого набора интерфейсов. Понятно, что разные классы могут иметь совершенно одинаковые интерфейсы.
Большинство фирм не разрабатывают собственные интерфейсы, а используют существующие. Например, интерфейс IDispatch предназначен для автоматизации приложений.
OLE-автоматизация
Любое приложение, которое хочет сделать доступными для других программ свои внутренние функции, (т.н. программируемое приложение), может осуществить это, предоставив клиентам объекты, реализующие интерфейс IDispatch. Это обычный COM-интерфейс, который реализован с помощью виртуальной таблицы указателей на его методы, но в его состав входит метод Invoke, который используется для вызова других (диспетчерских) методов. Клиент, которого в данном случае принято называть контроллером автоматизации, может посредством Invoke обращаться к необходимому методу и передавать параметры.
Конечно, современные программные инструментарии предоставляют в распоряжение программистов достаточно удобные средства для создания серверов и контроллеров. Рассмотрим примеры их создания.
Создание сервера автоматизации в 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 и т.д.
Контроллер на 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");