Использование компонентов COM в ASL

Содержание

  1. Краткое введение в технологию Microsoft
  2. Тип OBJECT
  3. Тип VARIANT
  4. Коллекции и перечислители коллекций
  5. Средства просмотра структуры кокласса, отладки и получения документации
  6. Недостатки реализации
  7. Немного практики
  8. Список литературы

Краткое введение в технологию Microsoft COM

Технология Microsoft COM (Component Object Model) является базой для всех существующих технологий со словами OLE (Object Linking and Embedding) и ActiveX в названии. Цель и суть технологии COM - сделать программу, поставляющую некие возможности (компонент), детально управляемой извне путем обращений к ее объектной модели из другой программы - клиента. Реально существующие объекты компонента, например книги, страницы, ячейки, диаграммы Microsoft Excel благодаря COM становятся доступными для управления извне Excel. Связь между компонентом и клиентом устанавливается на уровне исполняемых программ, поэтому технология COM независима от языка программирования. Компонент может быть написан, например, на C++, а клиент - на Visual Basic и наоборот. Компонент может быть реализован как в виде .DLL (как вариант - .OCX), так и в виде .EXE. Первый вариант работает быстрее, но и более опасен - ошибка в компоненте вызовет сбой клиента.

Ниже перечислены основные понятия, применяемые в COM.

GUID (Globally Unique Identifier) - 128-битный числовой универсальный уникальный идентификатор. GUID генерируется специальной утилитой на основе точного времени и номера сетевой карты, чем и гарантируется его уникальность. GUID используется в COM для идентификации сущностей. Вы можете встретить также и другое его название - UUID (Universal Unique Identifier). Строковая запись GUID выглядит, например, так: "{F05DF3A1-4999-11D4-B94E-0080AD74CA3A}".

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

Кокласс однозначно идентифицируется GUID-ом кокласса, также называемым CLSID. Информация о коклассах всех установленных в Windows компонентов хранится в ветви реестра HKCR\CLSID. Кокласс, как правило, имеет еще один идентификатор, называемый ProgID. ProgID - это неуникальный, но зато более осмысленный алиас к CLSID. Для сравнения: CLSID кокласса "приложение Excel" есть "{00024500-0000-0000-C000-000000000046}", а ProgID - "Excel.Application". Все ProgID-ы перечислены в разделе реестра HKCR\ и ссылаются на соответствующие CLSID.

Структура кокласса, т.е. определения его свойств и методов, физически хранится в библиотеке типов. Библиотека типов (Type Library) - это бинарный файл с расширением .TLB или .OLB, детально описывающий все коклассы, поставляемые компонентом. Библиотека типов может не храниться в отдельном файле, а пристыковываться к исполняемому файлу компонента. Библиотека типов однозначно идентифицируется GUID-ом библиотеки, также называемым LIBID. Наличие бинарной библиотеки типов обеспечивает языковую независимость компонентов и позволяет создать браузер объектов (Object Browser) для просмотра структуры коклассов. Информация о библиотеках типов хранится в ветви реестра HKCR\TypeLib.

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

Поскольку вся информация о коклассах компонента хранится в библиотеке типов, то программа, использующая интерфейс кокласса, могла бы обратиться к библиотеке типов и узнать, как именно следует пользоваться методами и свойствами объекта. Более того, все компилирующие языки: C++, Visual Basic, Java именно так и делают. Компилятор обращается к библиотеке типов, проверяет корректность типов, и настраивает в создаваемом исполняемом файле указатели на свойства и методы. Когда программа, собранная компилятором, начинает выполняться, уже не могут произойти ошибки типа "метод XXX не поддерживается объектом". Такой метод работы с компонентом называется ранним связыванием.

А как же работают интерпретаторы? Им ничего не остается, кроме как проверять корректность всех обращений к свойствам и методам на стадии исполнения программы, что и называется поздним связыванием. Разумеется, в процессе исполнения программы интерпретатором может оказаться, что метода не существует, или типы параметров не совпадают и т.п. Очевидно также, что позднее связывание существенно медленнее раннего.

