    ГЛАВА           20 

    Автоматизация OLE 

    Тодд Миллер 
    
    Что такое автоматизация OLE 

    Рассматриваются способы использования автоматизации OLE. 

    • Использование объектов автоматизации 

    Описываются способы получения доступа к объектам автоматизации с помощью Delphi. 

    В Создание объектов автоматизации 

    Демонстрируются методы построения внутренних (in-process) и внешних 
(out-of-process) серверов автоматизации с помощью новой архитектуры Delphi 
ActiveX (DAX). 

    • Библиотеки типов 

    Рассказывается об основных методах использования библиотек типов и 
редактора библиотек типов Delphi при создании серверов автоматизации. 

    Пытаясь расширить возможности Windows, компания Microsoft столкнулась с 
необходимостью создания технологии, которая обеспечивала бы доступ приложений 
к объектам, расположенным вне приложения. Другими словами, приложения должны 
работать с внешними объектами с помощью методов, которые используются для 
работы с внутренними объектами. Кроме того, объекты не должны зависеть от 
языка программирования; к ним должен быть открыт доступ со стороны приложений 
не только локального, но и удаленного компьютера. 
    В качестве решения проблемы, связанной с доступом к объектам в пределах 
отдельного компьютера (приложение—приложение) и локальной сети 
(компьютер—компьютер), компания Microsoft встроила в Windows модель составных 
объектов (СОМ и Distributed СОМ). Подсистема СОМ управляет связями между 
приложениями и составными объектами на базе СОМ. 
    Для совместного использования объектов разными процессами и компьютерами 
