Подавляющее большинство современных настольных приложений предназначено для выполнения пользователями тех или иных операций - создания документов, проведения расчетов, анализа данных и т.д. В процессе выполнения подобного рода работ применяются различные внутренние сервисы этих приложений, например, вычисление значений по формулам, автоматическая нумерация абзацев, заголовков или страниц, проверка орфографии и многое другое. Нередко подобные приложения обладают встроенными макроязыками, позволяющими создать код, использующий эти сервисы, например, в случае часто повторяющихся последовательностей операций. Иначе говоря, приложения подобного рода.обладают программируемостью.
Отметим, однако, что программирование с помощью макроязыков имеет свои недостатки, так как не существует спецификаций, которым должны подчиняться макроязыки. Соответственно в общем случае у каждого программируемого приложения имеется свой макроязык, отличный от макроязыков других программируемых приложений (Отличием здесь являются продукты фирмы Microsoft, где в качестве макроязыка выбрано подмножество Visual Basic - Visual Basic for Applications. Прим. ред).
Более удобной реализацией программируемости настольных приложений было бы наличие в них возможности предоставлять свои сервисы другим приложениям с помощью универсального механизма, не зависящего от встроенных макроязыков и позволяющего, в частности, использовать обычные языки программирования. Именно для этой цели и предназначен механизм, называемый автоматизацией (Automation) (Ранее этот механизм назывался OLE Automation. Прим. ред). В этом случае приложение, предоставляющее тот или иной сервис, использует для этой цели интерфейсы содержащихся внутри его адресного пространства COM-объектов, и называется сервером автоматизации. Приложение, использующее сервис, называется контроллером автоматизации и может быть написано с помощью подавляющего большинства современных средств разработки. Отметим, что серверами автоматизации являются, в частности, такие популярные приложения, как Microsoft Office (Word, Excel, PowerPoint), Seagate Crystal Reports, Microsoft Internet Explorer и даже сама оболочка Windows 95/98/NT.
В общем случае клиент и сервер находятся в разных адресных пространствах, и, соответственно, для управления сервером клиент должен обращаться к методам объектов, находящихся в другом адресном пространстве. Для этой цели используется технология LRPC (Local Remote Procedure Calls - локальные вызовы удаленных процедур).
Каждый COM-сервер (каковым является сервер автоматизации) и каждый класс COM-объектов обладает уникальным 128-битовым идентификатором GIUD (Global Unique Identifier). При обращении к классам COM-объектов он иногда называется CLSID (идентификатор класса). При создании COM-серверов (в том числе и серверов автоматизации) с помощью C++Builder GUID и CLSID генерируются автоматически, хотя при необходимости можно сгенерировать их с помощью вызова стандартной функции Windows API CoCreateGUID. Информация обо всех COM-серверах и классах COM-объектов хранится в системном реестре, что позволяет клиенту "не знать", в каком каталоге (или на каком компьютере локальной сети) находится COM-сервер.
В общем случае COM-сервер представляет собой приложение, которое создает COM-объект и делает его доступным для других программ. .Сервер автоматизации предоставляет для доступа объект специального типа - dispatch object. При этом в адресном пространстве приложения-контроллера, управляющего сервером, присутствует вариантная переменная, содержащая интерфейс IDispatch, открывающий предоставляющий доступ к этому объекту на COM-сервере..
Контроллер может управлять сервером, например, инициируя его выполнение, создание с его помощью документов и иных объектов, изменение размеров, положения и видимости окна сервера, копирование объектов сервера в буфер обмена, добавление данных в созданный сервером документ, и т.д.. Наличие тех или иных возможностей управления сервером зависит от того, какие объекты, свойства и методы сервера предоставлены разработчиками сервера для автоматизации с помощью внешних приложений.
Подготовительный этап: создание приложения, подлежащего автоматизации
Для создания сервера автоматизации следует создать обычное приложение и затем добавить к нему подобный объектописание класса COM-объектов, создаваемых этим приложением, для осуществления доступа к функциональности, которую нужно автоматизировать.
Разработаем простейший сервер автоматизации. С этой целью создадим обычное приложение, например, текстовый редактор, содержащий инструментальную панель с четырьмя кнопками и компонент TMemo, а также диалоговые панели открытия и сохранения файла.
Рис. 1. Главная форма будущего сервера автоматизации
Создадим обработчики событий, связанные с нажатием на кнопки, и характерные для текстовых редакторов:
//-------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include "main1.h" //-------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1; //-------------------------------------------------------------- __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { } //-------------------------------------------------------------- void __fastcall TForm1::SpeedButton1Click(TObject *Sender) { Memo1->Lines->Clear(); } //-------------------------------------------------------------- void __fastcall TForm1::SpeedButton2Click(TObject *Sender) { if (OpenDialog1->Execute()) Memo1->Lines->LoadFromFile(OpenDialog1->FileName); } //-------------------------------------------------------------- void __fastcall TForm1::SpeedButton3Click(TObject *Sender) { if (SaveDialog1->Execute()) Memo1->Lines->SaveToFile(SaveDialog1->FileName); } //-------------------------------------------------------------- void __fastcall TForm1::SpeedButton4Click(TObject *Sender) { Close(); } //--------------------------------------------------------------
Сохраним проект. Отметим, что пока созданный нами текстовый редактор представляет собой обычное Windows-приложение и не является сервером автоматизации.
Для превращения созданного нами выше приложения в сервер автоматизации выберем элемент Automation Object со страницы ActiveX репозитария объектов:
Рис. 2. Выбор Automation Object из репозитария объектов
Введем имя класса, под которым данный класс COM-объектов будет зарегистрирован в реестре.
Рис. 3. Диалоговая панель ввода имени класса
После этого мы окажемся в редакторе библиотеки типов (Type Library Editor), в котором нам предстоит определить свойства и методы созданного класса COM-объектов.
Рис. 4. Библиотека типов вновь созданного сервера
Что представляет собой библиотека типов и зачем она нужна? По существу, библиотека типов представляет собой двоичный файл с описанием интерфейсов COM-объекта и их методов.
Обычно такие описания создаются с помощью специального языка IDL (Interface Definition Language) и используются для того, чтобы разработчики знали, как создать код, реализующий методы COM-объекта (или вообще методы объекта, расположенного за пределами адресного пространства разрабатываемого приложения, так как IDL используется не только в COM-технологии, но и в иных технологиях, реализующих вызовы удаленных процедур или функций, например, CORBA). Помимо этого, описания методов на языке IDL могут быть использованы для автоматической генерации серверного и клиентского кода (так называемого proxy-кода и stub-кода) с помощью соответствующих утилит.
С другой стороны, proxy-код и stub-код могут быть сгенерированы динамически. В этом случае клиент должен динамически получать информацию о свойствах и методах интерфейсов COM-объекта, и в этом случае наличие библиотеки типов, содержащей такую информацию, может быть весьма удобно. Отметим, что библиотеку типов можно в принципе сгенерировать на основе описания на языке IDL с помощью специального компилятора MIDL, но в данном случае в этом нет необходимости.
Итак, приступим к редактированию библиотеки типов. Предположим, что мы хотим автоматизировать загрузку файла в окно редактора, сохранение набранного текста, очистку окна редактирования, определение и изменение ширины и видимости окна. Создадим также метод, добавляющий строку к редактируемому тексту. С этой целью реализуемjопишем для нашего сервера методы FileNew, FileOpen, FileSave, AddLine и их параметры, а также свойства Width и Visible, и опишем их параметры.
Отметим, что типы данных, используемые для описания параметров методов, не совпадают с типами данных С++, так как в этом случае используются типы данных, принятые в IDL. Наиболее часто используемые типы данных языка IDL приведены в таблице 1. Таблица 1. Наиболее часто используемые типы данных языка IDL
Тип данных | Описание |
---|---|
short | Двухбайтовое целое число со знаком |
long | Четырехбайтовое целое число со знаком |
single | Четырехбайтовое действительное число |
double | Восьмибайтовое действительное число |
BSTR | Двоичная строка |
DATE | Дата |
VARIANT_BOOL | true= -1, false = 0 |
VARIANT | Указатель на вариантную переменную |
int | Целое (размер в байтах в общем случае зависит от разрядности операционной системы) |
Подробный список типов данных IDL можно найти в документации С++Builder.
Отметим также, что наряду с типами данных IDL можно использовать все типы данных, определенные в самой библиотеке типов. а также в других библиотеках, на которые она ссылается.
Метод FileNew параметров не имеет. Методы FileOpen и FileSave имеют один строковый параметр типа BSTR - имя файла. Метод Addline также имеет один строковый параметр, задающий добавляемую строку. Свойство Visible имеет логический тип VARIANT_BOOL, при этом оно может быть как прочитано, так и изменено. Свойство Width имеет целый тип int (число пикселов) и также доступно как для чтения, так и для записи.
В результате библиотека типов приобретет примерно следующий вид:
Рис. 5. Библиотека типов сервера автоматизации после описания свойств и методов объекта IDispatch
Итак, мы описали свойства и методы нашего объекта, и теперь должны приступить к их реализации. Для этой цели следует нажать кнопку Refresh на инструментальной панели редактора библиотеки типов. После этого будет сгенерирован еще один модуль с заготовками процедур и функций, реализующих данные методы. В эти заготовки следует вписать соответствующий код (он показан выделенным шрифтом):
//-------------------------------------------------------------- #include <vcl.h> #pragma hdrstop #include <atl\atlvcl.h> #include "main3.h" #include "main1.h" //-------------------------------------------------------------- #pragma package(smart_init) STDMETHODIMP TMyAuto3Impl::get_Width(int* Value) { try { *Value=Form1->Width; } catch(Exception &e) { return Error(e.Message.c_str(), IID_IMyAuto3); } return S_OK; }; //-------------------------------------------------------------- STDMETHODIMP TMyAuto3Impl::set_Width(int Value) { try { Form1->Width=Value; } catch(Exception &e) { return Error(e.Message.c_str(), IID_IMyAuto3); } return S_OK; }; //-------------------------------------------------------------- STDMETHODIMP TMyAuto3Impl::get_Visible(VARIANT_BOOL* Value) { try { *Value=Form1->Visible; } catch(Exception &e) { return Error(e.Message.c_str(), IID_IMyAuto3); } return S_OK; }; //-------------------------------------------------------------- STDMETHODIMP TMyAuto3Impl::set_Visible(VARIANT_BOOL Value) { try { Form1->Visible=Value; } catch(Exception &e) { return Error(e.Message.c_str(), IID_IMyAuto3); } return S_OK; }; //-------------------------------------------------------------- STDMETHODIMP TMyAuto3Impl::NewFile() { try { Form1->Memo1->Lines->Clear(); } catch(Exception &e) { return Error(e.Message.c_str(), IID_IMyAuto3); } return S_OK; }; //-------------------------------------------------------------- STDMETHODIMP TMyAuto3Impl::SaveFile(BSTR Filename) { try { Form1->Memo1->Lines->SaveToFile(Filename); } catch(Exception &e) { return Error(e.Message.c_str(), IID_IMyAuto3); } return S_OK; }; //-------------------------------------------------------------- STDMETHODIMP TMyAuto3Impl::AddLine(BSTR Line) { try { Form1->Memo1->Lines->Add(Line); } catch(Exception &e) { return Error(e.Message.c_str(), IID_IMyAuto3); } return S_OK; }; //-------------------------------------------------------------- STDMETHODIMP TMyAuto3Impl::OpenFile(BSTR Filename) { try { Form1->Memo1->Lines->LoadFromFile(Filename); } catch(Exception &e) { return Error(e.Message.c_str(), IID_IMyAuto3); } return S_OK; }; //--------------------------------------------------------------
Скомпилируем и запустим сервер на выполнение. При этом он зарегистрируется в реестре.
Рис. 6. Запись о сервере автоматизации в реестре Windows
В действительности в разделах реестра HKEY_LOCAL_MASHINE\SOFTWARE и HKEY_CLASSES_ROOT содержится несколько записей, связанных с данным сервером и его интерфейсами, в том числе и информация о местоположении сервера.
Если в дальнейшем отпадет необходимость в использовании созданного сервера автоматизации, рекомендуется запустить его с ключом /unregserver. В этом случае соответствующие записи будут удалены из реестра. Если же возникнет необходимость перенести сервер автоматизации в другой каталог, можно после этого просто запустить его снова, и при этом записи в реестре обновятся.
Итак, мы создали настольное приложение, являющееся сервером автоматизации. Теперь, основываясь на информации о методах класса его объекта автоматизации, содержащейся в библиотеке типов, можно создавать приложения, управляющие этим сервером, с помощью довольно широкого спектра средств разработки (включающего Delphi, C++Builder, Visual Basic, Visual C++ и др.). Создание контроллеров автоматизации будет рассмотрено в следующей статье данного цикла.
Координаты автора:
Центр Информационных Технологий,
Тел. (095)932-92-12, 932-92-13,
http://www.citmgu.ru,
http://www.citforum.ru