Microsoft поставляет для интерпретаторов специальную технологию на базе COM, называемую COM автоматизацией. COM автоматизация позволяет интерпретатору, не разбирая библиотеки типов, в процессе исполнения программы  сделать "выстрел наугад", т.е. попытаться обратиться к свойству или методу объекта, предполагая, что имеется свойство или метод с таким-то именем, что метод принимает столько-то параметров таких-то типов и пр. Другими словами, что интерпретатор видит в исполняемой программе, то (буквально) он и пытается выполнить. Именно таким образом работают с объектами COM встроенные в Windows и в Internet Explorer интерпретаторы VBScript и JavaScript, и именно таким образом работает интерпретатор ASL. Соответственно, объект COM, поддерживающий интерфейс COM автоматизации, называют еще объектом COM автоматизации.

Изложенное выше представление о технологии COM является, разумеется, предельным упрощением, но достаточно для написания простейших ASL-программ с использованием объектов COM автоматизации. Для получения фундаментальной информации о COM рекомендуются издания: [1], [2], [3], [4]. Первые три имеются в библиотеке фирмы, четвертое - в MSDN в английской версии (на CDROM).

Тип OBJECT

Для работы с объектами компонента COM в ASL предназначен тип данных OBJECT. Значение типа OBJECT хранит ссылку на объект, а не сам объект автоматизации.

Значения типа OBJECT, т.е. ссылки можно присваивать друг другу. После выполнения присваивания obj1 := obj2 обе переменные obj1 и obj2 будут ссылаться на один и тот же объект. Ссылки можно сравнивать. Если obj1 и obj2 ссылаются на один объект, то операция obj1 = obj2 возвратит 1.

Ссылку на объект можно использовать для обращения к членам объекта: свойствам и методам. Пусть переменная obj1 ссылается на объект реально существующего кокласса "Excel.Application". Тогда к свойству Visible обращаемся высказыванием obj1.Visible, а к методу GetOpenFilename высказыванием obj1.GetOpenFilename("*.*"); в скобках указываются параметры метода. Операция обращения к члену объекта называется операцией разыменования, а точка - это оператор разыменования. Результат операции разыменования может быть ссылкой на объект, в этом случае возможно цепочечное разыменование, например obj1.Workbooks.Add(). Очевидно также, что высказывания
 obj2 := obj1.Workbooks;
 obj2.Add();
и
 obj1.Workbooks.Add();
эквивалентны.

Так же как переменные всех остальных типов, после объявления и до момента первого присваивания переменная типа OBJECT находится в неинициализированном состоянии, а неинициализированная переменная может применяться только в качестве приемника значения, но не в качестве источника.

Инициализированная ссылка может указывать на реальный объект, а может быть пустой и ни на какой объект не указывать. После выполнения процедуры SETEMPTY получаем пустую ссылку. Пустую ссылку можно проверять и присваивать, но нельзя использовать для обращения к методам и свойствам, иначе получим исключение класса Exc_RunTime.

Чтобы сослаться на реальный объект, необходимо воспользоваться встроенной функцией CREATE_OBJECT, указав в качестве параметра ProgID или CLSID кокласса.

 VAR obj: OBJECT;
BEGIN
 obj := CREATE_OBJECT("Excel.Application");
END.

Функция CREATE_OBJECT создает реальный объект и возвращает ссылку на него. Каждый объект, в соответствии со спецификацией COM, ведет счетчик ссылок на себя, и автоматически удаляется, когда счетчик достигает нуля. Рассмотрим сказанное на примере.

 VAR obj1, obj2, obj3: OBJECT;
