Некоторые комментарии
Теперь настало время пояснить, что именно делает приведенный выше код. Для управления сервером автоматизации мы создали переменную типа Variant (в C++Builder для этой цели имеется соответствующий класс) и вызвали функцию CreateOleObject, содержащуюся в модуле ComObj библиотеки VCL.
При выполнении функции CreateOleObject произойдет следующее. Эта функция, вызвав несколько функций Win32 API, создаст экземпляр COM-объекта IDispatch и вернет его внутри вариантной переменной. Этот объект, в свою очередь, содержит интерфейс объекта (в данном случае нашего сервера автоматизации), методы которого мы хотим вызывать из приложения. Если исследовать реализацию функции CreateOleObject в исходном тексте модуля ComObj, можно обнаружить, что она, в свою очередь, вызывает функцию Win32 API CoCreateInstance, являющуюся частью спецификации COM, назначение которой — создать объект из исполняемого файла или DLL. Переменная типа Variant может содержать разнообразные данные (строку, число и др., в том числе и интерфейс COM-объекта).
Отметим, что в отличие от Visual Basic или Delphi C++Builder не позволяет обращаться к методам и свойствам вариантных переменных, существование которых заранее неизвестно. Поэтому допустимый в Delphi код вида
if VarType(Serv) = varDispatch then
Serv.Width := StrToInt(Edit1.Text);
не имеет аналога в C++Builder. Дело в том, что при создании контроллеров с помощью Delphi в случае объектов типа Variant в отличие от объектов другого типа, например TForm, компилятор не проверяет, имеется ли в действительности такое свойство (в данном случае Width) у данного объекта. На этапе выполнения такого кода происходит вызов функций Win32 API, в результате работы которых меняется свойство Width объекта, содержащегося не в адресном проcтранстве созданного контроллера, а в адресном пространстве сервера.
В С++Builder достичь такого же результата можно с помощью следующего кода:
if (VarType(Serv) == varDispatch)
Serv.OlePropertySet(“Width”,StrToInt(Edit1->Text));
В этом случае на этапе выполнения производится вызов тех же самых функций Win32 API, что и в предыдущем случае. OlePropertySet представляет собой оболочку для метода вариантной переменной Exec() (наряду с OlePropertyGet, OleProcedure и OleFunction, позволяющими получать значения свойств объектов автоматизации и выполнять их методы). Отметим, что в Delphi также можно использовать вызовы OlePropertySet, OlePropertyGet, OleProcedure, OleFunction.
После запуска контроллера при нажатии кнопки Connect запускается сервер. При нажатии кнопки Disconnect он выгружается. При нажатии кнопок New File, Open File и Save File происходит очистка окна редактирования, загрузка текста в окно редактирования сервера из файла, сохранение текста в файле. Кнопка Get Visible показывает и скрывает окно сервера в зависимости от наличия отметки возле надписи Visible, при этом в невидимом состоянии сервер продолжает выполнять свои функции. Нажатие кнопки Set Visible приводит отметку возле надписи Visible в соответствие значению свойства Visible сервера. Нажатие кнопки Get Width приводит к тому, что в строке редактирования в верхней части окна контроллера отображается ширина окна сервера в пикселах. Если ввести в строку редактирования другое число и нажать кнопку Set Width, ширина окна сервера станет равной введенному числу пикселов. Нажатие кнопки Add String приводит к тому, что в редактируемый текст добавляется строка, находящаяся в этот момент в поле редактирования.
Контроллер и сервер автоматизации, запущенные одновременно
Хотелось бы обратить внимание на то, что, хотя мы и смогли протестировать свойства и методы сервера автоматизации с помощью созданного контроллера, у нас еще не было возможности произвести отладку части кода, связанного с автоматизацией. Естественно, если клиент запускается под управлением среды разработки C++Builder, использовать тот же самый экземпляр среды разработки для отладки сервера невозможно. Поэтому следует открыть проект сервера в отдельном экземпляре среды разработки и запустить его на выполнение. Если после этого запустить приложение-контроллер (неважно, под управлением другого экземпляра среды разработки или просто средствами операционной системы) и нажать кнопку Connect, контроллер соединится с уже запущенным экземпляром сервера. Если в исходном тексте сервера отмечены точки останова, при их достижении выполнение кода сервера прекращается и управление передается среде разработки.
Отметим один очевидный факт: COM-сервер и COM-клиент могут быть написаны с использованием любых средств разработки, поддерживающих COM-технологию. Поэтому в принципе не возбраняется написать сервер автоматизации с помощью Delphi, а контроллер — с помощью C++Builder (или наоборот).
Создание контроллеров для произвольных серверов автоматизации
Как было сказано в предыдущей статье, наличие тех или иных возможностей управления произвольным сервером зависит от того, какие объекты, свойства и методы сервера предоставлены разработчиками сервера для автоматизации с помощью внешних приложений. Каким образом можно получить информацию о них?
Обычно такие сведения содержатся в документации или файлах справочной системы, поставляемых с данным сервером, как, например, это сделано в MS Office или Seagate Crystal Reports Professional. Но в принципе такую информацию можно получить и из библиотеки типов.
В качестве примера рассмотрим использование информации из библиотеки типов MS Excel как одного из наиболее часто используемых серверов автоматизации в практике отечественных разработчиков. Надо заметить, что практически все, что может сделать пользователь, работая с этим приложением, равно как и с другими приложениями MS Office, доступно для автоматизации.
Как и в предыдущем случае, для управления сервером автоматизации следует создать переменную типа Variant (в C++Builder для этой цели имеется соответствующий класс) и вызвать функцию CreateOleObject:
Variant XL;
……
XL=CreateOleObject(“Excel.Application.8”);
В качестве параметра функции CreateOleObject передается имя объекта, который мы хотим создать. Найти его можно в реестре Windows:
Запись в реестре, соответствующая выбранному серверу