Глава 3
Элементы LuaCOM
LuaCOM состоит из следующих элементов:
- Объекты LuaCOM, которые делают доступными в Lua COM-объекты;
- API LuaCOM, набор функций для различных задач (инициализация библиотеки, создание объекта, реализация автоматизации интерфейсов в Lua, обработка точек подключения, и т.д.);
- Автоматизация привязки, которая преобразует обращения к объектам LuaCOM в вызовы COM интерфейса и COM обращения к интерфейсу, реализованному в Lua, на вызовы функций Lua или доступ к таблицам;
- Правила конвертирования типов LuaCOM, которые управляют преобразованием типов между Lua и видами автоматизации;
- Правила передачи параметров LuaCOM, которые описывают как LuaCOM перевести список параметров Lua в COM и наоборот;
- другие объекты, вроде typelib, typeinfo, enumerator, и т.д.
3.1 LuaCOM API
API LuaCOM разделяется на две части: стандартный API и расширенный API.
Стандартный API включает в себя основные функциональные возможности, необходимые для использования COM объектов.
Расширенный API включает дополнительные функции API Lua, которые упрощают разработку приложений с использованием LuaCOM.
Это различие было сделано в связи с возможностью неограниченного роста функций, которые могли, в конечном итоге, загромоздить библиотеку, делая её очень большой и тяжелой в использовании.
В настоящее время, расширенный API полностью реализован в Lua и может быть легко удален без каких-либо проблем.
Стандартный API в дальнейшем разделяется на два класса: API Lua и API C/C++.
API C/C++ используется прежде всего для инициализации библиотеки и низкоуровневого построения объектов LuaCOM. Он объявляется в файле заголовка
luacom.h.
API Lua разрешает Lua-программам доступ ко всем функциональным возможностям LuaCOM.
Он реализуется как набор функций внутри глобальной таблицы, называемой
luacom; далее эти функции будут называться LuaCOM
методами.
Эта таблица создается и заполняется когда вызвана функция
luacom_open API C/C++.
Ниже приводится краткое изложение API LuaCOM. Подробные сведения от этих методах имеются в
главе 6.
Стандартный API Lua
МЕТОД METHOD | ОПИСАНИЕ DESCRIPTION |
CreateObject | Создает объект LuaCOM. |
NewObject | Создает объект LuaCOM реализованный в Lua. |
NewControl | Создает LuaCOM элемент управления (контрол) OLE реализованный в Lua. |
GetObject | Создает объект LuaCOM, связанный с экземпляром уже работающего COM-объекта. |
ExposeObject | Выставляет объект LuaCOM или элемент управления OLE, так что другие приложения могут ссылаться на него. |
RevokeObject | Отменяет операцию ExposeObject. |
RegisterObject | Заполняет записи в реестре, необходимые для выставления объекта СОМ или элемента управления OLE. |
UnRegisterObject | Удаляет записи в реестре, необходимые для выставления объекта СОМ или элемента управления OLE. |
Connect | Создает точку подключения между объектом и Lua таблицей. |
ImplInterface | Реализует интерфейс IDispatch, с помощью таблицы Lua. |
ImplInterfaceFromTypelib | Реализует интерфейс IDispatch, описанный в библиотеке типов (Type Library) с помощью таблицы Lua. |
addConnection | Соединяет два объекта LuaCOM. |
releaseConnection | Отключает объект LuaCOM от его точки подключения. |
isMember | Проверяет, соответствует ли имя методу или свойству объекта LuaCOM. |
ProgIDfromCLSID | Получает ProgID связанный с CLSID. |
CLSIDfromProgID | Получает CLSID связанный с ProgID. |
GetIUnknown | Возвращает интерфейс IUnknown к объекту LuaCOM как полные пользовательские данные (userdata). |
DumpTypeInfo | Выводит на консоль типовые сведения об указанном объекте LuaCOM. Этот метод следует использовать только для отладки. |
GetCurrentDirectory | Возвращает текущий каталог. |
CreateLuaCOM | Преобразовывает полные пользовательские данные IUnknown в объект LuaCOM. |
ImportIUnknown | Конвертирует облегченнные пользовательские данные (указатель - pointer) в полные пользовательские данные IUnknown. |
DetectAutomation | Используется для реализации COM-серверов. Разыскивает в командной строке /Register или /UnRegister /Automation
(без учета регистра) и вызывает определяемые пользователем функции для регистрации, отмены регистрации или выставления объектов, вводя цикл обработки сообщений в последнем случае.
Если командной строки нет, то предполагается что все это уже в работе, вызывается функция выставления и возврата. |
Расширенный API Lua
МЕТОД METHOD | ОПИСАНИЕ DESCRIPTION |
CreateLocalObject | Создает объект LuaCOM как . |
CreateInprocObject | Создает объект LuaCOM как . |
ExportConstants | Экспортирует все константы библиотеки типов (автономные или связанные с объектом LuaCOM) для общей окружающей среды (или возможно для таблицы). |
DumpTypeLib | Создает файл HTML с описанием библиотеки типов. |
GetType | Возвращает строку, с описанием типа объекта, в случае если этот объект принадлежит библиотеке LuaCOM. |
ViewTypeLib | Запускает DumpTypeLib и показывает созданный файл с помощью Internet Explorer®. |
pairs | Делает тоже самое, что и pairs для COM Enumerators. |
FillTypeLib | Создает таблицу с описанием библиотеки типов. |
FillTypeInfo | Создает таблицу с описанием сведений типа. |
Стандартный API C/C++
ФУНКЦИЯ FUNCTION | ОПИСАНИЕ DESCRIPTION |
luacom_open | Инициализирует библиотеку LuaCOM в структуре Lua. Её нужно вызывать перед любым использованием функций LuaCOM. |
luacom_close | Функция завершения LuaCOM. |
luacom_detectAutomation | Эта функция является помощником для создания COM серверов. Она отыскивает в командной строке ключи “/Automation” и “/Register” и соответственно вызывает некоторые определяемые пользователем функции Lua. |
luacom_IDispatch2LuaCOM | Принимает IDispatch интерфейс и создает объект LuaCOM чтобы выставить его, перемещая объект по Lua стеку. |
3.2 Объекты LuaCOM
LuaCOM имеет дело с
объектами LuaCOM, которые являются не более чем Lua таблицей с метатаблицей LuaCOM, что ссылается на LuaCOM'овский объект C++; который, в свою очередь, является посредником для COM объекта:
он удерживает указатель
IDispatch на объект и переводит обращения Lua к вызовам Automation (автоматизации) и обращениям к свойствам. Вот пример использования LuaCOM объекта:
-- Instantiate a Microsoft(R) Calendar Object
-- Создаем экземпляр объекта Microsoft(R) Calendar
calendar = luacom.CreateObject("MSCAL.Calendar")
-- Error check (проверка ошибок)
if calendar == nil then
print("Error creating object")
exit(1)
end
-- Method call (вызов метода)
calendar:AboutBox()
-- Property Get (получение свойства)
current_day = calendar.Day
-- Property Put (размещение свойства)
calendar.Month = calendar.Month + 1
print(current_day)
print(calendar.Month)
Каждый раз, когда LuaCOM нужно преобразовать указатель
IDispatch в Lua, он создает LuaCOM объект. Существуют две ситуации, когда это происходит:
- при вызове функций API LuaCOM, которые возвращают COM объекты (CreateObject, GetObject, NewObject, Connect, и т.д.) и
- при получении возвращаемых значений из COM, где некоторые из них являются указателями IDispatch.
Вот пример таких ситуаций:
-- First, we get a luacom object using LuaCOM API
-- Вначале получаем объект luacom с помощью API LuaCOM
excel = luacom.CreateObject("Excel.Application")
assert(luacom.GetType(excel) == "LuaCOM")
-- now we get one from a method call
-- теперь получаем один из вызовов метода
sheets = excel.Sheets
assert(luacom.GetType(sheets) == "LuaCOM")
Объект LuaCOM может быть передан как параметр вызовов методов на других объектах LuaCOM, если эти методы предполагают аргумент типа . Вот пример иллюстрирующий такую ситуацию:
-- Gets a running instance of Excel
-- Получаем запущенный экземпляр Excel
excel = luacom.GetObject("Excel.Application")
-- Gets the set of worksheets (получаем набор рабочих листов)
sheets = excel.Worksheets
-- gets the first two sheets (получаем первые два листа)
sheet1 = sheets:Item(1)
sheet2 = sheets:Item(2)
-- Exchange them (here we pass the second sheet as a parameter to a method)
-- Заменяем их (здесь мы передаем второй лист как параметр метода)
sheet1:Move(nil, sheet2)
Существуют два вида LuaCOM объектов: типовые и универсальные. Типовые объекты - те, чей COM-объект имеет информацию о типе.
Универсальные - те, чей COM-объект не предоставляет никаких сведений о типе. В некоторых ситуациях эта разница является важной.
3.2.1 Удаление объекта
Объекты LuaCOM высвобождаются через механизм сбора мусора Lua, так как не существует никакого определенного метода API для их уничтожения.
Внимание LuaCOM только отслеживает ссылки на COM-объекты.
Он не работает с понятиями “приложение”, “компонент”, “процесс”, и т.д.
Он даже не знает какие объекты являются частью того же компонента или приложения. Это имеет некоторые последствия на утилизацию объекта:
- компонент можно считать “завершившим” свои отношения с LuaCOM только когда высвобождены все ссылки к его объектам, а не только к тем, что созданы с помощью CreateObject;
- у некоторых компонентов имеется метод “Quit”. С его помощью можно закрыть интерфейс компонента, но сам он может остаться в работе, если на него имеются хоть какие-то ссылки.
Тем не менее, эти ссылки не могут использоваться, с достаточной гарантией, после вызова метода “Quit”.
Для высвобождения компонента, он должен присвоить значение nil всем ссылкам на компонент (и его вложенные объекты) и затем вызвать функцию collectgarbage - сборщик мусора.
3.3 Автоматизация привязки
Автоматизация привязки отвечает за преобразование табличных обращений к объекту LuaCOM в вызовы COM интерфейса. Кроме того, она также предоставляет механизм для реализации диспетчерских интерфейсов (диспинтерфейсов), использующих обычные Lua таблицы.
3.3.1 Реализация диспинтерфейсов в Lua
Автоматизация привязки имеет класс C++ по имени
tLuaDispatch, который реализует универсальный интерфейс
IDispatch.
Реализация данного класса переводит вызовы метода и обращения к свойствам, сделанные к объектам этого класса, в вызовы Lua и обращения к таблицам.
Таким образом, можно реализовать диспинтерфейс полностью в Lua, при условии, что он имеет библиотеку типов с его описанием.
Эта библиотека типов может быть автономной (с привязкой по её расположению в файловой системе) или может быть связанной с некоторым зарегистрированным компонентом.
В этом случае она может быть связана
ProgID компонента.
Объекты C++ этого класса можно использовать в любом месте, где предполагается интерфейс
IDispatch или
IUnknown.
LuaCOM берет на себя эти преобразования. Вот ниже показан пример реализации диспинтерфейса в Lua.
-- Creates and fills the Lua table
-- that will implement the COM interface
-- Создаем и заполняем таблицу Lua,
-- что будет реализовывать COM интерфейс
events_table = {}
function events_table:AfterUpdate()
print("AfterUpdate called!")
end
-- Here we implement the interface DCalendarEvents, which is part
-- of the Microsoft(R) Calendar object, whose ProgID is MSCAL.Calendar
-- Здесь мы реализуем интерфейс DCalendarEvents, который является частью
-- объекта Calendar Microsoft(R), чей ProgID - MSCAL.Calendar
events_obj = luacom.ImplInterface(
events_table,
"MSCAL.Calendar",
"DCalendarEvents")
-- Checks for errors (проверяем ошибки)
--
if events_obj == nil then
print("Implementation failed")
exit(1)
end
-- Tests the interface: this must generate a call
-- to the events:AfterUpdate defined above
-- Проверяем интерфейс: это должно привести
-- к вызову events:AfterUpdate заданному выше
--
events_obj:AfterUpdate()
Если интерфейс для реализации описан в автономной библиотеке типов, взамен должен использоваться метод
ImplInterfaceFromTypelib:
-- Creates and fills the Lua table that will
-- implement the Automation interface
-- Создаем и заполняем таблицу Lua, что будет
-- реализовывать интерфейс автоматизации
hello_table = {}
function hello:Hello()
print("Hello World!")
end
-- Here we implement the interface IHello
-- Здесь реализуем интерфейс IHello
--
hello_obj = luacom.ImplInterfaceFromTypelib(hello_table, "hello.tlb", "IHello")
-- Checks for errors (проверка на ошибки)
--
if hello_obj == nil then
print("Implementation failed")
os.exit(1)
end
-- Tests the interface (тестируем интерфейс)
--
hello_obj:Hello()
Оба метода возвращают объект LuaCOM, соответствующий интерфейс
IDispatch которого реализуется по указанной таблице.
Этот объект LuaCOM можно передать в качестве аргумента COM-методам, которые рассчитаны на диспинтерфейс или методы API LuaCOM (подобные
addConnection).
Также можно использовать метод
NewObject, который лучше подходит к ситуации, где нужно создать полный компонент в Lua и требуется экспортировать его так, чтобы он был доступен через COM любому запущенному приложению.
3.3.2 Использование методов и свойств
Диспинтерфейсы имеют два “типа” составляющих: свойства и методы. LuaCOM имеет дело с ними обоими.
Обращения к методам делаются таким же образом, что и вызовы функций Lua, сохраняемых в таблице и имеющих “собственный” параметр:
obj = luacom.CreateObject("TEST.Test")
if obj == nil then
exit(1)
end
-- method call (вызов метода)
a = obj:Teste(1,2)
-- another one (другой вызов)
obj:Teste2(a+1)
Важно заметить необходимость использования двоеточия – “:” – для вызовов методов.
Хотя LuaCOM не использует собственный параметр, что передает Lua в этом случае, его наличие предполагается — то есть,
LuaCOM всегда пропускает первый параметр в случае вызова метода; пренебрежение им может преподнести неприятные ошибки.
Заметьте, что это правило не применяется когда используется метод по умолчанию LuaCOM-объекта, хранящегося в таблице или в свойстве другого LuaCOM-объекта (смотрите подраздел
Методы по умолчанию ниже).
Обращение к свойствам очень похоже на подобные обращения к полям Lua-таблиц:
obj = luacom.CreateObject("TEST.Test")
if obj == nil then
exit(1)
end
-- property access (обращение к свойству)
a = obj.TestData
-- property setting (установка свойства)
obj.TestData = a + 1
К свойствам можно обращаться также как к методам. Это является обязательным при работе с параметризованными свойствами — то есть теми, что принимают (или требуют) параметров. Типичным примером такой ситуации является свойство коллекции “Item”.
-- property access (обращение к свойству)
a = obj:TestData()
-- Parametrized property access
-- Обращение к параметризованному свойству
b = obj:TestInfo(2)
-- Accessing collections (обращение к коллекции)
c = obj.Files:Item(2)
Обратите внимание, что в этой ситуации также нужно использовать двоеточие – “:”.
При обращении к свойству с вызовами метода, LuaCOM всегда переводит вызов метода к считыванию обращения (получению свойства).
Для установки значения свойства, с помощью вызова метода, необходимо добавлять к имени свойства и новое значение должно быть предоставлено в качестве последнего аргумента.
-- property access (обращаемся к свойству)
a = obj:TestData()
-- Setting the property (устанавливаем свойство)
b = obj:setTestInfo(2)
-- Setting a parametrized property
-- Установка параметризованного свойства
c = obj.Files:setItem(2, "test.txt")
Для уточнения кода, также можно использовать префикс “get”, хотя он и необязателен, так как поведением по умолчанию является считывание обращения.
-- property access (обращаемся к свойству)
a = obj:getTestData()
b = obj:getTestInfo(2)
c = obj.Files:getItem(2)
Расширяемые интерфейсы
LuaCOM позволяет использовать свойства как простые поля Lua таблицы только для объектов, которые имеют сведения о типе.
Тем не менее, некоторые объекты, которые имеют типовую информацию, описывающую их интерфейсы, реализуют свойства не описанные в библиотеке типов: такие объекты реализуют расширяемые интерфейсы.
Эти свойства могут быть использованы только с функциями доступа, как показано в
главе 5.4.4.
Пример подобного поведения обнаруживается в объектах .
Методы по умолчанию
Диспинтерфейс может иметь метод или свойство по умолчанию - то есть такой, который вызывается, когда клиент не указывает имя метода.
LuaCOM вызывает метод по умолчанию, когда сам объект используется в качестве функции.
excel = luacom.CreateObject("Excel.Application")
excel.Visible = true
excel.Workbooks:Add()
-- Here we call the default method
-- notice we DID NOT use the colon, as
-- the object used is Sheets, not excel
-- Здесь вызывается метод по умолчанию
-- заметьте, двоеточие НЕ используется,
-- так как объект использует Sheets, а не excel
sheet = excel.Sheets(1)
print(sheet.Name)
-- Here we also call the default method
-- Здесь также вызывается метод по умолчанию
sheets = excel.Sheets
sheet2 = sheets(2)
print(sheet2.Name)
-- Setting values (установка значений)
excel.Sheets(1).Name = "MySheet1"
excel:Quit()
Это может быть полезно при работе с коллекциями, как правило, они по умолчанию имеют свойство
Item.
ПРЕДУПРЕЖДЕНИЕ: будьте осторожны, не ставьте двоеточие при использовании методов по умолчанию для LuaCOM-объектов, содержащихся в таблице или в других объектах LuaCOM (смотрите пример выше).
Универсальные объекты LuaCOM
Для считывания или записи свойств в универсальные объекты LuaCOM, необходимо обращение к ним в виде вызовов метода с правильным префиксом (
get/set). Простая семантика обращения к полям таблицы здесь не работает.
obj_typ = luacom.CreateObject("Some.TypedObject")
obj_untyp = luacom.CreateObject("Untyped.Object")
-- property read (get) считывание свойства
a = obj_typ.Value
b = obj_untyp:getValue()
-- property write (set) запись свойства
obj.typ = a + 1
obj_untyp:setValue(b + 1)
Обращение к свойству в Lua
При реализации COM интерфейса в Lua, LuaCOM также поддерживает понятия свойств и индексацию свойств. LuaCOM переводит считывание и запись свойства в обращения к полям таблицы:
interface = {}
interface.Test = 1
interface.TestIndex = {2,3}
obj = luacom.ImplInterface(interface, "TEST.Test", "ITest")
-- must print "1" (должна быть выведена "1")
print(obj.Test)
-- must print nil (if there is no member named Test2)
-- должно быть выведено nil (если нет элемента по имени Test2)
print(obj.Test2)
-- this writes the field Test (делаем запись в поле Test)
obj.Test = 1
-- Indexed property read. Must return 3 (remember that
-- indexed tables start at 1 in Lua)
-- Считывание индексированного свойства. Должна возвратиться 3
-- (помните, что индексация таблиц в Lua начинается с 1)
i = obj:TestIndex(2)
-- Sets the indexed field (установка индексированного поля)
obj:setTestIndex(2,4)
-- Now must return 4 (теперь должна возвратиться 4)
i = obj:TestIndex(2)
3.3.3 Точки подключения: обработка событий
Точки подключения являются частью стандартного механизма ActiveX, основной задачей которой, является предоставление объекту ActiveX возможности оповещения своего владельца (основную программу, что содержит объект ActiveX) о событиях любого рода.
Точка подключения работает как "приемник событий", через который проходят события и уведомления.
Чтобы установить соединение с использованием LuaCOM, владелец объекта ActiveX должен создать таблицу для реализации интерфейса подключения, описание которого предоставляется объектом ActiveX
(такой интерфейс называется
исходным интерфейсом) и затем вызвать метод API
Connect, передавая в качестве аргументов объекта LuaCOM объект ActiveX и таблицу реализации.
Выполнив это, LuaCOM автоматически найдет исходный интерфейс по умолчанию, создаст объект LuaCOM, реализованный по предоставленной таблице и затем подключит этот объект к объекту ActiveX. Посмотрите следующий пример:
-- Creates the COM object (создаем COM объект)
--
calendar = luacom.CreateObject("MSCAL.Calendar")
if calendar == nil then
os.exit(1)
end
-- Creates implementation table (создаем таблицу реализации)
--
calendar_events = {}
function calendar_events:AfterUpdate()
print("Calendar updated!")
end
-- Connects object and table (подключаем объект и таблицу)
--
res, cookie = luacom.Connect(calendar, calendar_events)
if res == nil then
exit(1)
end
-- This should trigger the AfterUpdate event
-- Это будет срабатывать по событию AfterUpdate
calendar:NextMonth()
, возвращаемый
Connect, идентифицирует это соединение и впоследствии может использоваться для высвобождения подключения. A COM объект может иметь несколько приемников событий подключенных к нему одновременно.
Также возможно создание отдельного LuaCOM объекта, реализующего точку подключения исходного интерфейса и затем подключение его к объекту при помощи
addConnection.
-- Instances the COM object (создаем экземпляр объекта COM)
--
calendar = luacom.CreateObject("MSCAL.Calendar")
if calendar == nil then
print("Error instantiating calendar")
os.exit(1)
end
-- Creates implementation table (создаем таблицу реализации)
--
calendar_events = {}
function calendar_events:AfterUpdate()
print("Calendar updated!")
end
-- Creates LuaCOM object implemented by calendar_events
-- Создаем объект LuaCOM реализованный по calendar_events
event_handler = luacom.ImplInterface(calendar_events,
"MSCAL.Calendar",
"DCalendarEvents")
if event_handler == nil then
print("Error implementing DCalendarEvents")
exit(1)
end
-- Connects both objects (подключаем оба объекта)
--
cookie = luacom.addConnection(calendar, event_handler)
-- This should trigger the AfterUpdate event
-- Это будет срабатывать по событию AfterUpdate
calendar:NextMonth()
-- This disconnects the connection point established
-- Это отключает установленную точку подключения
luacom.releaseConnection(calendar, event_handler, cookie)
-- This should NOT trigger the AfterUpdate event
-- Это НЕ будет срабатывать по событию AfterUpdate
calendar:NextMonth()
Обратите внимание, что
addConnection также возвращает cookie. Для вызова
releaseConnection чтобы разъединить подключение нужны как приемник событий, так и cookie.
Старый синтаксис (до версии 1.3)
releaseConnection (не принимает во внимание приемник событий и cookie) по-прежнему работает, но только отключает последнее сделанное соединение (но утечки не будет; все соединения отключаются когда объект попадает к сборщику мусора).
Цикл обработки сообщений Для получения событий необходимо иметь цикл обработки сообщений в потоке, которому принадлежит объект, принимающий события.
Все события отправляются через очередь сообщений Windows, созданной во время инициализации COM.
Без цикла обработки сообщений, объекты событий, реализованные LuaCOM, никогда не будут принимать вызовы метода из COM-объектов, где они зарегистрированы.
Внепроцессным COM серверам, реализованным с LuaCOM. также нужен цикл обработки сообщений, чтобы иметь возможность обслуживать вызовы методов (которые предоставляются вызовом
luacom.DetectAutomation).
3.3.4 Передача параметров
LuaCOM имеет некоторые правила относительно передачи параметров. Они определяют как LuaCOM будет конвертировать списки COM параметров в Lua и обратно.
Есть две различных ситуации для которых применяются эти правила: вызов метода COM-объекта из Lua и вызов Lua-функции из COM.
Основной вопрос здесь в том, как обрабатывать различные типы параметров, поддерживаемых COM (“in” входные параметры, “out” выходные параметры, “in-out” параметры вход-выход, “optional” - дополнительные параметры и “defaultvalue” - параметры по умолчанию).
Также имеются и специальные правила относительно универсальных LuaCOM объектов.
Вызов COM из Lua
Данная ситуация происходит когда обращаются к свойству или вызывается метод объекта COM через LuaCOM объект. Вот следующий пример:
word = luacom.GetObject("Word.Application")
-- Here we are calling the "Move" method of the Application object of
-- a running instance of Microsoft(R) Word(R)
-- Здесь мы вызываем метод "Move" объекта Application
-- запущенного экземпляра Microsoft(R) Word(R)
word:Move(100,100)
В этом состоянии в процессе передачи параметров имеются два шага:
- конвертирование параметров Lua в COM (эта ситуация будет называться “lua2com”);
- конвертирование возвращаемого значения COM и вывод значений обратно в Lua (эта ситуация будет называться “com2lua”).
ситуация lua2com Конвертирование выполняется на основе типовых сведений метода (или свойства); это делается в соответствии с тем порядком, в котором параметры располагаются в типовых сведениях метода.
Параметры Lua используются в том же порядке. Для каждого параметра имеются три возможности:
Параметр является “in” параметром LuaCOM получает первый параметр Lua еще не преобразованным и конвертирует его в COM используя механизм преобразования типа LuaCOM.
Параметр является “out” параметром LuaCOM игнорирует этот параметр, так как он будте заполнен только вызванным методом. То есть, параметры “out” НЕ ДОЛЖНЫ появляться в списке параметров Lua.
Параметр является “in-out” параметром LuaCOM делает тоже самое, что и для параметров “in”.
Когда сторона, вызывающая метод, хочет пропустить (не включать) параметр, она должна передать значение
nil; тогда LuaCOM поступит соответственно, сообщив вызываемому методу о пропущенном параметре.
Если параметр имеет значение по умолчанию, то взамен используется оно. Обратите внимание, что LuaCOM не выражает недовольства когда пропускаются обязательные параметры. На самом деле LuaCOM игнорирует тот факт, что параметр является обязательным или дополнительным.
Тем самым он оставляет ответственность за проверку этого на реализацию вызываемого метода.
ситуация com2lua Когда вызванный метод завершает работу, LuaCOM преобразует возвращаемое значение и выходные значения (то есть, значения параметров “out” и “in-out”) в возвращаемые значения Lua.
То есть, возвращаемым значением метода является код Lua, возвращенный как первое возвращаемое значение; выходные значения возвращаются в порядке их отображения в списке параметров
(обратите внимание, что здесь используется функция Lua с несколькими возвращаемыми значениями).
Если метод не имеет возвращаемых значений, то есть, является методом, возвращаемыми значениями будут выходные значения.
Если нет и выходных значений, то возвращаемых значений вообще не будет.
Вызванный метод может опустить (не включать) возвращаемое значение или выходные значения; LuaCOM для каждого пропущенного значения возвратит значение
nil.
Для иллюстрации этих понятий, далее следует пример таких ситуаций. Вначале показан отрывок из , с описанием метода COM-объекта:
HRESULT TestShort(
[in] short p1, // an "in" parameter (параметр "in")
[out] short* p2, // an "out" parameter (параметр "out")
[in,out] short* p3, // an "in-out" parameter (параметр "in-out")
[out,retval] short* retval); // the return value (возвращаемое значение)
Далее следует пример того, что происходит при вызове метода:
-- assume that "com" is a LuaCOM object
-- Here we set p1 = 1, p3 = 2 and leave p2 uninitialized
-- When the method returns, r1 = retval and r2 = p2 and r3 = p3
-- Здесь мы устанавливаем p1 = 1, p3 = 2 и оставляем p2 неинициализированным
-- При возврате метода, r1 = возвращаемое значение, r2 = p2 и r3 = p3
r1, r2, r3 = com:TestShort(1,2)
-- Below is WRONG! The are only two in/in-out parameters! Out parameters
-- are ignored in the lua2com parameter translation
-- Ниже НЕВЕРНО! Только два параметра in/in-out! Выходные параметры
-- игнорируются в переводе параметра lua2com
r1, r2, r3 = com:TestShort(1,2,3) -- WRONG! (НЕВЕРНО!)
-- Here p1 = 1, p2 is uninitialized and p3 is omitted.
-- Здесь p1 = 1, p2 не инициализирован, а p3 опущен.
r1, r2, r3 = com:TestShort(1)
-- Here we ignore the output value p3
-- Здесь мы игнорируем выходное значение p3
r1,r2 = com:TestShort(1)
-- Here we ignore all output values (including the return value)
-- Здесь мы игнорируем все выходные значения (включая возвращаемое значение)
com:TestShort(1,2)
Универсальные LuaCOM объекты При работе с универсальными объектами LuaCOM, привязка применяет другие правила: все Lua параметры преобразуются в COM как параметры “in-out”.
LuaCOM полагает что эти методы всегда возвращают значение; если вызванный метод ничего не возвращает, LuaCOM продвигает .
Поскольку все параметры установлены как “in-out”, все они будут возвращены назад в Lua, изменены ли они или нет вызванным методом.
Вызов Lua из COM
Эта ситуация случается, когда кто-нибудь реализует COM диспинтерфейс в Lua. Привязка ActiveX должна переводить вызовы методов COM в вызовы функций Lua.
Правила здесь, относительно преобразования списка параметров, теже самые, что показаны выше, только замените “Lua” на “COM” и наоборот.
То есть, все COM параметры “in” и “in-out” преобразуются в параметры вызова функции Lua (выходные параметры игнорируются).
По окончании вызова, первое возвращаемое значение конвертируется как возвращаемое значение COM метода,
а другие возвращаемые значения переводятся как значения “in-out” и “out”, следуя порядку, в котором они отображаются в типовых сведениях метода.
Продолжая предыдущий пример, здесь показана реализация метода, вызываемого из COM:
implementation = {}
-- This method receives TWO in/in-out parameters
-- Этот метод принимает ДВА параметра in/in-out
function implementation:TestShort(p1, p2)
-- the first one is the retval, the second the first out param
-- the third the second out param (in fact, an in-out param)
-- первый параметр является возвращаемым значением
-- второй - первым параметром out
-- третий - вторым параметром out (фактически, параметр in-out)
return p1+p2, p1-p2, p1*p2
end
-- Implements an interface (реализуем интерфейс)
obj = luacom.ImplInterface(implementation, "TEST.Test", ITest)
-- calls the function implementation:TestShort via COM
-- вызываем функцию implementation:TestShort через COM
r1, r2, r3 = obj:TestShort(1,2)
3.3.5 Обработка исключений
Когда во время работы, при использованиии методов или объектов LuaCOM, происходят ошибки, имеются два действия которыми может отреагировать LuaCOM:
- передать сигнал об ошибке с помощью lua_error;
- игнорировать ошибку, просто ничего не делать или возвратить какой-либо тип значения ошибки.
Ошибки во время выполнения разделяются на три типа:
- ошибки внутри вызовов API, подобных CreateObject;
- ошибки при использовании объектов LuaCOM (вызовы методов COM);
- ошибки внутри COM-объектов, реализованных в Lua.
Третий тип ошибки всегда преобразуется в исключение COM. возвращаемое на сервер.
Для облегчения отладки, эти ошибки также регистрируются в журнале (если возможность ведения журнала была активирована), так как сервер может молча проигнорировать эти исключения, особенно в событиях.
Если библиотека LuaCOM собрана с параметром , тогда масса информационных сообщений заносится в журнал и все ошибки отображаются в диалоговом окне.
Это помогает отлаживать ошибки внутри событий "на лету", поскольку такие ошибки, как правило, игнорируются сервером. Обратите внимание, что при таком выборе замедляется работа LuaCOM и могут создаваться очень большие файлы журнала.
Поведение LuaCOM для двух других типов можно настраивать. Внутри таблицы LuaCOM имеется таблица по имени
config. Эта таблица содержит три поля, связанные с обработкой ошибок:
abort_on_API_error (прервать на ошибке API) - если установлено значение
false, LuaCOM не реагирует на ошибки внутри вызовов API.
Это НЕ относится к ошибкам, вызванным неверными параметрами: такие ошибки всегда создают вызовы
lua_error.
Значением по умолчанию для этого поля является
false.
abort_on_error (прервать на ошибке) - если установлено значение
false, ошибки внутри вызовов метода и обращений к свойствам также игнорируются, возможно возвращение значения
nil там, где предполагается возвращаемое значение.
Значением по умолчанию для этого поля является
true.
last error (последняя ошибка) - каждый раз, когда происходит ошибка во время выполнения, LuaCOM выдает это поле с текстом описания ошибки.
Это поле можно использовать для проверки, не произошла ли неудача при выполнении некоей операции; просто не забывайте устанавливать его как
nil перед операцией, представляющей интерес.
Пример
-- to make all LuaCOM errors runtime errors
-- чтобы сделать все ошибки LuaCOM ошибками во время выполнения
luacom.config.abort_on_error = true
luacom.config.abort_on_API_error = true
-- to silently ignore all errors
-- для "тихого" игнорирования всех ошибок
luacom.config.abort_on_error = false
luacom.config.abort_on_API_error = false
-- catching an ignored error
-- перехват игнорируемой ошибки
luacom.config.last_error = nil
obj:RunMethod(x,y)
if luacom.config.last_error then
print("Error!")
exit(1)
end
Все ошибки также регистрируются в журнале. Обратите внимание, что некоторые, записанные в журнал, исключения на самом деле вовсе не ошибки: это побочные эффекты широкого использования обработки исключений внутри LuaCOM кода.
3.4 Преобразование типов
LuaCOM отвечает за преобразование значений из COM в Lua и наоборот. Большинство типов могут быть без проблем конвертированы из COM в Lua и обратно.
Но есть некоторые типы, для которых преобразование не совсем очевидно. В таких случаях LuaCOM использует некоторые предопределенные правила для преобразования типов.
Эти правила должны быть известны, чтобы избежать неверное истолкование результатов конвертации и не допустить ошибок.
3.4.1 Логические (boolean) значения
LuaCOM использует логические значения
true и
false и не работает с соглашением старше Lua 4 (
nil и
non-nil).
3.4.2 Указатели на IDispatch и LuaCOM объекты
Указатель на
IDispatch преобразуется в LuaCOM объект, реализация которого предоставлена данным указателем.
Если объект реализован локальной Lua таблицей, то указатель конвертируется в эту таблицу.
Объект LuaCOM преобразуется в COM простой передачей его интерфейса реализации в COM.
3.4.3 Указатели на IUnknown
LuaCOM только позволяет передавать и принимать указатели
IUnknown; он не работает с ними. Они преобразуются из пользовательских данных (userdata) в указанную метатаблицу и обратно.
3.4.4 Массивы и таблицы
Если таблица не имеет метаметода
__tocom, то вначале LuaCOM проверяет не является ли таблица описанием типа данных .
Таблица является описанием типа данных variant, если она имеет поле
Type. Это поле должно иметь строку, которая описывает как следует конвертировать поле
Value таблицы.
Возможными значениями для поля
Type являются
string,
bool,
error,
null,
currency,
decimal,
double,
float,
int8,
uint8,
int4,
uint4,
int2,
uint2,
int1,
uint1,
int, и
uint. Каждое значение соответствует типу данных в variant.
Если таблица не описание типа данных variant, то она может быть описанием даты. Таблица является описанием даты если имеет одно из следующих полей:
Day (день),
DayOfWeek (день недели),
Month (месяц),
Year (год),
Hour (час),
Minute (минута),
Second (секунда),
Milliseconds (миллисекунда).
LuaCOM инициализирует таблицу описания даты с предоставленными полями; другие - сохраняют свои значения по умолчанию.
Если таблица не является описанием даты, LuaCOM конвертирует таблицы Lua в и обратно.
Чтобы быть преобразованными, таблицы Lua должны быть “массивоподобными”, то есть, все её элементы должны быть или “скалярами” или таблицами с одинаковой длиной.
Эти таблицы также должны быть “массивоподобными”. Вот некоторые примеры как делается такое преобразование:
Таблица Lua | Safe Array |
table = {"name", "phone"} | [”name” ”phone”] |
table = {{1,2},{4,9}} |
|
Если у таблицы есть метаметод конвертирования, LuaCOM использует его как руководство для проведения преобразования. Если метаметодом является метод, LuaCOM вызывает его, передавая таблицу и COM тип.
Метод должен возвратить COM-объект, который будет передан LuaCOM.
Если метаметодом является таблица, LuaCOM разыскивает поля
typelib,
interface и
coclass, и передает их в качестве аргументов в вызов API
ImplInterfaceFromTypelib.
Если таблица не имеет поля
typelib, LuaCOM разыскивает поля
progid и
interface, и передает их в API вызов
ImplInterface. В любом случае, LuaCOM передаст возвращенный объект в COM.
3.4.5 Тип CURRENCY
Значения
CURRENCY конвертируются в Lua как числа. При конвертировании значения в COM, где предполагается
CURRENCY, LuaCOM допускает как числа, так и строки, отформатированные с помощью текущей локали для значений валюты.
Обратите внимание, что все это сильно зависит от конфигурации и LuaCOM только использует функции конвертирования VARIANT.
3.4.6 Тип DATE
При конвертировании из COM в Lua, поведением по умолчанию является преобразование значений
DATE в строки, форматированные в соответствии с текущей локалью (текущим языковым стандартом).
Верно и обратное: LuaCOM конвертирует строки, форматированные согласно текущей локали в значения
DATE.
Скрипт может изменить преобразование из строк в таблицы, установкой в поле
DateFormat таблицы
luacom (пространство имен LuaCOM) строкового значения "
table".
Таблица будет иметь поля
Day,
DayOfWeek,
Month,
Year,
Hour,
Minute,
Second и
Milliseconds.
Чтобы вернуть преобразование в строки, установите поле
DateFormat как "
string". Будьте осторожны с этой функцией, так как она может нарушить совместимость с другими скриптами.
3.4.7 Тип variant
При конвертировании из COM в Lua, поведением по умолчанию является преобразование различных значений в ближайший Lua-тип.
Скрипт может изменить преобразование из Lua-типов в таблицу, описывающую variant, установкой поля
TableVariants таблицы
luacom (пространство имен LuaCOM) как
true.
Таблицы будут иметь поле
Type, сообщающее исходный тип variant, и поле
Value, содержащее преобразование к ближайшему Lua-типу.
Будьте осторожны с этой функцией, так как она может нарушить совместимость с другими скриптами.
3.4.8 Обработка ошибок
Когда LuaCOM не может конвертировать значение из COM или в COM, он выдает исключение, которое может быть преобразовано в
lua_error или в COM исключение, в зависимости от того, кто из них был вызван.
3.5 Другие объекты
Помимо COM автоматизации LuaCOM работает и с другими объектами. Вот их краткое описание.
3.5.1 Объект перечислитель (enumerator)
Этот объект является посредником для COM-объекта реализующего
IEnumVARIANT интерфейс.
Он переводит вызовы, сделанные к полям таблицы в вызовы метода, использующего данный интерфейс. Перечислители часто появляются при работе с коллекциями.
Для получения перечислителя для коллекции используется метод API Lua
GetEnumerator. Пример:
--
-- Sample use of enumerators
-- Пример использования перечислителя
--
-- Gets an instance (получаем экземпляр)
word = luacom.GetObject("Word.Application")
-- Gets an enumerator for the Documents collection
-- Получаем перечислитель для коллекции документов
docs_enum = luacom.GetEnumerator(word.Documents)
-- Prints the names of all open documents
-- Выводим в печать имена всех открытых документов
doc = docs_enum:Next()
while doc do
print(doc.Name)
doc = docs_enum:Next()
end
Метод
pairs расширенного API Lua позволяет обход перечисления при помощи оператора цикла
for Lua. Пример, показанный выше, может быть переписан так:
--
-- Sample use of enumerators
-- Пример использования перечислителя
--
-- Gets an instance (получаем экземпляр)
word = luacom.GetObject("Word.Application")
-- Prints the names of all open documents
-- Выводим в печать имена всех открытых документов
for index, doc in luacom.pairs(word.Documents) do
print(doc.Name)
end
3.5.2 Объект-контейнер точки подключения
Данный объект позволяет COM-объекту, реализованному с помощью LuaCOM, отправлять события своему клиенту. Он используется, в первую очередь, для реализации COM-объекта в Lua, так что смотрите
главу 4 для дополнительной информации.
3.5.3 Объекты Typelib и Typeinfo
Эти объекты позволяют переходить через описания типов или библиотеку типов LuaCOM объекта.
Они являются посредниками для интерфейсов
ITypeLib и
ITypeInfo, хотя доступны не все методы. Для получения дополнительной информации смотрите главы
6.5 Объект Type Library и
6.6 Объект Type Information.
LuaCOM был разработан Renato Cerqueira, Vinicius Almendra и Fabio Mascarenhas.
Проект финансируется компанией TeCGraf
(Technology Group on Computer Graphics).