BEGIN
 obj1 := CREATE_OBJECT("Excel.Application");
 (* Создали объект кокласса Excel.Application *)
 (* Счетчик ссылок объекта кокласса Excel.Application стал равным 1*)
 
 obj2 := obj1;
 (* Счетчик ссылок объекта кокласса Excel.Application стал равным 2 *)
 
 obj3 := CREATE_OBJECT("Word.Application");
  (* Создали объект кокласса Word.Application *)
 (* Счетчик ссылок объекта кокласса Word.Application стал равным 1*)
 
 obj1 := obj3;
 (* Счетчик ссылок объекта кокласса Excel.Application стал равным 1 *)
 (* Счетчик ссылок объекта кокласса Word.Application стал равным 2 *)
 
 SETEMPTY(obj2);
 (* Счетчик ссылок объекта кокласса Excel.Application стал равным 0 и объект самоуничтожился *)
 
 (* ...........................................................*)
 
END
 (* По завершении программы все глобальные переменные уничтожаются *)
 (* Счетчик ссылок объекта кокласса Word.Application стал равным 0 и объект самоуничтожился *)
.

Помимо CREATE_OBJECT есть еще две встроенные функции, предназначенные для получения ссылки на объект автоматизации. Windows хранит таблицу всех существующих в системе объектов автоматизации, и при помощи функции GET_OBJECT можно получить ссылку на существующий объект автоматизации. А функция LOAD_PICTURE может загрузить в память изображение в форматах BMP, JPEG, GIF, ICO, WMF, EMF, и возвратить ссылку на объект автоматизации, связанный с изображением.

При обращении клиента к методам и свойствам объекта, последний по собственной инициативе может выбросить исключение. Такие исключения обрабатываются V32 и относятся к классу Exc_Object, код и текст исключения формируются компонентом. Возможные исключения перечисляются в документации компонента.

Некоторые свойства объекта могут быть объявлены автором компонента как свойства "только для чтения". При попытке изменить значение свойства "только для чтения" будет выброшено исключение класса Exc_RunTime.

Член интерфейса кокласса может быть членом по умолчанию (свойством по умолчанию или методом по умолчанию). Имя члена по умолчанию в определенных ситуациях может опускаться при обращении к нему. Пользователь не имеет возможности объявить некий член объекта членом по умолчанию, напротив, этот вопрос раз и навсегда решает автор компонента. Интерфейс кокласса может иметь лишь один член по умолчанию: либо одно свойство, либо один метод.

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

VAR
 Obj: OBJECT;
BEGIN
 Obj := CREATE_OBJECT("Example.Object");  (* Example.Object - вымышленный кокласс *)
 Obj := 3.5;
END.

Очевидно, что присвоить ссылке на объект число 3.5 невозможно. В данном случае имеется ввиду присваивание значения свойству объекта по умолчанию. Как правило, свойство по умолчанию называется Value и хранит данные простого типа (число, строка и т.д.), а не ссылку на объект. Приведенный ниже пример полностью эквивалентен предыдущему.

VAR
 Obj: OBJECT;
BEGIN
 Obj := CREATE_OBJECT("Example.Object");  (* Example.Object - вымышленный кокласс *)
 Obj.Value := 3.5;
END.

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

VAR
 Obj: OBJECT;
 N1, N2: NUMERIC[0];
BEGIN
 Obj := CREATE_OBJECT("Example.Object");  (* Example.Object - вымышленный кокласс *)
 N1 := Obj;
 N2 := Obj + 1;
END.

Общее правило таково: если оба параметра операции присваивания или сравнения есть ссылки на объекты, то выполняется соответственно копирование ссылок или сравнение ссылок. Во всех остальных случаях вместо объекта в операции применяется его свойство по умолчанию.

Имя метода по умолчанию можно опускать при его вызове.

VAR
 Obj: OBJECT;
 S: STRING[];
BEGIN
 Obj := CREATE_OBJECT("Example.Object");  (* Example.Object - вымышленный кокласс *)
 S := Obj.SomeMethod("Some string parameter");
END.

Предположим, что для объекта кокласса "Example.Object" SomeMethod является методом по умолчанию. Тогда пример можно переписать так.