компания Microsoft на базе модели составных объектов создала технологию 
автоматизации OLE. В данной главе будут изложены основы этой технологии и 
продемонстрированы примеры создания и использования объектов автоматизации в Delphi. 
    Здесь будут рассмотрены следующие вопросы. 
    И Основы автоматизации OLE 
    • Объекты автоматизации 
    И Контроллеры автоматизации 
    К Доступ к объектам автоматизации 
    И Тип данных OleVariant и массивы OleVariant 
    (В Создание объектов автоматизации 
    • Информация о типах 
    •! Библиотеки типов 

    466 


    Что такое автоматизация OLE 

    Начнем с терминологии. Автоматизация OLE, или просто автоматизация, — это 
независимая от приложений парадигма программирования, которая позволяет 
модулям ЕХЕ и DLL предоставлять объект (или объекты) автоматизации 
контроллеру или клиенту автоматизации. 
    Объект автоматизации — это, как и класс Delphi, набор свойств и методов, 
которые поддерживают функции, полезные для контроллера автоматизации. 
    В основе механизма, используемого клиентами для получения доступа к 
объектам автоматизации, лежит поддерживающий интерфейс IDispatch объект 
автоматизации, который по определению является объектом СОМ, и контроллер 
автоматизации, который знает, как использовать интерфейс IDispatch. Этот 
интерфейс позволяет клиентам получать доступ к свойствам и методам любого 
объекта автоматизации. К счастью, изучать интерфейс IDispatch во всех 
подробностях не придется — Delphi полностью инкапсулирует вызовы методов этого 
интерфейса. Как будет рассказано ниже, получение доступа к объекту 
автоматизации ненамного сложнее работы с классом Delphi. 
    Сервер автоматизации содержит несколько объектов автоматизации. Из-за 
такой связи между сервером и объектом термины связь с сервером автоматизации и 
связь с объектом автоматизации иногда применяются как синонимы. 

    Типы серверов 

    Существует два типа серверов автоматизации: 
    • внутренние (in-process); 
    • внешние (out-of-process), или локальные. 

    Внешние серверы 

    Внешние (out-of-process) серверы — это приложения, которые предоставляют 
клиентам один или несколько объектов автоматизации. Здесь приобретает значение 
термин внешний, так как контроллер автоматизации выполняется в адресном 
пространстве, отличном от адресного пространства сервера автоматизации. Обычно 
внешними серверами являются гюл^ нефункциональные приложения, которыми можно 
управлять извне, из других приложений. Microsoft Word для Windows является 
внешним сервером автоматизации. Пример использования Word вы найдете далее в 
этой главе.                \У 

    Нз заметку д 
    Объекты автоматизации, управляемые внешними приложениями, иногда называют 
программируемыми обьектами. 


    Внутренние серверы 

    Внутренние (in-process) серверы содержат объекты автоматизации 
^динамических библиотеках IDLL^ Следовательно, внутренние серверы загружаются 
непосредственно в адресное пространство контроллера автоматизации. 
Преимуществом внутренних серверов является скорость. Кроме того, при 
конвертировании свойств и данных методов внутренние серверы не требуют 
выполнения преобразований в объеме, необходимом для внешних серверов. Если 
прямой доступ к внутреннему серверу автоматизации получен через его интерфейс, 
то преобразования не выполняются вообще. 
    Теперь, предоставив читателю информацию о том, что такое автоматизация и 
как она работает, перейдем к описанию способов реального использования 
объектов автоматизации. 
    / В этой главе под контроллером автоматизации понимается клиентское 
приложение, управляющее объектом автоматизации. — Прим. ред. 

    Глава 20. Автоматизация OLE                                        467 


    Использование объектов автоматизации 

    Прежде чем использовать объект автоматизации, необходимо выяснить, какими 
свойствами и методами он обладает. Для методов нужно также выяснить количество 
и типы аргументов. Но где найти эту информацию? Производитель сервера 
автоматизации, который вы используете, наверняка предоставил некоторую 
документацию, но на нее расчитывать не стоит. Кроме звонков разработчикам, 
наиболее надежный способ получения нужных сведений — это опрос ин4юрмации о типе! 

    Что такое информация о типах 

    Информация о типах — это электронная форма документации для элементов ОСХ, 
элементов управления ActiveX и серверов автоматизации. Информация о типе 
используется в основном в средах программирования интерпретирующего типа, 
такйх'как VIlual Basic, для выполнения проверки синтаксиса и типов. Кроме 
того, эта информация используется редактором библиотек типов Delphi (Type 
Library Editor). Информация о типе может стать основным источником данных т 
объектах автоматизации, реализуемых выбранным сервером автоматизации. Тем не 
менее следует заметить, что не вс; серверы автоматизации снабжены информацией 
о типах. 
    следующей главы вы узнаете, как Delphi использует информацию о типе 
элемента управления ActiveX дли создания классов-оболочек, используемых в 
Delphi для проверки синтаксиса и типов на этапе компиляции. 

    ззмбтку 
    Информация о типах хранится в библиотеках, которые содержат какой-либо из 
элементов, перечисленных ниже. 

    • Информация о перечислениях. 
    • Описания одного или нескольких объектов, таких как интерфейсы, 
диспинтерфейсы (инструкция dispinterface) или классы составных объектов 
(инструкция CoClass). 
    • Ссылки на описания типов из других библиотек типов. 
    Информация о типах может храниться в файлах формата Type Library (TLB) или 
Object Type Library (OLB), в файл^ ресурсов, компонуемых с ЕХЕ- или 
DLL-файлом, или, в редких случаях, в составном документе OLE. По умолчанию 
Deiphi создает файл формата TLB, который компонуется с выполняемым файлом 
приложения на этапе компиляции. 
    Чтобы просмотреть информацию о типах сервера автоматизации, выполните 
перечисленные ниже действия. 
    1. Выберите команду File^Open. 
    2. В поле File of type выберите Type Library. 
    3. Найдите одну из библиотек типов и выделите ее. 
    4. Щелкните на кнопке Open, что приведет к запуску редактора библиотек 
типов Type Library Editor с информацией из выбранной вами библиотеки.          
    Для доступа к серверам автоматизации Delphi информация о типах не 
требуется. Тем не^менее_без нее на этапе компиляции Delphi не сможет выполнять 
проверку синтаксиса в коде, который используется для доступа к серверу 
автоматизации. Из этого следует, что на этапе компиляции Delphi не выполняет 
проверку типов для свойств и методов объектов автоматизации. Такая проверка 
осуществляется во время выполнения при вызове методов и использовании свойств 
сервера. Далее будет рассказано, как Delphi определяет, какой код относится к 
серверу автоматизации, а какой — нет. 

    Доступ к объектам автоматизации 

    Delphi намного упрощает доступ к серверам автоматизации. Скоро вы 
убедитесь, что работа с сервером автоматизации лишь незначительно отличается 
от работы с объектами стандартных классов Delphi. Различие состоит в том, что 
клиент автоматизации реально не создает объекта автоматизации, а просто 
вызывает функцию оболочки Delphi CreateOleObject. 
    Функция CreateOleObject получает один аргумент— идентификатор программы 
ProgID, связанный с объектом автоматизации, и возвращает интерфейс IDispatch 
этого объекта. Идентификатор программы ProgID— это уникальный строковой 
идентификатор, который функционирует как синоним объекта автоматизации. В 
показанном ниже фраг

    468                                           Часть V. Работа с OLE 

    менте кода для установки связи с внутренним объектом автоматизации 
Microsoft Word вызывается функция CreateOleObject. В вызове функции 
используется идентификатор программы Microsoft Word Word. Basic. Здесь 
демонстрируется вставка текста в текущий документ Word. 

    Uses ComObj ; 
    var 
    WordBasicObject: OleVariant; 
    procedure TFormI.ButtonlClick(Sender: Tobject); 
    begin 
    WordBasicObject := CreateOleObject('Word.Basic'); 
    WordBasicObject.Insert("inserting text into Word via Automation'); 
    end; 

    He похоже, что этот фрагмент кода выполняет что-то сверхъестественное. 
Прежде всего, для вызова функции CreateOleObject в раздел uses нужно включить 
модуль ComObj. Затем объявляется переменная WordBasicObject типа OleVariant. 
Тип OleVariant является основным типом данных, который используется в процессе 
работы с автоматизацией OLE, так как он тождественен другим типам данных. В 
данном случае переменная типа OleVariant будет содержать ссылку на интерфейс 
IDispatch, который является базовым интерфейсом автоматизации OLE. 
    В процессе работы функция CreateOleObject не выполняет никаких магических 
действий компиляции. Она вызывает функцию Windows CLSIDFromProgID, которой 
передается идентификатор ProgID, для получения идентификатора класса (CLSID). 
Затем полученный CLSID передается функции Windows CoCreмжвьяв:. atelnstance 
для получения интерфейсного объекта IDispatch объекта автоматизации. Значение 
Result— это ссылка на интерфейс IDispatch для запрошенного объекта 
автоматизации. Ниже показана реализация функции CreateOleObject. 

    Function CreateOleObject(const CiassName: string): IDispatch; 
    var ClassID: TCLSID; 
    begin ClassID := ProgIDToClassID(CiassName); 
    01eCheck(CoCreateInstance(ClassID, nil, CLSCTS_INPROC_SERVER or 
CLSCTX_LOCAL_SERVER, IDispatch, Result)); 
    end; 

    Вспомните предыдущую главу: идентификаторы CLSID и ProgID вместе с 
информацией о серверах СОМ хранятся в реестре Windows. 
    Для получения доступа к удаленному серверу DCOM используйте функцию 
createRemoteComObject, которая определена в модуле ComObj. 
    Теперь, после получения ссылки на объект автоматизации Word .Basic, можно 
вызывать любые методы этого объекта и использовать любые свойства. В данном 
примере мы просто вызываем метод Insert и передаем ему строку, которая будет 
вставлена в активный документ Microsoft Word. Заметьте, что после запуска 
созданный фрагмент кода запускает и активизирует Word (если это приложение еще 
не запущено). Более подробную информацию о свойствах и методах Word можно 
получить в справке Microsoft Word для Windows. 

    | Совет « 
    Каково время существования объекта автоматизации? Как было сказано в 
предыдущей главе, для всех объектов СОМ (объекты автоматизации являются 
подмножеством объектов СОМ) подсчитываются ссылки. Когда счетчик ссылок 
объекта становится равным нулю, объект уничтожается. Заметив это, вспомним, 
что переменная WordBasicObject объявлена глобально, а не как 'локальная 
переменная процедуры ButtonlClick. Следовательно, на время существования 
объекта автоматизации (однажды созданного) время существования локальной 
переменной влиять не будет. Локальная переменная не использовалась, потому 
что, как только переменная типа OleVariant выходит за пределы области 
определения, Delphi автоматически уменьшает значение счетчика ссылок 
интерфейсного объекта IDispatch, ссылка на который хранилась в переменной типа 
OleVariant. Наиболее вероятно, что в данном примере уменьшение значения 
счетчика ссылок приведет к закрытию приложения Microsoft Word. Следовательно, 
объявление глобальной переменной WordBasicObject гарантирует, что время 
существования переменной будет таким же, как и время существования приложения, 
содержащего описываемый фрагмент кода. 

    Глава 20. Автоматизация OLE                                       469 

    Я обещал рассказать о том, как Delphi определяет, какой фрагмент кода 
относится к объекту автоматизации, а какой — нет. Для этого в Delphi 
используются переменные типа OleVariant. Delphi известно, что эти переменные 
являются реализацией механизма позднего связывания, который используется в 
Delphi для связи с серверами автоматизации. Кроме того, Delphi известно, что в 
переменных OleVariant точка используется только для доступа к свойствам или 
методам объекта автоматизации. Следовательно, если во время выполнения 
приложения переменная OleVariant не содержит ссылки на интерфейс IDispatch, 
будет сгенерирована исключительная ситуация. Информация о методе (или 
свойстве), который следует после точки (.), сохраняется в вашем приложении и 
передается объекту автоматизации по требованию во время выполнения приложения. 

    Свойства объектов автоматизации 

    Свойства объектов автоматизации в основном идентичны свойствам стандартных 
классов Delphi. Они используются для получения и установки внутренних 
значений, определяющих состояние объекта автоматизации. Например, показанный 
ниже фрагмент кода устанавливает значение свойства Sound объекта автоматизации 
Beeper, который будет создан ниже в этой главе. В данном примере переменная 
Beeper принадлежит типу OleVariant. 

    Beeper.Sound := MB_ICONEXCLAMATION; 

    Автоматизация также поддерживает параметризованные свойства, синтаксически 
подобные массивам свойств класса Delphi, которые во многих случаях ведут себя 
как методы. Например, при использовании объектов Oracle (сервер автоматизации 
баз данных) в коде, устанавливающем соединение с сервером баз данных, 
используется свойство с тремя параметрами. Ниже показан фрагмент кода, 
использующий такое свойство. 

    var 
    DBMSServer : OleVariant; 
    begin 
    DBMSServer.OpenDatabase['US.borland.corn', 'SysAdmin / Password', 0] 

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

    Beeper.SoundBeep[MB_ICONEXCLAMATION] ; 

    Этот метод получает один параметр (как метод) для установки нового звука, 
который будет воспроизводиться при этом и последующих вызовах. 
    Одним из основных ограничений свойств объектов автоматизации является 
ограничение типов данных, которые можно использовать для значении "свойств. 
Ниже перечислены типы данных,'которые можно использовать при работе со 
свойствами объектов автоматизации.          
    • Byte 
    • Integer 
    Я Smallint 
    В Currency 
    • Double 
    • Single 
    • WideString                                                               
    •I TDateTime 
    • WordBool 
    • OleVariant                                                               
            '' 
    Это ограничение не является результатом применения Delphi для работы с 
объектами автоматизации. Оно введено самой архитектурой автоматизации 
OLE, согласно которой все значения свойств, аргументы методов и возвращаемые 
значения передаются как данные вариантного типа (для Delphi это 
OleVariant). Таким образом, на ограничение типов данных, которые можно 
использовать для значений свойств объектов автоматизации, с одной стороны, 
повлияла сама архитектура автоматизации OLE, а с другой — необходимость 
упаковки данных, передаваемых от клиента серверу и наоборот. В среде OLE 
существуют механизмы передачи более сложных структур данных (это интерфейсные 
объекты IStorage и IDataObject), но рассмотрение этих механизмов выходит за 
рамки главы. Есть также методы использования массивов данных общего типа 
(надежных массивов), которые будут описаны далее в этой главе. 

    470                                          Часть V. Работа с OLE 


    Методы объектов автоматизации 

    Как и свойства объектов автоматизации, методы объектов автоматизации 
обладают несколькими интересными возможностями, что позволяет гибко 
использовать методы объектов автоматизации. Конечно, эти методы можно вызывать 
и как методы классов Delphi, которые вызывают, указывая все параметры. Ниже 
показан пример такого вызова. 

    SomeObject.OpenFile('noname.dat', 'File Description Text'); 

    В Delphi установлено жесткое ограничение, не допускающее передачи методу 
объекта автоматизации более 64 аргументов. Это значит, что если метод объекта 
автоматизации требует 65 параметров, то ваше приложение потерпит неудачу. 
Чтобы разрешить использование большего количества параметров, нужно увеличить 
значение константы MaxDispArgs в модуле ComObj. После этого модуль нужно 
перекомпилировать, чтобы внесенные изменения повлияли'на создаваемые 
приложения. (Я очень удивлюсь, если вам понадобится больше 64 параметров для 
вызова методов объектов автоматизации.) 

    Позиционные параметры методов 

    Как альтернативу передаче всех аргументов методу автоматизация OLE 
позволяет объектам принимать от контроллеров вызовы, в которых опущены 
некоторые параметры выбранного метода. Это возможно только в том случае, если 
объект поддерживает позиционные параметры. Предположим, что вы работаете с 
объектом автоматизации, обладающим методом Rectangle. Метод Rectangle получает 
пять необязательных параметров. Определение этого метода на языке Basic 
выглядит приблизительно так, как показано ниже. 

    Rectangle [.Color = number] [,.Left = number] [,.Top = number] 
    4> [,.Width = number] [,.Height = number] 

    Следовательно, объект автоматизации поддерживает значения по умолчанию для 
всех параметров этого метода. Метод Rectangle можно вызывать для рисования 
прямоугольника в выбранной по умолчанию позиции, но с шириной и высотой, 
равными 100 точкам, и красным цветом. 

    SomeObject.Rectangle(cIRed, , , 100, 100); 

    А показанный ниже вызов, нарисует прямоугольник с левой вершиной в точке 
20, 40 и шириной, высотой и цветом, установленными по умолчанию. 

    SomeObject.Rectangle(, 20, 40); 

    Заметьте, что для опущенных параметров не нужно указывать две запятые, так 
как запятые используются только для разделения позиции параметров. 

    Именованные параметры методов 

    Последний тип параметров, используемый средствами автоматизации OLE, — это 
именованные параметры. Их использование означает, что можно забыть о запятых, 
которые необходимы для позиционных параметров, и указывать имена параметров, 
значения которых нужно передавать. Например, чтобы нарисовать прямоугольник 
красного цвета с шириной и высотой, равной 100 точкам, в позиции, которая 
установлена по умолчанию, можно воспользоваться следующим вызовом. 

    SomeObject.Rectangle(Color = cIRed, Width = 100, Height = 100); 

    Итак, мы рассмотрели базовые приемы работы с объектами автоматизации. 
    Чтобы ознакомиться с работой простого клиента автоматизации,  
и просмотрите листинг 20.1. Приложение, 
исходный код которого показан В ЛИСТИНГе, демонстрирует ИСПОЛЬЗОВаНИе Объекта 
aBTO

    Word.Basic, ПОМещеННОГО В Microsoft Word ДЛЯ 

    This example demonslrales how loconnecllolheWo.d Base ; automation 
ob]eclwilhinWo(d (01 Windows. The text you see  , here wilbe copied oveilo new 
Wold document and Ihen printed^ —toeeie—хви                                    
                                                       homWoid foi Windows via 
OLE Automation                : 
    Windows 7.0, для вставки содержимого поля TMemo в документ 
    Word и печати этого документа. Окно приложения показано на                 
                  I 
    рис. 20.1.                                                                 
                              ! 
    Рис. 20.1. Клиентское приложение, использующее сервер автоматизации word. 
Basic  I_______________ 


    |  Глава 20. Автоматизация OLE                                       471 


    Листинг 20.1. \UDELPHl3\CHP20\woRDCLNT\CLiENTFM. PAS. Демонстрация 
соединения с объектом автоматизации Word. Bas ic в составе Word для Windows 

    unit ClientFm; 
    interface 
    uses 
    Windows, Messages, SysUtiis, Classes, Graphics, Controls, Forms, Dialogs, 
    Buttons, StdCtrls, ExtCtrls; 
    type 
    TWordClinetForm = class(TForm) 
    Panell: TPanel; 
    Memol: TMemo; 
    CloseButton: TSpeedButton; 
    CopyButton: TSpeedButton; 
    PrintButton: TSpeedButton; 
    procedure CopyButtonClick(Sender: TObject); 
    procedure CloseButtonClick(Sender: TObject); 
    procedure PrintButtonClick(Sender: TObject); 
    private 
    ( Private declarations ) 
    public 
    WordBasicObject : OleVariant; 
    end; 
    var 
    WordClinetForm: TWordClinetForm; 
    implementation 
    ($R *.DFM} 
    Uses ComObj ; 
    function HandleException(E : Exception): Boolean; 
    // 
    // Назначение. Централизованная функция обработки исключительных 
    //             ситуаций автоматизации OLE. 
    // 
    const 
    WordError = "Error connecting to Word for Windows - '; 
    begin 
    Result := True; 
    if (E is EOleError) or (E is EOleSysError) or 
    (E is EOleException) then 
    ShowMessage(WordError + E.Message) 
    else 
    Result := False; // Исключительная ситуация не обработана. 
    end; 
    procedure TWordClinetForm.CopyButtonClick(Sender: TObject); 
    // 
    // Назначение. Установить связь с Word для Windows и 
    //          копировать содержимое элемента Memol в созданный 
    //          документ Word. 
    // 
    var 
    I : Integer; 
    begin try 
    // Установлена ли связь с Word для Windows? Если нет, 
    // то установить ее. 


    472                                           Часть V. Работа с OLE 


    if VarType(WordBasicObject) <> varDispatch then 
    WordBasicObject := CreateOleObject('Word.Basic'); 
    WordBasicObject.FileNew; 
    for I := 0 to Memol.Lines.Count - 1 do 
    WordBasicObject.Insert( Memol.Lines[I] ); 
    except 
    on E : Exception do 
    if not HandleException(E) then Raise; 
    end; 
    end; 
    procedure TWordClientForm.CloseButtonClick(Sender: TObject); 
    // 
    // Назначение. Закрывает активный документ Word, если он был 
    //          создан, без записи документа. После этого форма 
    //          TWordClientForm закрывается. Связь между клиентом и 
    //          Word разрывается после завершения приложения. 
    // 
    begin 
    try 
    // Создана ли связь с Word для Windows? Если да, то закрыть 
    // созданный документ. 
    if VarType(WordBasicObject) = varDispatch then 
    WordBasicObject.FileClose (2); // 1) Записать/закрыть. 2) Только закрыть. 
    Close; 
    except 
    on E : Exception do 
    if not HandleException(E) then Raise; 
    end; 
    end; 
    procedure TWordClinetForm.PrintButtonClick(Sender: TObject); 
    // 
    // Назначение. Печатает активный документ Word, если была 
    //          установлена связь с сервером автоматизации 
    //          Word для Windows. 
    // 
    begin 
    try 
    // Создана ли связь с Word для Windows? 
    // Если нет, выдать сообщение об ошибке. 
    if VarType(WordBasicObject) <> varDispatch then 
    raise EOleError.Create('No connection established'); 
    WordBasicObject.FilePrint; 
    except 
    on E : Exception do 
    if not HandleException(E) then Raise; 
    end; 
    end; 
    end. 


    заметку 

    Электронная справка по Word содержит более полную информацию о методах и 
свойствах сервера автоматизации Word. Basic. 


    Углубленное изучение автоматизации OLE 

    В этом разделе аспекты передачи параметров и связь между типами данных 
variant и средствами автоматизации рассматриваются более подробно. Прежде 
всего, рассмотрим основные принципы работы типа variant. 

    Глава 20. Автоматизация OLE                                       473 


    Вариантные типы и автоматизация OLE 

    Тип OleVariant является одним из самых сложных типов данных (точнее, 
структур данных) в Delphi. Формат типа OieVariant определен записью TVarData, 
расположенной в модуле System. Ниже показано определение записи 
TVarData. 

    TVarData = record 
    VType: Word; 
    Reservedl, Reserved2, Reserved3: Word; 
    case Integer of 
    varSmallint: (VSmallint: Smallint); 
    varlnteger:  (VInteger: Integer); 
    varSingle:   (VSingle: Single); 
    varDouble:   (VDouble: Double); 
    varCurrency: (VCurrency: Currency) ; 
    varDate:     (VDate: Double); 
    varOleStr:   (VOleStr: PWideChar); 
    varDispatch: (VDispatch: Pointer) ; 
    varError:    (VError: Integer) ; 
    varBoolean:  (VBoolean: WordBool) ; 
    varUnknown:  (VUnknown: Pointer) ; 
    varByte:     (VByte: Byte); 
    varString:   (VString: Pointer) ; 
    varArray:    (VArray: PVarArray) ; 
    varByRef:    (VPointer: Pointer); 
    end; 


    Зачем нужны в Delphi вариантные типы 

    Тип OleVariant используется в Delphi только для работы со средствами 
автоматизации OLE. Автоматизация OLE использует тип variant, так как 
интерпретирующая среда Microsoft Visual Basic нуждается в подобном типе, чтобы 
обеспечить присваивание необъявленным переменным значений любого типа в 
произвольный момент выполнения программы. Следовательно, как наверняка заметил 
читатель, этот тип данных противоречит всей концепции жесткой проверки типов, 
которая используется в Delphi. Основное правило — не использовать переменные 
типа variant вне фрагментов кода, работающих с сервером автоматизации. И даже 
работая с объектами автоматизации, использовать тип variant нужно в редких 
случаях, так как при доступе   | к свойствам и методам объектов автоматизации 
Delphi автоматически преобразует передаваемые параметры   i в тип variant. По 
сравнению со стандартными типами данных Delphi, использование типов variant 
увеличи- ^1 вает размер расходуемой памяти и снижает производительность 
приложения.                              , 
    Как вы могли заметить, тип OleVariant разделен на две части — тип данных, 
которые хранятся в переменной, и сами данные (или ссылки на них). 
Следовательно, переменной типа OleVariant значение можно присвоить двумя 
способами. Первый — позволить Delphi выполнить присваивание, как показано ниже. 

    V := 25; 

    Другой способ — выполнить работу Delphi собственноручно, объявив тип 
значения, хранимого в переменной. Ниже показан пример такого присваивания. 

    TVarData(V).Type := varlnteger; 
    TVarData(V).VInteger := 25; 

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

    TVarData(V).VType := varError; 
    // Переменным этого типа значения присваивать не нужно. 
    ServerObj ect.Method(V) 

    В табл. 20.1 перечислены коды типов переменных OleVariant, используемые 
для указания типа значения, хранимого в переменной OleVariant. 

    474                                           Часть V. Работа с OLE 


    Таблица 20.1. Коды типов для OleVariant, определенные в модуле System 

    Код типа             Описание    Тип данных Delphi 

    varEmpty	Значение не указано	Нет 
    varNull	Значение Null	Нет 
    varByte	8-разрядное целое со знаком	Byte 
    varSmallint	16-разрядное целое со знаком	Smallint 
    varlnteger	32-разрядное целое со знаком	Integer 
    varSingle	Значение с плавающей точкой одинарной точности	Single 
    varDouble	Значение с плавающей точкой двойной точности	Double 
    varCurrency	Денежное значение одинарной точности	Currency 
    varDate	Значение даты и времени	TDateTime 
    varOleStr	Ссылка на динамически выделенную строку в формате JU^J^DE,	WideString 
    varString	Ссылка на динамическую строку в формате Pascal	AnsiString 
    varDispatch	Ссылка на объект автоматизации OLE	Ссылка на интерфейс iDispatch 
    varError	Код ошибки операционной системы	Нет 
    varBoolean	16-разрядное булево значение	WordBool 
    varVariant 
    varUnknown	Ссылка на неизвестный объект OLE	Ссылка на интерфейс I Unknown 
    

    Переменные variant, не содержащие значений 

    Большинство типов данных являются очевидными и понятными, но это относится 
не ко всем типам. Тип varEmpty представляет переменные, которым не присвоено 
никакого значения. Для сравнения можно рассмотреть тип varNull. представляющий 
переменную OleVariant, которой значение было присвоено лишь однажды, после 
чего она потеряла свое значение. Поскольку значения указанных типов часто 
используются, разработчики Delphi определили две глобальные переменные, Null и 
Unassigned, принадлежащие типам varNull и varEmpty соответственно. 

    Переменные variant, содержащие строки 

    Для передачи строк в технологии OLE используется тип данных varOleStr. 
Строки OLE (varOleStr) отличаются от стандартных строк Delphi (AnsiString) 
тем, что в строке OLE для хранения каждого символа выделяется два байта, а в 
стандартных строках Delphi — один байт. Следовательно, среда OLE не может 
оперировать стандартными строками Delphi (AnsiString), которые представлены 
кодом типа varString. Это дополнительный тип переменных типа variant, 
реализованный в Delphi для поддержки стандартных строк AnsiString в переменных 
типа variant. Тип varString можно использовать только в приложениях, созданных 
в Delphi. Если используется переменная типа varString, то при ее передаче 
объекту автоматизации строка varString будет автоматически преобразована в 
формат varOleStr. 

    Переменные variant, содержащие интерфейсы 

    Тип varDispatch представляет переменные variant, которые содержат 
указатели на интерфейс IDispatch. Delphi знает, что переменные variant, 
которые содержат значение типа varDispatch, нужно обрабатывать, как ссылки на 
интерфейс IDispatch. Тип varUnknown представляет переменные variant, которые 
содержат ссылку на произвольный интерфейс lUnknown. Этот интерфейс клиент 
может использовать для получения других интерфейсов с помощью метода Querylnterface. 

    Флаги кодов типа переменных variant 

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

    Глава 20. Автоматизация OLE                                       475 


    Таблица 20.2. Флаги кодов типа, определенные в модуле System 

    Флаг кода типа     Описание 

    varTypeMask	Битовая маска (SOFFF) для получения кода типа 
    varArray	Бит, указывающий, является ли переменная variant массивом 
    varByRef	Бит, указывающий, что содержит переменная: данные или указатель 
на эти данные 


    Переменные variant, содержащие ссылки на данные 

    Иногда данные могут передаваться по ссылке, а не путем вставки реальных 
данных в 16-байтовую структуру переменной variant. В этом случае флаг varByRef 
логически добавлен (операция OR) к значению поля VType, в котором хранится код 
типа. Чтобы определить код типа переменой variant, нужно выполнить логическое 
умножение (операция AND) значения поля VType и флага varTypeMask, как показано ниже. 

    if (TVarData(V).VType and varTypeMask) = varDispatch then... 


    Переменные variant, содержащие значения variant 

    Типы varVariant и varArray работают рука об руку. Тип varVariant 
используется исключительно для передачи массивов типа variant. А если код 
varArray логически суммируется с кодом какого-либо типа данных (включая 
varVariant), значит, переменная типа OleVariant содержит массив данных, код 
типа которых хранится в поле VType. Например, если показанное ниже выражение 
истинно, то переменная V содержит массив значений типа Integer. 

    If TVarData(V).VType = (varlnteger and varArray) then... 

    В следующем разделе описаны способы использования массивов переменных variant. 

    Подпрограммы поддержки переменных variant 

    В табл. 20.3 показаны все процедуры и функции для манипуляций переменными 
variant. Эти подпрограммы определены в модуле System. 

    Таблица 20.3. Процедуры и функции поддержки типов variant, определенные в 
модуле System 

    Подпрограмма      Описание 

    VarClear	Присваивает переменной типа OleVariant значение Unassigned 
    VarCopy	Копирует значение переменной-источника в переменную-приемник. Обе 
переменные принадлежат типу OleVariant 
    VarCast	Преобразует значение переменной-источника OleVariant в указанный 
тип varXXXX и сохраняет полученное значение в переменной-приемнике OleVariant. 
Значение varXXXX не включает кодов varArray и varByRef 
    VarType ^-n•i	- Возвращает код varXXXX указанной переменной. Младшие 12 
разрядов значения VType определяют код типа переменной OleVariant. 
Возвращаемое функцией значение соответствует значению поля VType записи TVarData 
    VarAsType	Преобразует переменную-источник OleVariant в указанный тип 
varXXXX и возвращает преобразованную переменную. Используемые коды типов не 
должны включать коды varArray и varByRef 
    VarIsEmpty	Возвращает значение True, если переменная типа OleVariant 
содержит значение Unas-signed (varEmpty) 
    VarIsNull	Возвращает значение True, если переменная типа OleVariant 
содержит значение Null (varNull) 
    VarToStr	Преобразует целые числа, числа с плавающей точкой, логические 
значения и строки в составе переменной OleVariant в строку. Эффект от 
использования этой функции не отличается от эффекта прямого присваивания 
строковой переменной значения переменной типа OleVariant 
    VarFromDateTime	Возвращает значение типа OleVariant, которое содержит 
указанное значение даты и времени TDateTime 
    VarToDateTime	Преобразует значение типа OleVariant в значение типа TDateTime 


    476 Часть V. Работа с OLE 

    Прежде всего, рассмотрим функцию VarType. Вместо использования записи 
TVarData для определения типа переменной variant можно воспользоваться 
функцией VarType. В предыдущем примере, демонстрирующем работу с объектом 
автоматизации Word. Basic, использовалась функция VarType для определения, 
установлена ли связь с объектом автоматизации. Для этого была выполнена 
показанная ниже проверка, соответствует ли тип переменной объекта 
автоматизации типу varDispatch. 

    If VarType(WordBasicObject) = varDispatch then 
    WordBasicObject.FileClose(2); // 1 - сохранить и закрыть. 
    // 2 - закрыть без сохранения. 
    Конечно, в данном случае можно использовать и функцию VarIsEmpty, так как 
Delphi всегда инициализирует переменные типа OleVariant значением varEmpty. 
    If not VarIsEmpty(WordBasicObject) then 
    WordBasicObject.FileClose(2); // 1 - сохранить и закрыть. 
    // 2 - закрыть без сохранения. 


    Массивы OleVariant (надежные массивы) 

    Последний тип данных, которые можно использовать при работе с объектами 
автоматизации, — это массивы OleVariant. Массив типа OleVariant — это 
переменная типа OleVariant, которая содержит указатель на область памяти, 
являющийся массивом 16-байтовых записей TVarData. Каждый элемент этого массива 
принадлежит типу OleVariant и может содержать любые значения типа OleVariant, 
описанные выше. Ясно, что большинство разработчиков на любят слишком часто 
применять массивы OleVariant, так как последние используют много памяти. Но в 
интерпретирующих средах разработки, таких как Visual Basic, использование 
памяти не учитывалось во время разработки технологии автоматизации OLE. Таким 
образом, при передаче массивов данных в объекты автоматизации массивы типа 
OleVariant хороши настолько, насколько они этого заслуживают. Для объявления 
массива типа OleVariant используйте функцию VarArrayCreate, определение 
которой показано ниже. 

    function VarArrayCreate(const Bounds: array of Integer; VarType: Integer): Variant; 

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

    function VarArrayOf(const Values: array of Variant): Variant; 

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

    Procedure TFormI.VarArrayCreateButtonClick(Sender: TObject); 
    var 
    V: OleVariant; 
    begin 
    V := VarArrayCreate([0, 4], varVariant) ; 
    V[0] := 1; 
    V[1] := 1234.5678; 
    V[2] := 'Using Delphi 3': 
    V[3] := True; 
    ShowMessage(V[2]); // Вывод сообщения 'Using Delphi 3'. 
    end; 

    Функция VarArrayOf является оболочкой для функции VarArrayCreate. Она 
позволяет одновременно создавать и инициализировать значениями массивы данных 
типа OleVariant с переменным числом элементов. Ниже приведен пример 
использования функции VarArrayOf для создания массива элементов OleVariant. 

    Procedure TFormI.VarArrayOfButtonClick(Sender: TObject); 
    var 
    V: OleVariant;                                                       .ii 
    begin                                                            I 
    V := VarArrayOf([1, 10, 100, 1000]); 
    ShowMessage(V[2]); // Вывод сообщения '100'. 
    end; 


    Глава 20. Автоматизация OLE                                       477 

    Чтобы передать объекту автоматизации многомерный массив данных типа 
OleVariant, можно воспользоваться тем, что, поскольку элемент одномерного 
массива принадлежит типу OleVariant, каждый элемент массива может содержать 
ссылку на массив элементов типа OleVariant. Приведенный ниже пример 
демонстрирует создание массива из двух элементов, каждый из которых является 
массивом элементов типа OleVariant. 

    Procedure TFormI.MultiDimensionVariantArrayButtonClick(Sender: TObject); 
    var 
    V: OleVariant; 
    begin 
    V := VarArrayCreate ( [1, 2], varVariant); 
    V[1] := VarArrayOft[1, 10, 100, 1000]); 
    V[2] := VarArrayOf([1, 20, 200, 2000]); 
    ShowMessage(V[2][3]); // Вывод-сообщения '2000'. 
    end; 

    В табл. 20.4 перечислены дополнительные функции, реализованные в Delphi 
для поддержки массивов элементов типа OleVariant. 

    1 Таблица 20.4. Процедуры и функции поддержки массивов OleVariant, 
определенные в модуле System 

    Подпрограмма        Описание 

    VarArrayRedim      Изменяет размер массива типа OleVariant путем изменения 
максимального значения индекса. Существующие элементы массива не изменяются. 
Новые элементы устанавливаются равными нулю или значению varEmpty 
    VarArrayDimCount   Возвращает размерность массива 
    VarArrayLowBound   Возвращает минимальное значение индекса указанного 
измерения массива типа OleVariant 
    VarArrayHighBound  Возвращает максимальное значение индекса указанного 
измерения массива типа OleVariant 
    VarArrayLock       Возвращает ссылку на буфер необработанных данных 
массива типа OleVariant после блокировки массива 
    VarArrayUnLock     Снимает блокировку массива, который ранее был 
заблокирован функцией VarArrayLock 
    VarArrayRef        Возвращает OleVariant типа varByRef, которое ссылается 
на массив OleVariant 
    VarIsArray          Возвращает значение True, если указанная переменная 
типа OleVariant является массивом OleVariant (флаг кода типа — varArray) 

    В следующем разделе речь пойдет о методах создания объектов автоматизации 
в Delphi. 

    Создание серверов автоматизации 

    Создавать серверы автоматизации можно было и в Delphi 2. В Delphi 3 этот 
процесс значительно отличается от своего аналога в предыдущей версии. В Delphi 
2 методы и свойства объекта автоматизации определялись в разделе automated 
описания класса Object Pascal, который являлся потомком класса TAutoObject. 
Такой код все еще можно компилировать в Delphi 3, но он не отражает принципов 
построения объектов автоматизации в Delphi 3. 
    В Delphi 3 все средства взаимодействия с архитектурой ActiveX были 
переработаны, что привело к созданию среды DAX (Delphi AcliveXframework). 
Среда DAX значительно упростила программирование элементов СОМ, OLE и ActiveX. 
В рамках этой среды создан полностью обновленный класс TAutoObject, который 
определен в модуле ComObj. Кроме того, среда DAX поддерживает прозрачный 
доступ к библиотекам типов, которые полезны при работе с объектами 
автоматизации и базовыми элементами управления ActiveX. Как будет показано в 
оставшейся части этой главы, процессы создания объектов автоматизации 
сгруппированы вокруг создания информации об объекте в библиотеке типов. 
    Процесс создания объекта автоматизации в Delphi 3 можно разделить на 
следующие базовые этапы. 
    1. Определите, какой сервер автоматизации вы будете создавать — внутренний 
или внешний. Для создания внешнего сервера выберите команду File^New 
Application. Для создания внутреннего сервера автоматизации следует выполнить 
команду File^New и во вкладке ActiveX выбрать пиктограмму ActiveX Library. 

    478                                           Часть V. Работа с OLE 

    2. Чтобы создать объект автоматизации, выберите команду File^New и во 
вкладке ActiveX дважды щелкните на пиктограмме Automation Object. На экране 
появится диалоговое окно, в поле Class Name которого нужно указать имя 
создаваемого класса. Это имя должно удовлетворять правилам именования 
идентификаторов, используемым в Delphi. 
    3. В появившемся на экране окне редактора библиотек типов укажите свойства 
и методы объекта автоматизации. 
    4. В модуле объекта автоматизации дополните заготовки кода, который 
сгенерирован редактором библиотек типов. 
    5. Зарегистрируйте сервер автоматизации в Windows. Сначала откомпилируйте 
модуль (<Ctrl+F9>). После этого, чтобы зарегистрировать внешний сервер, просто 
запустите его (<F9>). Для регистрации внутреннего сервера выполните команду 
Run^>Register ActiveX Server. 

    Создание внешнего сервера автоматизации 

    Создать сервер в Delphi 3 довольно просто. Возможно, самое сложное в этом 
процессе — понять, какие данные нужно поместить в библиотеку типов, чтобы 
определить объект автоматизации. Наилучший способ разобраться в этом — 
рассмотреть пример. Поэтому сначала рассмотрим начальные этапы создания 
сервера автоматизации, а затем — полный исходный код сервера. 
    Создаваемый в рассматриваемом примере объект автоматизации будет выполнять следующее. 
    Я Воспроизводить пять возможных звуков функции MessageBeep. 
    в Хранить внутри себя значения текущего звука. 
    И Содержать свойство Sound, которое можно использовать для установки или 
получения значения текущего звука. 
    И Содержать метод Веер для воспроизведения заданного по умолчанию звука. 
    в Содержать свойство SoundBeep, которое назначает звук по умолчанию и 
сразу же воспроизводит его. 
    • Вести журнал всех действий, выполненных контроллером автоматизации. 
    Выберите команду File'=>New Application. Теперь для создания объекта 
автоматизации выполните команду File^New и выберите вкладку ActiveX. На рис. 
20.2 показана вкладка ActiveX хранилища объектов (Object Repository). 
    После этого дважды щелкните на пиктограмме Automation Object (см. рис. 
20.2). Это приведет к запуску мастера Automation Object (рис. 20.3). 

    Рис. 20.2. Вкладка ActiveX хранилища объектов 

    Рис. 20.3. Для запуска мастера Automation Object нужно указать имя класса 
и метод управления копиями (список Interfacing) 

    В поле Class Name введите имя Beeper и оставьте без изменения значение 
списка Instancing. Значение Multiple Instances означает, что по каждому 
запросу контроллера автоматизации будет создаваться новая копия объекта 
автоматизации. В отличие от этого выбор значения Single Instance приведет к 
тому, что все контроллеры будут получать доступ к копии объекта автоматизации, 
которая была создана первой. Значение Internal информирует Delphi о том, что 
объект автоматизации будет использоваться только в создаваемом в данный момент 
приложении и не требует регистрации в реестре Windows. 

    Глава 20. Автоматизация OLE                                       479 

    После щелчка на кнопке ОК в окне, показанном на рис. 20.3, запустится 
мастер Automation Object. Этот мастер создаст библиотеку типов с именем, 
совпадающим с именем проекта (в данном случае— PROJECTI. TLB) и откроет 
созданную библиотеку с помощью редактора библиотек типов (Type Library Editor) 
(рис. 20.4). 

    Рис. 20.4. Редактировать информацию об объекте автоматизации можно в окне 
редактора библиотек типов 

    Как показано на рис. 20.4, библиотека типов состоит минимум из трех 
элементов. Первый элемент — это ее имя, информация о библиотеке и атрибуты 
библиотеки. Изменим имя библиотеки на BeeperLib. 
    Основным объектом нашего внимания будет следующий элемент библиотеки 
(IBeeper), так как он определяет методы и свойства объекта автоматизации 
Beeper. Интерфейс IBeeper базируется на интерфейсе IDispatch. На данном этапе 
мы не будем выполнять никаких действий с интерфейсом. 
    Последний элемент библиотеки типов— это класс составных объектов (compound 
object class, coclass) Beeper. Составной объект, или coclass, — это контейнер 
интерфейсных объектов. Если щелкнуть на классе составного объекта Beeper и 
выбрать вкладку Members, то в списке интерфейсов появится интерфейс IBeeper. 
Для успешной автоматизации объект должен предоставлять только один интерфейс, 
порожденный от IDispatch. В данном случае это интерфейс IBeeper. 

    Рис. 20.5. Создание перечисления типов звуков 

    Перечисление — это еще один базовый элемент библиотеки типов. Например, 
нужно создать перечисление, представляющее типы звуков, которые поддерживает 
объект автоматизации. Для этого щелкните правой кнопкой мыши на области окна, 
в которой показано дерево элементов библиотеки, и выберите команду New=>Enum 
(рис. 20.5). Это же действие можно выполнить с помощью кнопки Enum панели 
инструментов редактора библиотек типов. 

    480                                           Часть V. Работа с OLE 

    В правой части окна, в строке ввода Name, введите SoundTypes в качестве 
имени перечисления. Нажмите клавишу <Enter>. Чтобы добавить перечисляемые типы 
звуков, выберите вкладку Members (если она еще не выбрана) и введите 
показанные ниже типы звуков вместе со строками справки, которые соответствуют 
этим типам (рис. 20.6). 

    MB_Ok = 0; 
    helpstring = 'Ok sound'; 
    MB_ICONHAND = 16; 
    helpstring = 'ICONHAND sound'; 
    MB_ICONQUESTION = 32; 
    helpstring = 'ICONQUESTION sound'; 
    MB_ICONEXCLAMATION =48; 
    helpstring = 'ICONEXCLAMATION sound'; 
    MB_ICONASTERISK = 64; 
    helpstring = 4CONASTERISK sound'; 

    Рис. 20.6. Чтобы создать новый интерфейс, класс составного объекта или 
перечисление, щелкните правой кнопкой мыши на области дерева элементов и 
выберите команду New 

    Теперь можно приступить к определению свойств и методов объекта 
автоматизации Beeper. Выберите интерфейс IBeeper и вкладку Members этого 
элемента. Во вкладке введите показанные ниже свойства и методы вместе с 
соответствующими идентификаторами DispID (рис. 20.7). 

    property Sound: SoundTypes; dispid 1; 
    property SoundBeep[SoundType: SoundTypes]: SoundTypes; readonly; dispid 2; 
    procedure Beep; dispid 3; 
    
    Рис. 20.7. Окно редактора библиотек типов после вставки свойств и методов 
интерфейса IBeeper 

    Глава 20. Автоматизация OLE  481 
    16 Использование Delphi 3. Специальное издание 

    В дополнение к стандартному средству переключения между окном редактора 
библиотек типов и другими окзаметку   нами ^Р"' (команда View^Type Library) 
эта среда программирования обеспечивает пользователей альтернативными 
возможностями переключения. Если вы хотите добавить новое свойство или метод 
во время редактирования кода модуля объекта автоматизации, то можете 
воспользоваться командой меню Edit^Add To Interface, которая выведет на экран 
диалоговое окно Add To Interface. Любые свойства и методы, добавленные в 
объект с помощью этого окна, будут автоматически отображены в библиотеке типов 
сервера автоматизации. 
    Заметьте, что перечисление SoundTypes используется в определении свойств 
как тип данных. Поэтому перечисление SoundTypes было создано первым. 
    Определение объекта автоматизации Beeper завершено, и можно приступать к 
кодированию функциональности сервера, чем и заканчивается процесс создания 
объекта автоматизации. Перед просмотром полного исходного кода выясните, какой 
код был сгенерирован в результате работы с редактором библиотек типов. 
    Прежде всего, Delphi создаст исходный файл, имеющий такое же имя, как и 
проект, с суффиксом _TLB (например, Project_TLB). В данном случае 
использовалось имя BeepLib. В листинге 20.2 показан модуль BeepLib, который 
содержит информацию о перечислении, интерфейсе и классе составного объекта, 
который находится в библиотеке проекта. 

    Листинг 20.2. \UDELPHI ЛСНР2 О \BEEPERI \BEEPLIB . РАЗ. Модуль BeepLib, 
созданный на основе библиотеки типов, содержащей определение интерфейса IBeeper 

    unit BeepLib; 
    { ProjectlLib Library } 
    { Version 1.0 } 
    interface 
    uses Windows, ActiveX, Classes, Graphics, OleCtrls, StdVCL; 
    const 
    LIBID_BeepLib: TGUID = '{12CFDC67-5519-11DO-A45E-444553540000}'; 
    const 
    { Перечисление SoundTypes. } 
    MB_Ok = 0; 
    MB_ICONHAND = 16; 
    MB_ICONQUESTION = 32; 
    MB_ICONEXCLAMATION = 48; 
    MB_ICONASTERISK = 64; 
    const 
    ( Идентификатор класса составного объекта. } 
    Class_Beeper: TGUID = '{12CFDC69-5519-11DO-A45E-444553540000}'; 
    type 
    { Предварительные объявления. } 
    IBeeper = interface; 
    DBeeper = dispinterface; 
    Beeper = IBeeper; 
    SoundTypes = TOleEnum; 
    ( Интерфейс Dispatch для объекта Beeper. } 
    IBeeper = interface(IDispatch) 
    ['{12CFDC68-5519-11DO-A45E-444553540000}'] 
    function Get Sound: SoundTypes; safecall; 
    procedure Set Sound(Value: SoundTypes); safecall; 


    482                                           Часть V. Работа с OLE 


    function Get_SoundBeep(SoundType: SoundTypes): SoundTypes; safecall; 
    procedure Beep; safecall; 
    property Sound: SoundTypes read Get Sound write Set Sound; 
    property SoundBeep[SoundType: SoundTypes]: SoundTypes read Get_SoundBeep; 
    end; 
    ( Объявление Displnterface для двойственного интерфейса Ibeeper. } 
    DBeeper_ = dispinterface 
    ['(12CFDC68-5519-11DO-A45E-444553540000}'] 
    property Sound: SoundTypes dispid 1; 
    property SoundBeep[SoundType: SoundTypes]: SoundTypes readonly dispid 2; 
    procedure Beep; dispid 3; 
    end; 
    { Объект Beeper. } 
    CoBeeper = class 
    class function Create: IBeeper; 
    class function CreateRemote(const MachineName: string): IBeeper; 
    end; 
    implementation 
    uses ComObj ; 
    class function CoBeeper.Create: IBeeper; 
    begin 
    Result := CreateComObject(Class Beeper) as IBeeper; 
    end; 
    class function CoBeeper.CreateRemote(const MachineName: string): IBeeper; 
    begin 
    Result := CreateRemoteComObject(MachineName, Class_Beeper) as IBeeper; 
    end; 
    end. 

    Модуль BeepLib объявляет интерфейс IBeeper, но не реализует его. Это 
сделано в другом модуле, который, как вы заметили, добавлен в проект. Имя 
этого модуля должно быть UNIT2. PAS, если вы начали работу с нового проекта. 
Как показано в листинге 20.3, Delphi не только генерирует определение класса, 
реализующего интерфейс IBeeper, но и создает заготовки методов, чтобы 
упростить написание кода объекта автоматизации. 

    Листинг 20.3, UNIT2. PAS. Реализация интерфейса IBeeper, сгенерированная в Delphi 

    unit Unit2; 
    interface 
    uses 
    ComObj, Projectl_TLB; 
    type 
    TMyBeep = class(TAutoObject, IBeeper) 
    protected 
    function Get_Sound: SoundTypes; safecall; 
    function Get_SoundBeep: BeepTypes; safecall; 
    procedure Beep; safecall; 
    procedure Set_Sound(Value: SoundTypes); safecall; 
    end; 
    implementation 


    Глава 20. Автоматизация OLE                                       483 


    uses ComServ; 
    function TBeeper.Get_Sound: SoundTypes; 
    begin 
    end; 
    function TBeeper.Get_SoundBeep: SoundTypes; 
    begin 
    end; 
    procedure TBeeper.Beep; 
    begin 
    end; 
    procedure TBeeper.Set_Sound(Value: SoundTypes); 
    begin 
    end; 
    initialization 
    TAutoObjectFactory.Create(ComServer, TBeeper, Class_Beeper, ciMultiInstance); 
    end. 

    Прежде чем рассмотреть полный исходный код, хотелось бы обратить внимание 
читателя на несколько интересных аспектов, затронутых в показанном листинге. 
Класс TBeeper использует новую технологию интерфейсов СОМ для включения 
функциональности объекта TAutoObject среды DAX, который знает, как ведет себя 
объект TBeeper в составе сервера автоматизации. Также можно заметить, что 
класс TBeeper поддерживает интерфейс I Веере г, что требует реализации 
виртуальных методов интерфейса IBeeper. 
    Кроме того, каждый из методов использует соглашение по вызовам safecall. 
Эффект этого соглашения совпадает с эффектом соглашения FastCall, но safecall 
предназначено для перехвата необработанных в методе исключительных ситуаций и 
передачи их в качестве исключений OLE контроллеру автоматизации. 
    Самая "волшебная" строка в этом проекте — строка инициализации модуля 
UNIT2, показанная ниже. 

    TAuotObjectFactory.Create(ComServer, TBeeper, Class_Beeper, ciMultiInstance); 

    Класс TAutoObjectFactory также является частью архитектуры DAX. Созданный 
объект TAutoObjectFactory выполняет три основные задачи, перечисленные ниже. 
    • Объект автоматизации Beeper регистрируется вместе с объектом сервера СОМ Delphi. 
    • В реестр Windows вносится информация об идентификаторе класса объекта 
Beeper и его идентификаторе программы. Идентификатор программы создается в 
следующем формате: <Имя внутренней библиотеки типов>.<Имя класса составного 
объекта>. В нашем примере это BeepLib.Beeper. 
    • Объект TAutoObjectFactory выполняет все обязанности интерфейса 
IClassFactory самостоятельно, поэтому разработчику не нужно создавать код 
большого объема для каждого нового объекта. 
    Теперь рассмотрим реализацию сервера автоматизации Beeper. Проект этого 
сервера можно загрузить, открыв файл BEEPER. DPR, из каталога 
\UDELPHI3\CHP20\BEEPER1\. В листинге 20.4 содержится исходный код модуля 
BeepAuto, который реализует все функции объекта автоматизации Beeper. 
Реализация сервера автоматизации Beeper довольно проста. Таким образом, чтобы 
понять реализацию объекта Beeper, хорошенько изучите модуль BeepAuto. Здесь 
реализовано то, что рассматривалось в этом разделе. 

    Листинг 20.4. \UDELPHl3\CHP20\BEEpERl\BEEPAUTO. PAS. Модуль BeepAuto 
содержит полную реализацию    | объекта автоматизации Beeper 

    unit BeepAuto; 
    interface 
    uses 


    484                                           Часть V. Работа с OLE 


    ComObj, BeepLib; 
    type 
    TBeeper = class(TAutoObject, IBeeper)* 
    private                     ~—————' 
    FSound : Integer; 
    protected 
    function Get Sound: SoundTypes; safecall; 
    function Get SoundBeep(SoundType: SoundTypes): SoundTypes; safecall; 
    procedure Beep; safecall; 
    procedure Set_Sound(Value: SoundTypes); safecall; 
    end; 
    implementation 
    uses ComServ, Windows, Beep.Form, SysUtils; 
    function TBeeper.Get_Sound: SoundTypes; 
    begin 
    BeeperLogForm.AddActionToLog('Get Sound value: ' + IntToStr(FSound) ); 
    Result := FSound; 
    end; 
    function TBeeper.Get_SoundBeep(SoundType: SoundTypes): SoundTypes; safecall; 
    begin 
    BeeperLogForm.AddActionToLog('Get SoundBeep') ; 
    Set_Sound(SoundType) ; 
    Beep; 
    Result := FSound; 
    end; 
    procedure TBeeper.Beep; 
    begin 
    BeeperLogForm.AddActionToLog('Beep method called'); 
    MessageBeep(FSound) ; 
    end; 
    procedure TBeeper.Set Sound(Value: SoundTypes); 
    const 
    InvalidSound = 'Invalid Sound property parameter. ' + 
    'The possible values are: MB_OK(0), MB_ICONHAND(16) , ' + 
    'MB_ICONQUESTION(32), MB_ICONEXCLAMATION(64) ' + 
    'and MB_ICONASTERISK(128)'; 
    begin 
    BeeperLogForm.AddActionToLog('Set Sound value to ' + IntToStr(Value)); 
    if not (Value in [MB_OK, MB_ICONHAND, MB_ICONQUESTION, 
    MB_ICONEX5LAHATION, MB_ICONASTERISK]) then 
    Raise EOleError.Create(InValidSound); //Exit; 
    if FSound <> Value then FSound := Value; 
    end; 
    initialization 
    TAutoObjectFactory.Create(ComServer, TBeeper, Class_Beeper, 
ciMultiInstance) ; 
    end. 

    Проект Delphi содержит связанную с ним библиотеку типов. Библиотека типов 
компилируется непосредственно в модуль ЕХЕ или DLL как ресурс. Если 
просмотреть исходный файл проекта (команда View^ Project Source), то можно 
заметить, что с помощью команды компилятора $R к проекту подключаются два типа ресурсов. 

    {$R *.RES) 
    ($R *.TLB) 

    В некоторых случаях, когда библиотека типов слишком большая, целесообразно 
распространять ее в отдельном файле с расширением TLB. 

    Глава 20. Автоматизация OLE                                       485 

    Для демонстрации объекта автоматизации Beeper был создан контроллер 
автоматизации, как рекомендовалось в предыдущей главе. Перед запуском 
контроллера автоматизации удостоверьтесь, что сервер автоматизации Beeper 
запускался хотя бы один раз, что позволило ему зарегистрироваться в реестре 
Windows. Чтобы проверить созданный контроллер автоматизации в действии, 
загрузите и запустите на выполнение проект CLIENT.DPR, расположенный в 
каталоге \UDELPHI3\CHP20\BEEPCTRL\. В листинге 20.5 содержится исходный код 
базовых элементов клиентского приложения. На рис. 20.8 показаны окна 
контроллера и сервера автоматизации во время выполнения. 

    Рис. 20.8. Контроллер автоматизации, работающий с сервером автоматизации Beeper 

    Листинг 20.5. \UDELPHI3\CHP20\BEEPCTRL\CLIENTFM. PAS. Код для связи с 
объектом автоматизации Beeper и манипуляций им 

    unit ClientFm; 
    interface 
    uses 
    Windows, Classes, Controls, Forms, Dialogs, ComCtrls, 
    StdCtrls, ExtCtrls; 
    type 
    TAutomationController = class(TForm) 
    SoundTypes: TRadioGroup; 
    ConnectButton: TButton; 
    SetSoundButton: TButton; 
    BeepButton: TButton; 
    SoundBeepButton: TButton; 
    DisconnectButton: TButton; 
    procedure ConnectButtonClick(Sender: TObject); 
    procedure SoundBeepButtonClick(Sender: TObject); 
    procedure BeepButtonClick(Sender: TObject); 
    procedure SetSoundButtonClick(Sender: TObject); 
    procedure DisconnectButtonClick(Sender: TObject); 
    private 
    BeeperObject : OleVariant; 
    function GetMBSoundType : Byte; 
    end; 
    var 
    AutomationController: TAutomationController; 
    implementation 
    <$R *.DFM} 
    uses ComObj; 
    const 
    NotConnectedMsg = 'Please connect to the Beeper automation object first'; 
    ConnectFailMsg = "Make sure automation object is registered'; 


    486                                           Часть V. Работа с OLE 


    function TAutomationController.GetMBSoundType : Byte; 
    // 
    // Назначение. Возвращает значение Sound Type, соответствующее 
    //          выбранному пользователем типу звука. 
    // 
    const 
    SoundTypes : array[0..4] of Integer = (MB_Ok, MB_ICONHAND, 
    MB_ICONQUESTION, MB_ICONEXCLAMATION, MB_ICONASTERISK) ; 
    begin 
    Result := SoundTypes[Self.SoundTypes.Itemlndex]; 
    end; 
    procedure TAutomationController.ConnectButtonClick(Sender: TObject) ; 
    // 
    // Назначение. Устанавливает связь с объектом автоматизации 
    // Beeper. Функция CreateOleObject во'з"враТца?т' "ин'терфе^с 
    // IDispatch, который будет присвоен переменной Delphi типа          | 
    // OleVariant. Этот тип используется при работе с автоматизацией 
    // OLE. 
    // 
    begin 
    if VarIsEmpty(BeeperObject) then 
    try 
    BeeperObject := CreateOleObject('BeepLib.Beeper'); 
    except 
    ShowMessage(ConnectFailMsg); 
    end; 
    end; 
    procedure TAutomationController.SoundBeepButtonClick(Sender: TObject) ; 
    // 
    // Назначение. Присваивает выбранное пользователем значение 
    // свойству SoundBeep объекта Beeper. 
    // Объект Beeper также воспроизведет этот звук. 
    // 
    begin 
    if VarIsEmpty(BeeperObject) then 
    ShowMessage(NotConnectedMsg) 
    else 
    BeeperObject.SoundBeep[GetMBSoundType] ; 
    end; 
    procedure TAutomationController.BeepButtonClick(Sender: TObject) ; 
    // 
    // Назначение. Вызывает метод Веер объекта автоматизации Beeper. 
    // 
    begin 
    if VarIsEmpty(BeeperObject) then 
    ShowMessage(NotConnectedMsg) 
    else 
    BeeperObject.Beep; 
    end; 
    procedure TAutomationController.SetSoundButtonClick(Sender: TObject) ; 
    // 
    // Назначение. Присваивает выбранное пользователем значение // свойству 
Sound объекта автоматизации Beeper. // 
    begin 
    if VarIsEmpty(BeeperObject) then 
    ShowMessage(NotConnectedMsg) 
    else 
    BeeperObject.Sound := GetMBSoundType; 
    end; 


    Глава 20. Автоматизация OLE                                       487 


    procedure TAutomationController.DisconnectButtonClick(Sender: TObject) ; 
    // 
    // Назначение. Освобождает наше соединение с сервером. 
    // Когда бы переменная типа OleVariant ни принимала новое 
    // значение, Delphi прежде всего проверяет, является ли текущее 
    // значение ссылкой на объект автоматизации. Если это так, то 
    // перед присваиванием нового значения вызывается метод Release 
    // объекта, ссылка на который находится в переменной. 
    // Следовательно, если единственный клиент, использовавший объект 
    // Beeper, закончил работу с этим объектом, то работа сервера 
    // объекта Beeper будет завершена. 
    // 
    begin 
    BeeperObject := Unassigned; 
    end; 
    end. 


    Создание внутреннего сервера автоматизации 

    Создание внутреннего сервера автоматизации практически не отличается от 
создания внешнего сервера, которое мы выполняли в предыдущем разделе, за 
исключением того, что создается динамическая библиотека, а не выполняемый 
файл. Таким образом, вместо команды File^New Application нужно выполнить 
команду File^New и щелкнуть на вкладке ActiveX, во вкладке ActiveX дважды 
щелкнуть на пиктограмме ActiveX Library и продолжить создание сервера 
автоматизации, следуя рекомендациям предыдущего раздела. В листинге 20.6 
содержится файл проекта для внутреннего сервера автоматизации Beeper. Этот 
файл вы можете найти на дополнительно распространяемой дискете. 

    1 Листинг 20.6. \UDELPHi3\CHP20\BEEPER2\BEEPER.DPR. Пример внутреннего 
сервера автоматизации 

    library Beeper; 
    uses 
    ComServ, 
    Forms, 
    BeepAuto in 'BeepAuto.pas' (Beeper: CoClass), 
    BeepLib in 'BeepLib.pas', 
    BeepForm in 'BeepForm.pas' {BeeperLogForm}; 
    {$R *.RES} 
    ($R *.TLB} 
    exports 
    DllGetClassObject, 
    DllCanUnloadNow, 
    DllRegisterServer, 
    DllUnregisterServer; 
    begin 
    Application.Initialize; 
    end. 

    Для демонстрации работы внутреннего сервера автоматизации Beeper можно 
воспользоваться проектом клиентского приложения, которое применялось для 
проверки внешнего сервера. Тем не менее сначала нужно зарегистрировать 
внутренний сервер. Поскольку это динамическая библиотека, ее нельзя запустить, 
как внешний сервер Beeper. Ниже перечислено пять способов регистрации 
внутреннего сервера в Windows, но ни один из них не является настолько 
элегантным, чтобы подойти для распространения приложений. 
    • В среде Delphi выполните команду меню Run^Register ActiveX Server. 
    • Используйте приложение REGSVR32 компании Microsoft вместе с именем 
динамической библиотеки. К сожалению, эта утилита поставляется только с 
Windows NT или на компакт-дисках MSDN. 

    488                                           Часть V. Работа с OLE 

    • Используйте REG-файл, в котором содержится вся необходимая для реестра 
информация, включая длинные пути к ключам. 
    и Если у вас есть дополнительно распространяемая дискета, воспользуйтесь 
контекстным меню Register/Unregister ActiveX server, которое рассматривалось в 
качестве примера в главе об интерфейсах СОМ. 
    В Создайте приложение, которое загружает внутренний сервер и вызывает 
экспортируемую функцию DllRegisterServer. 
    Полный исходный код внутреннего сервера автоматизации Beeper можно найти в 
каталоге \UDELPHI3\CHP20\BEEPER2 на дополнительно распространяемой дискете. 
Для регистрации этого сервера дважды щелкните на файле BEEPER. REG, 
расположенном в каталоге проекта. 

    Резюме 

    В этой главе рассматривались аспекты использования автоматизации в рамках 
одного компьютера. Средства автоматизации хорошо работают на удаленных 
компьютерах. Существует две технологии, поддерживающие распределенные объекты 
СОМ. Первая— технология DCOM компании Microsoft, которая функционирует только 
на платформе Windows NT 4. Во время создания этой книги компания Microsoft 
работала над реализацией технологии DCOM на платформе Windows 95. Вторая 
технология, поддерживающая распределенные объекты СОМ, — это технология OLE 
Enterprise корпорации Open Environment (дочернее предприятие компании 
Borland). Технология OLE Enterprise поддерживает объекты СОМ на платформах 
Windows 95 и Windows NT 4. Этот продукт прост в использовании и включен в 
пакет программ Delphi Client/Server. OLE Enterprise и сопутствующие технологии 
также обладают возможностью балансировать загрузку объектов автоматизации 
клиентскими запросами в пределах нескольких серверов. Таким образом, если один 
из объектов автоматизации перегружен или не работает, OLE Enterprise может 
получить объект автоматизации из другого сервера. 
    В следующей главе будет рассказано, что автоматизация OLE используется не 
только для создания изолированной функциональности. Средства автоматизации 
являются основой методов работы со свойствами, методами и событиями элементов 
управления ActiveX. 

    Глава 20. Автоматизация OLE                                       489