VAR
 Obj: OBJECT;
 S: STRING[];
BEGIN
 Obj := CREATE_OBJECT("Example.Object");  (* Example.Object - вымышленный кокласс *)
 S := Obj("Some string parameter");
END.

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

Значения типа OBJECT не могут храниться в файлах базы данных. Функция FLDTOSTR для значения типа OBJECT игнорирует шаблон, для пустой ссылки возвращает пустую строку, а для любой непустой - строку "OBJECT". STRTOFLD с типом OBJECT не работает вовсе (выбросит исключение). Функции MINOF и MAXOF для типа OBJECT вернут пустую ссылку.

Тип VARIANT

Все, кто хоть раз видел Microsoft Excel, знают, что ячейка листа Excel может содержать значения различных простых типов: строки, числа, даты. Более того, строка "на лету" преобразуется в дату, дата в число, число в строку, достаточно лишь сменить формат ячейки. Это не особенность Excel, а проявление возможностей фундаментального типа данных COM автоматизации, называемого VARIANT. Тип VARIANT частично поддерживается V32.

Результат любой операции разыменования ссылки на объект есть VARIANT.

Другими словами, любое свойство объекта есть значение типа VARIANT и любой результат метода-функции есть значение типа VARIANT.

У типа VARIANT имеется набор подтипов, в которых он может находиться:

Теоретически имеется еще ряд подтипов типа VARIANT, они достаточно редко используются и в данной реализации не поддерживаются. К таковым подтипам относится, например, абстрактная запись.

В библиотеке ASL имеется встроенная функция INSTANCEOF, возвращающая текущий подтип значения типа VARIANT. Текущий подтип значения типа VARIANT почти ничего не значит. В любой момент значение типа VARIANT может быть преобразовано из одного подтипа в другой. Приведенная ниже программа прописывает в ячейку Excel строку "123" и обнаруживает, что ячейка хранит число.

VAR obj: OBJECT;
BEGIN
 obj := CREATE_OBJECT("Excel.Application");
 obj.Visible := 1;
 obj.Workbooks.Add();
 obj.Cells(1, 1).Value := "123";
 STDMSG(FLDTOSTR(INSTANCEOF(obj.Cells(1, 1).Value), "", 0), INFORM);
END.

Тип любого параметра метода объекта также есть VARIANT.

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

Особо следует поговорить о подтипе "время" типа VARIANT. Время в VARIANT физически представляет собой дробное число с плавающей десятичной точкой, в котором целая часть есть число полных дней, прошедших с момента 30.12.1899 00:00:00, а абсолютное значение дробной части - время в долях дня. Если целая часть равна нулю, значение можно интерпретировать двояко: как относительное время (время суток) или как абсолютное время 30.12.1899 ЧЧ:ММ:СС. Примеры.

Числовое значение Интерпретация
2.25 01.01.1900 06:00
2.0 01.01.1900 00:00
1.0 31.12.1899 00:00
0.25 06:00 или 30.12.1899 06:00
0.0 00:00 или 30.12.1899 00:00
-0.25 06:00 или 30.12.1899 06:00
-1.0 29.12.1899 00:00
-1.25 29.12.1899 06:00
-1.5 29.12.1899 12:00

Разумеется, подтип "время" типа VARIANT с легкостью преобразуется в подтип "число" и обратно. Для работы c подтипом "время" типа VARIANT предназначены процедуры упаковки PackVARIANTDATE и распаковки UnPkVARIANTDATE времени из/в типы DATE и TIME/LONGTIME/LTIME.

Тип VARIANT статически совместим со всеми типами данных по всем операциям, кроме типа TIME/LONGTIME/LTIME. Несовместимость с типом TIME происходит из разной природы результатов операций вычитания для типов DATE и TIME: разница двух значений типа DATE есть число дней, а разница двух значений типа TIME - число часов. Тип TIME лишь односторонне совместим с типом VARIANT по присваиванию: VARIANT-у можно присвоить TIME (VARIANT будет содержать относительное время), но не наоборот.

Любая процедура, функция и операция ASL пытается преобразовать значение типа VARIANT к тому подтипу, который она ожидает.

Например, функция Str_Length, получив в качестве параметра значение типа VARIANT, преобразует его в строку, а операция ~ (отрицание) - в число. Разумеется, преобразование может не удаться, например строка "QQQ" в число не преобразуется. Тогда будет выброшено исключение 143 "Динамическая несовместимость типов".

Сложнее дело обстоит с бинарными операциями. Если тип одного из параметров - не VARIANT, то ожидание для другого параметра известно. Например, если в операции + (сложение) участвуют значения типов STRING и VARIANT, то предполагается конкатенация строк и параметр типа VARIANT будет преобразован в строку. Если же оба параметра бинарной операции имеют тип VARIANT, то интерпретатор сначала пытается выполнить числовую операцию, а если хотя бы один из параметров не преобразуется в число, то строковую операцию.

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

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

VARIANT разрешается указывать в качестве типа параметра по VAR (физически переменная в этом случае не создается). Например, если Вы передаете значение ячейки Excel в процедуру параметром по ссылке, то имет смысл описать параметр как VAR cell: VARIANT.

 PROCEDURE ChangeCell(VAR cell: VARIANT);
 BEGIN
  (* ..................................... *)
 END;

 VAR objXL: OBJECT;
BEGIN
 objXL := CREATE_OBJECT("Excel.Application");
 objXL.Visible := 1;
 objXL.Workbooks.Add();
 ChangeCell(objXL.Cells(1, 1).Value);
END.

Значения типа VARIANT также не могут храниться в файлах базы данных. Функция FLDTOSTR для значения типа VARIANT игнорирует шаблон и преобразует VARIANT в  подтип "строка". В STRTOFLD указать VARIANT в качестве целевого типа нельзя (функция выбросит исключение). Функции MINOF и MAXOF для типа VARIANT вернут неопределенное состояние VARIANT, также как SETEMPTY.

Коллекции и перечислители коллекций

Зададимся вопросом, как выглядит типичная объектная модель компонента? Как правило, имеется некий корневой объект (чаще всего этот объект называется Application), у которого есть ряд свойств и методов. Некоторые свойства корневого объекта - простые типы, а некоторые - объекты, в свою очередь имеющие свойства и методы и т.д. Объектная модель приложения статична по своей природе, некоторую динамику в нее вносят специальные объекты - коллекции.

Коллекция в COM - это объект, при помощи которого мы получаем доступ к другим объектам - элементам коллекции. Теоретически, коллекция может содержать не только объекты, но и "простые" типы данных: строки, числа и пр., т.е. любые подтипы типа VARIANT. Визуальным признаком того, что объект является коллекцией, служит, как правило, идентификатор в множественном числе: Workbooks, Documents, Folders и т.п.

Интерфейс коллекции определяется автором компонента, но, как правило, коллекции предоставляют следующие свойства и методы:

Все коллекции в обязательном порядке поддерживают только свойства Count и _NewEnum. Методы могут отсутствовать, например по следующим причинам:

Бывает и так, что типовые свойства и методы коллекции имеют нетрадиционное название, например Delete вместо Remove и т.п.

Существует два вида коллекций, в зависимости от того, может или нет существовать потенциальный элемент коллекции вне коллекции.

  1. Элемент коллекции не может существовать вне коллекции. Метод Add создает объект, исходя из параметров, добавляет его в коллекцию, и возвращает ссылку на объект. Как правило, все параметры метода Add необязательны, и свойства объекта можно задать уже после создания. Метод Remove удаляет элемент из коллекции и уничтожает его. Пример такого вида - коллекция книг Excel Workbooks.
  2. Объект - потенциальный элемент коллекции может существовать вне коллекции. Метод Add принимает в качестве аргумента ссылку на объект, помещает объект в коллекцию и не возвращает результата. Метод Remove удаляет элемент из коллекции, но не уничтожает его. В качестве параметров Remove принимает не сам объект, а идентифицирующие его как элемент коллекции признаки.

Часто возникает задача - перебрать в цикле все элементы коллекции. Для ее решения заведен еще один тип данных - ENUMERATOR. Тип ENUMERATOR - это фактически узкоспециализированный OBJECT. Значение типа ENUMERATOR, также как OBJECT, хранит ссылку на объект-перечислитель, а не сам перечислитель. Для создания объекта-перечислителя служит функция CreateENUMERATOR с одним параметром - ссылкой на коллекцию. Если объект не является коллекцией, будет выброшено исключение. После создания перечислитель позиционирован на первый элемент коллекции.

Для создания перечислителя можно также воспользоваться свойством коллекции _NewEnum, однако для единообразия рекомендуется всегда пользоваться функцией CreateENUMERATOR, поскольку нет абсолютной гарантии, что в любой коллекции свойство _NewEnum называется именно так (это зависит от автора компонента).

Ниже описан интерфейс перечислителя:

Т.е., перечислитель имеет четыре метода и не имеет свойств.

Так выглядит типичный перебор элементов коллекции.

 VAR objXL: OBJECT;
     e: ENUMERATOR;
BEGIN
 objXL := CREATE_OBJECT("Excel.Application");
 objXL.Workbooks.Add();
 objXL.Workbooks.Add();
 objXL.Workbooks.Add();
 e := CreateENUMERATOR(ObjXL.Workbooks);
 WHILE !e.AtEnd() DO
  STDMSG(e.Item().Name, INFORM);
  e.MoveNext();
 END;
END.

Значения типа ENUMERATOR не могут храниться в файлах базы данных. Функция FLDTOSTR для значения типа ENUMERATOR игнорирует шаблон, для пустой ссылки на перечислитель возвращает пустую строку, а для любой непустой - строку "ENUMERATOR". STRTOFLD с типом ENUMERATOR не работает вовсе (выбросит исключение). Функции MINOF и MAXOF для типа OBJECT вернут пустую ссылку.

Средства просмотра структуры кокласса, отладки и получения документации

  1. В средах Microsoft (Visual Basic, VBA, Visual InterDev, ...) основным средством для получения информации о структуре объекта служит Object Browser - средство для просмотра библиотек типов. В существующей реализации имеется лишь зачаток браузера библиотек типов - диалог "Type Libraries", поднимаемый по CtrlF8 из V32, Ted и отладчика ASL, и ориентированный на получение контекстной справки для тех компонентов, которые эту возможность поддерживают. Все компоненты Microsoft Office поддерживают контекстную справку. Диалог работает в двух режимах. В режиме "View All" отображаются все библиотеки типов по информации из реестра (их много - сотни), а в режиме "View Selected" - только выбранные Вами. В режиме "View Selected" выбранные библиотеки загружаются, поэтому если выбрано много библиотек, поднятие диалога происходит с задержкой. В момент первого поднятия диалога ряд библиотек (по большей части Microsoft Office) выбираются автоматически. Список выбранных библиотек хранится в ветви реестра HKCU\Software\Polak Computer System\Atol System Base\Object Browser\Selected TypeLib.
  2. В отладчике имеется средство для просмотра свойств и методов уже созданного объекта. Значения свойств объектов можно редактировать прямо в диалоге.

Недостатки реализации

  1. Просмотр структуры объекта реализован только в отладчике и только для уже созданных объектов. Посмотреть структуру произвольной библиотеки типов из клиента ASB невозможно.
  2. Не поддерживается обработка событий. Теоретически, если объект поддерживает события, то ASL-программа должна была бы работать следующим образом.
    1. В программе должны быть специальным образом описаны процедуры-обработчики событий.
    2. В теле программы создаются объекты (CREATE_OBJECT).
    3. По завершении тела программа висит и слушает события от объектов.
    4. Когда объект сгенерирует событие, в программе отработает процедура-обработчик этого события.
    5. Обработчик может отключиться от событий объекта вызовом DisconnectOBJECT.
    6. Программа завершается после отключения от всех объектов, генерирующих события.
  3. Имена свойств и методов объектов автоматизации разбираются регистронезависимо. То есть, если Вы вместо objXL.Workbooks напишете, например, objXL.WorkBOOKs, то программа будет работать. Проблема в том, что COM автоматизация изначально создавалась для Visual Basic, в котором принят регистронезависимый разбор идентификаторов, и разработчики Microsoft не позаботились о дополнительном параметре. Так что все регистрозависимые языки (например JavaScript), пользующиеся COM автоматизацией, страдают этим недостатком. В будущем возможен переход от COM автоматизации к прямому использованию библиотек типов, тогда вместо VAR objXL: OBJECT; будем писать VAR objXL: Excel.Application; и разбор свойств и методов будет регистрозависимым. Посему уже сейчас рекомендуется писать имена членов объекта так, как они пишутся в документации.
  4. Все параметры методов объектов по умолчанию передаются по значению. В принципе, для передачи параметра по ссылке никакой дополнительной информации от программиста не требуется. Контроллер автоматизации (в нашем случае - клиент ASB) обязан передавать результат выражения по значению, а переменную или свойство объекта - по ссылке. Если сервер автоматизации не ожидает параметр по ссылке, он автоматически преобразует его в параметр по значению. Однако реально передача параметра по ссылке используется крайне редко, а на как правило ненужные преобразования в тип VARIANT и обратно теряется масса времени, хуже того, будет потерян контроль за инициализированностью переменной. Поэтому было принято компромиссное решение: программист должен явно указать параметр, передаваемый по ссылке в метод объекта автоматизации, при помощи оператора REF(...).
  5. Частично поддержан подтип "безопасный массив" (SAFEARRAY) типа VARIANT - массив значений типа VARIANT. В результате через преобразование в SAFEARRAY и обратно реализована передача массива любого ASB-типа в метод объекта COM автоматизации по значению и по ссылке, а также прием массивового результата метода или свойства объекта автоматизации. Принятый результат подтипа SAFEARRAY типа VARIANT может быть присвоен массивовой переменной соответствующего ASB-типа  или передан в процедуру параметром по значению, описанным как массив соответствующего ASB-типа. Реализовать передачу SAFEARRAY в процедуру параметром по ссылке затруднительно. В данном случае ASL-программистам придется выполнять промежуточное присваивание.
  6. К сожалению, некоторая часть компонентов COM обладает прескверным характером. Это особенно опасно, когда компонент реализован как DLL. Например, обращения к некоторым методам MAPI (Microsoft CDO Library), реализующим универсальный, независимый от конкретной почтовой программы сервис электронной почты, изменяют текущую папку (только с Outlook 2000 в качестве почтовой программы по умолчанию).

Немного практики

Ниже приведен ряд примеров использования объектов автоматизации COM. Можно запустить их под отладчиком и посмотреть, как они работают.

Пример 1. "Hello, Word!"

VAR objWord: OBJECT;
BEGIN
 objWord := CREATE_OBJECT("Word.Application");
 objWord.Visible := 1;
 objWord.Documents.Add();
 objWord.ActiveDocument.Range(0, 0).InsertBefore("Hello, Word!");
END.

Пример 2. Набиваем лист Excel

PROCEDURE incDate(VAR d: VARIANT; inc: NUMERIC[0]);
BEGIN
 INC(d, inc);
END;

VAR
 objXL: OBJECT;
BEGIN
 objXL := CREATE_OBJECT("Excel.Application");
 objXL.Visible := 1;
 objXL.Workbooks.Add();

 objXL.Cells(1, 1) := 2;
 objXL.Cells(2, 1) := "=A1+1";
 objXL.Cells(1, 2) := "12345";
 objXL.Cells(2, 2) := "=B1+1";
 objXL.Cells(1, 3) := 13\10\2000;
 incDate(objXL.Cells(1, 3), objXL.Cells(1, 1));
 objXL.Cells(2, 3) := "=C1+1";
 objXL.Cells(1, 4) := 22:23;
 objXL.Cells(2, 4) := "=D1+0,25";
 PackVARIANTDATE(objXL.Cells(1, 5), 01\01\2000, 22:23, 20);
 incDate(objXL.Cells(1, 5), objXL.Cells(1, 1).Value);
 objXL.Cells(2, 5) := "=E1+1,25";
END.

Пример 3. Создание и отправка почтового сообщения с вложением

INCLUDE FIO, Reg, Shell;

VAR
 dfltMsgProfile: STRING[];
 objMapiSession: OBJECT;
 objMessage: OBJECT;
 objRecipient: OBJECT;
 urlPath: STRING[];
 objAttachment: OBJECT;
BEGIN
 (* Узнаем профиль по умолчанию *)
 Reg_Read("HKCU\Software\Microsoft\Windows Messaging Subsystem\Profiles\DefaultProfile",
          dfltMsgProfile);
 objMapiSession := CREATE_OBJECT("MAPI.Session");

 (* Подключаемся к MAPI *)
 objMapiSession.Logon(dfltMsgProfile, "", 1);

 (* Создаем новое сообщение в Outbox *)
 objMessage := objMapiSession.Outbox.Messages.Add();
 objMessage.Type := "IPM.Note";
 objMessage.Subject := "Test: Пересылка ярлыка РБК";

 objRecipient := objMessage.Recipients.Add();
 objRecipient.Type := 1;
 objRecipient.Name := "shur@polak.ru";
 objRecipient.Resolve(1);

 objMessage.Text := "Ярлык на РосБизнесКонсалтинг";

 (* Создаем на рабочем столе ярлык Интернета на сайт РБК *)
 urlPath := FIO_MakePath("РосБизнесКонсалтинг", "url", Shell_SpecialFolders("Desktop"));
 Shell_CreateUrlShortcut(urlPath, "http://www.rbc.ru");

 (* Добавляем ярлык в сообщение *)
 objAttachment := objMessage.Attachments.Add();
 objAttachment.Type := 1;
 objAttachment.Position := 0;
 objAttachment.Source := urlPath;
 objAttachment.Name := "РосБизнесКонсалтинг";

 (* Сохраняем и отправляем сообщение; поднимется диалог *)
 TRY
  objMessage.Send(1, 1);
 EXCEPT
 ON exc: Exc_Object DO
  STDMSG("Сообщение не было создано.|"+exc.Description, STOP);
 END;

 (* Отключаемся от MAPI *)
 objMapiSession.Logoff();
END.

Пример 4. Вращающаяся диаграмма Excel

VAR
 objXL: OBJECT;
 objXLchart: OBJECT;
 rotate: NUMERIC[0];
BEGIN
 objXL := CREATE_OBJECT("Excel.Application");
 objXL.Workbooks.Add();
 objXL.Cells(1,1) := 5;
 objXL.Cells(1,2) := 10;
 objXL.Cells(1,3) := 15;
 objXL.Range("A1:C1").Select();

 objXLchart := objXL.Charts.Add();
 objXL.Visible := 1;
 objXLchart.Type := -4100;

 rotate := 5;
 WHILE rotate <= 180 DO
  objXLchart.Rotation := rotate;
  INC(rotate, 5);
 END;

 rotate := 175;
 WHILE rotate > 0 DO
  objXLchart.Rotation := rotate;
  DEC(rotate, 5);
 END;
END.

Список литературы

  1. Д. Роджерсон. "Основы COM. 2е издание"
  2. Э. Трельсен. "Модель COM и применение ATL 3.0"
  3. Р. Оберг. "COM+ технология. Основы и программирование"
  4. K. Brockschmidt. "Inside OLE 2nd Edition"