Vinicius Almendra
Renato Cerqueira
Currently, the LuaCOM library supports the following features:
CreateObject
method;
GetObject
;
This manual does not provide information for developers who need deeper technical information about LuaCOM or who are willing to modify it for some reason. For this kind of information, please contact the authors.
LuaCOM is an add-on to the Lua language. To be used, either the binary library of LuaCOM must be linked with the host program, just like the Lua library and other add-ons, or you should load a LuaCOMdynamic library through Lua 5's require/loadlib mechanism. To use dynamic loading in Lua 4 you should implement a similar mechanism. There are different versions of the LuaCOM binary for the different versions of the Lua library, so pay attention to link the right one.
If you are linking LuaCOMto your program, the next step is to modify
the source code of the host program to
call LuaCOM's and COM initialization and termination
functions, which are part of the C/C++ API. To do so, include the
LuaCOM's header -- luacom.h
-- and call these functions
in the proper order: LuaCOM must be initialize after COM and after
Lua; it must be terminated before Lua; COM must be terminated AFTER
Lua2.1. Here is an example of a simple C host program
program using LuaCOM.
/* * Sample C program using luacom */ #include <stdio.h> #include <ole2.h> // needed for CoInitialize and CoUninitialize #include <lua.h> #include "luacom.h" int main (int argc, char *argv[]) { /* COM initialization */ CoInitialize(NULL); /* library initialization */ lua_State *L = lua_open(); luacom_open(L); if(lua_dofile("luacom_sample.lua") != 0) { puts("Error running sample!"); exit(1); } luacom_close(L); lua_close(L); CoUninitialize(NULL); return 0; }Notice that it's necessary to initialize COM before
lua_open
and to terminate it only after the last
lua_close
, otherwise fatal errors may occur.
Using Lua 5 to dynamically load LuaCOM is simpler. Just call require("luacom")
in your Lua script, and make sure the file luacom.lua
is in your LUA_PATH
environment variable, and the Lua and LuaCOM DLLs (lua-5.0.dll
, lualib-5.0.dll
and
luacom-lua5-1.2.dll
, respectively) are in your PATH
.
Then run your script with the Lua standalone interpreter.
The first step to use a COM object is to find it. COM objects are registered in the system registry and are associated with an unique Class Identifier, known as CLSID. A CLSID may also be associated with a string known as Programmatic Identifier or ProgID. This last one is the easiest way to reference a COM object. E.g., the ProgID for Microsoft© Word© is ``Word.Application''.
If one do not know in advance what is the CLSID or the ProgID of the object of interest, them it's possible to use tools like OleView to find the object, although the best place to find it is in the object's documentation.
With the ProgID or the CLSID of an object, it's now possible to create a new instance of it or to get a running instance. To do so, the easiest way is to use the method CreateObject of the Lua API:
word = luacom.CreateObject("Word.Application") assert(word) word.Visible = true
If there is an already running instance of the object you want, GetObject must be used to use it. The following code illustrates this:
-- If there is an instance of Word(r) running, -- it will end it word = luacom.GetObject("Word.Application") if word then word:Quit() word = nil end
To use a COM object, the first thing one must know is its interface, that is, its set of methods and properties. This information normally is available in the documentation of the object, but sometimes one do not have access to this documentation. LuaCOM can offer some help if the object has type information. If the object has an associated help file, LuaCOM can launch it using the method ShowHelp:
word = luacom.CreateObject("Word.Application") assert(word) luacom.ShowHelp(word)
If the object has an associated type library, LuaCOM can generate and display an HTML file describing it. This information can also be read using other type library browsers, as OleView.
The method DumpTypeInfo can be used in console applications to list the methods and properties of the interface. It does not give much information, but can be helpful when playing with an object.
After creating an object, the next step is to use it. This is primarily done through method calls and property accesses. To call a method of the object, do it as if the method was a function stored in a Lua table whose key is the method name:
-- Here we call the method 'Show' of the COM object myobj:Show() -- A method with a return value result = myobj:CheckState() -- A method with parameters file = myobj:LoadFile("test.xyz", 1) -- A method with output values x, y = myobj:UpdatePosition(x, y)
To read or write simple properties, one must simply use them as if they were normal table fields.
-- Reading properties value1 = obj1.Value value1 = obj2.Value -- writing a property obj3.Value = value1 + value2
Automation includes support to parametrized properties. These can be accessed (or written) using accessor functions.
value = obj:getMatrixValue(1,1) value = value*0,125 obj:setMatrixValue(1, 1, value)
obj = luacom.CreateObject("MyApp.MyObj") -- Here we force an immediate release of the object obj = nil collectgarbage()
Notice that if there is any references to the COM object alive in Lua then the application (or library) that implements it will not exit.
LuaCOM is composed by the following elements:
The LuaCOM API is divided in two parts: the standard API and the extended API. The standard API comprises the core functionality needed to use COM objects. The extended API includes more advanced features to Lua API that simplify the development of applications using LuaCOM. This distinction has been made due to the possible unbounded growth of features, which could end up cluttering the library and making it bigger and bigger and more and more difficult to use. For now, the extended API is entirely implemented in Lua 5 and can be easily removed without trouble.
The standard API is further divided in two classes: the Lua API and
the C/C++ API. The C/C++ API is used primarily for initialization of
the library and for low-level construction of LuaCOM objects. It
is declared in the header file luacomh. The Lua API
permits Lua programs to access all the functionality of LuaCOM. It
is implemented as a set of functions inside a global table named
luacom
; hereafter these functions will be called LuaCOM
methods. This table is created and populated when the C/C++ API function
luacom_open is called. Notice that the extended API lies in
a different table, called luacomE
. Below there is summary of
the LuaCOM API. Detailed information on these methods is available
in chapter 6.
Standard Lua API | |||
|
|
||
CreateObject |
Creates a LuaCOM object.
|
||
NewObject |
Creates a LuaCOM object implemented in Lua.
|
||
GetObject |
Creates a LuaCOM object associated with an
instance of an already running COM object.
|
||
ExposeObject |
Exposes a LuaCOM object, so
that other applications can get a reference to it.
|
||
RevokeObject |
Undoes the operation of ExposeObject.
|
||
RegisterObject |
Fills in the registry entries necessary for exposing a
COM object.
|
||
Connect |
Creates a connection point between an object and a Lua table.
|
||
ImplInterface |
Implements an IDispatch
interface using a Lua table.
|
||
ImplInterfaceFromTypelib |
Implements an
IDispatch interface described in a Type Library using a Lua table.
|
||
addConnection |
Connects two LuaCOM objects.
|
||
releaseConnection |
Disconnects a LuaCOM object from its
connection point.
|
||
isMember |
Checks whether a name correspond to a method
or a property of an LuaCOM object.
|
||
ProgIDfromCLSID |
Gets the ProgID associated with a CLSID.
|
||
CLSIDfromProgID |
Gets the CLSID associated with a ProgID.
|
||
GetIUnknown |
Returns an IUnknown interface to a LuaCOM
object as a full userdata.
|
||
DumpTypeInfo |
Dumps to the console the type
information of the specified LuaCOM object. This method should
be used only for debugging purposes.
|
Standard Lua API (continued) | |||
|
|
||
GetCurrentDirectory |
Returns the current directory.
|
||
CreateLuaCOM |
Transforms an IUnknown full userdata
into a LuaCOM object.
|
||
ImportIUnknown |
Converts a light userdata (pointer)
to an IUnknown full userdata.
|
||
DetectAutomation |
Used to implement COM servers.
Looks in the command-line for /Register or /Automation and calls user-defined
functions to register or expose objects, entering a message loop in the latter case.
If there is no command-line then assume it is being run in-process, calls the expose
function and returns.
|
Extended Lua API | |||
|
|
||
CreateLocalObject |
Creates a LuaCOM object as an out-of-process server.
|
||
CreateInprocObject |
Creates a LuaCOM object as an in-process server.
|
||
ExportConstants |
Exports all the constants of a type
library (standalone or bound to a LuaCOM object) to the global
environment (or optionally to a table).
|
||
DumpTypeLib |
Creates an HTML file describing a type library.
|
||
GetType |
Returns a string describing the type of object, in the
case its an object belonging to the LuaCOM library.
|
||
ViewTypeLib |
Runs DumpTypeLib and shows the created file using
Internet Explorer©.
|
||
pairs |
Does the same as pairs for COM Enumerators.
|
||
FillTypeLib |
Creates a table describing a type
library.
|
||
FillTypeInfo |
Creates a table describing a type info.
|
Standard C/C++ API | |||
|
|
||
luacom_open |
Initializes the LuaCOM library in a
Lua state. It must be called before any use of LuaCOM
features.
|
||
luacom_close |
LuaCOM's termination function.
|
||
luacom_detectAutomation |
This function is a helper to create COM servers. It looks
in the command line for the switches ``/Automation'' and
``/Register'' and call some user-defined Lua functions accordingly.
|
||
luacom_IDispatch2LuaCOM |
Takes an IDispatch interface and creates a LuaCOM
object to expose it, pushing the object on the Lua stack.
|
-- Instantiate a Microsoft(R) Calendar Object 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)
Every time LuaCOM needs to convert an IDispatch pointer to Lua it creates a LuaCOM object. There are two situations where this happens:
Follows a sample of these situations:
-- First, we get a luacom object using LuaCOM API excel = luacom.CreateObject("Excel.Application") assert(luacomE.GetType(excel) == "LuaCOM") -- now we get one from a method call sheets = excel.Sheets assert(luacomE.GetType(sheets) == "LuaCOM")
A LuaCOM object may be passed as a parameter to method calls on other LuaCOM objects, if these methods expect an argument of type dispinterface. Here is a sample to illustrate this situation:
-- Gets a running instance of 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)
There are two kinds of LuaCOM objects: typed and generic ones. The typed ones are those whose COM object has type information. The generic ones are those whose COM object does not supply any type information. This distinction is important in some situations.
LuaCOM objects are released through Lua's garbage collection mechanism, so there isn't any explicit API method to destroy them.
The Automation binding is responsible for translating the table accesses to the LuaCOM object into COM interface calls. Besides that, it also provides a mechanism for implementing dispinterfaces using ordinary Lua tables.
The Automation binding has a C++ class called tLuaDispatch that implements a generic IDispatch interface. The implementation of this class translates the method calls and property accesses done on the objects of this class to Lua calls and table accesses. So, one may implement a dispinterface entirely in Lua, provided it has a type library describing it. This type library may be a stand-alone one (referenced by its location on the file system) or may be associated with some registered component. In this case, it may be referenced by the ProgID of the component.
The C++ objects of this class can be used in any place where an IDispatch or IUnknown interface is expected. LuaCOM takes care of these conversion. Follows a sample implementation of a dispinterface in Lua.
-- Creates and fills the Lua table that will implement the -- COM interface 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 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_obj:AfterUpdate()
If the interface to be implemented is described in a stand-alone
type library, the method ImplInterfaceFromTypelib
must be used instead:
-- Creates and fills the Lua table that will implement the -- Automation interface hello_table = {} function hello:Hello() print("Hello World!") end -- Here we implement the interface IHello -- hello_obj = luacom.ImplInterfaceFromTypelib("hello.tlb","IHello") -- Checks for errors -- if hello_obj == nil then print("Implementation failed") os.exit(1) end -- Tests the interface -- hello_obj:Hello()
Both methods return a LuaCOM object, whose corresponding
IDispatch interface is implemented by the supplied table. This
LuaCOM object can be passed as an argument to COM methods who
expect a dispinterface or to LuaCOM API methods (like
addConnection
).
One can also use the NewObject
method, which is best suited
to the situation where one needs to create a complete component in
Lua and wants to export it, so that it can be accessed through COM
by any running application.
The dispinterfaces have two ``types'' of members: properties and methods. LuaCOM deals with both.
Method accesses are done in the same way as calling Lua functions stored in a table and having a ``self'' parameter:
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)
It's important to notice the need of using the colon - ``:'' - for method calls. Although LuaCOM does not use the self parameter that Lua passes in this case, its presence is assumed, that is, LuaCOM always skips the first parameter in the case of method calls; forgetting it may cause nasty bugs. Notice that this rule doesn't apply when using the default method of a LuaCOM object stored in a table or in a property of another LuaCOM object (see section 3.3.2 below).
Accessing properties is much like the same of accessing fields in Lua tables:
obj = luacom.CreateObject("TEST.Test") if obj == nil then exit(1) end -- property access a = obj.TestData -- property setting obj.TestData = a + 1
Properties may also be accessed as methods. This is mandatory when dealing with parameterized properties, that it, ones that accept (or demand) parameters. A common example of this situation is the ``Item'' property of collections.
-- property access a = obj:TestData() -- Parametrized property access b = obj:TestInfo(2) -- Accessing collections c = obj.Files:Item(2)
Notice that the colon - ``:'' - must also be used in this situation.
When accessing properties with method calls, LuaCOM always translates the method call to a read access (property get). To set the value of a property using a method call, it's necessary append the prefix ``set''3.1 to the property name and the new value must be supplied as the last argument.
-- property access a = obj:TestData() -- Setting the property b = obj:setTestInfo(2) -- Setting a parametrized property c = obj.Files:setItem(2, "test.txt")
The prefix ``get'' may also be used, to clarify the code, although it's not necessary, as the default behavior is to make a read access.
-- property access a = obj:getTestData() b = obj:getTestInfo(2) c = obj.Files:getItem(2)
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 sheet = excel.Sheets(1) print(sheet.Name) -- Here we also call the default method -- We must supply the self parameter sheets = excel.Sheets sheet2 = sheets(2) print(sheet2.Name) -- Setting values excel.Sheets(1).Name = "MySheet1" excel:Quit()
This can be very useful when dealing with collections, as commonly they have a default Item property.
WARNING: one must be careful not to put the colon when using default methods of LuaCOM objects contained in table or in other LuaCOM objects (see the sample above).
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)
interface = {} interface.Test = 1 interface.TestIndex = {2,3} obj = luacom.ImplInterface(interface, "TEST.Test", "ITest") -- must print "1" print(obj.Test) -- must print nil (if there is no member named Test2) print(obj.Test2) -- this writes the filed Test obj.Test = 1 -- Indexed property read. Must return 3 (remember that -- indexed tables start at 1 in Lua) i = obj:TestIndex(2) -- Sets the indexed field obj:setTestIndex(2,4) -- Now must return 4 i = obj:TestIndex(2)
To establish a connection using LuaCOM, the owner of the ActiveX object
must create a table to implement the connection interface,
whose description is provided by the ActiveX object (this interface
is called a source interface) and then call the API method
Connect
, passing as arguments the LuaCOM object
for the ActiveX object and the implementation table. Doing this,
LuaCOM will automatically find the default source interface, create
a LuaCOM object implemented by the supplied table and then connect
this object to the ActiveX object. Here follows a sample:
-- Creates the COM object -- 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 = luacom.Connect(calendar, calendar_events) if res == nil then exit(1) end -- This should trigger the AfterUpdate event -- calendar:NextMonth()
It's also possible to separately create a LuaCOM object implementing the
connection point source interface and then connect it to the object using
AddConnection
.
-- Instances the COM object -- 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 -- event_handler = luacom.ImplInterface(calendar_events, "MSCAL.Calendar", "DCalendarEvents") if event_handler == nil then print("Error implementing DCalendarEvents") exit(1) end -- Connects both objects -- luacom.addConnection(calendar, event_handler) -- This should trigger the AfterUpdate event -- calendar:NextMonth() -- This disconnects the connection point established -- luacom.releaseConnection(calendar) -- This should NOT trigger the AfterUpdate event -- calendar:NextMonth()
luacom.DetectAutomation
).
LuaCOM has some policies concerning parameter passing. They specify how LuaCOM will translate COM parameter lists to Lua and vice-versa. There are two different situations to which these policies apply: calling a method of a COM object from Lua and calling a Lua function from COM. The main question here is how to deal with the different types of parameters supported by COM (``in'' parameters, ``out'' parameters, ``in-out'' parameters, ``optional'' parameters and ``defaultvalue'' parameters). There is also a special policy concerning generic LuaCOM objects.
This situation happens when accessing a property or calling a method of a COM object through the LuaCOM object. Here follows a sample:
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) word:Move(100,100)
In this situation, there are two steps in the parameter passing process:
When the caller of the method wants to omit a parameter, it must pass the nil value; LuaCOM then proceeds accordingly, informing the called method about the omission of the parameter. If the parameter has a default value, it is used instead. Notice that LuaCOM does not complain when one omits non-optional parameters. In fact, LuaCOM ignores the fact that a parameter is or isn't optional. It leaves the responsibility for checking this to the implementation of the called method.
The called method can omit the return value or the output values; LuaCOM them will return nil for each omitted value.
To illustrate these concepts, here follows a sample of these situations. First, we show an excerpt of an ODL file describing a method of a COM object:
HRESULT TestShort( [in] short p1, // an "in" parameter [out] short* p2, // an "out" parameter [in,out] short* p3, // an "in-out" parameter [out,retval] short* retval); // the return value
Now follows a sample of what happens when calling the method:
-- 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 r1, r2, r3 = com:TestShort(1,2) -- WRONG! The are only two in/in-out parameters! Out parameters -- are ignored in the lua2com parameter translation r1, r2, r3 = com:TestShort(1,2,3) -- WRONG! -- Here p1 = 1, p2 is uninitialized and p3 is omitted. r1, r2, r3 = com:TestShort(1) -- Here we ignore the output value p3 r1,r2 = com:TestShort(1) -- Here we ignore all output values (including the return value) com:TestShort(1,2)
When dealing with generic LuaCOM objects, the binding adopts a different policy: all Lua parameters are converted to COM ones as ``in-out'' parameters. LuaCOM assumes that these methods always return a value; if the called method does not return anything, LuaCOM pushes a nil value3.2. As all parameters are set as ``in-out'', all of them will be returned back to Lua, modified or not by the called method.
This situation happens when one implements a COM dispinterface in Lua. The ActiveX binding has to translate the COM method calls to Lua function calls. The policy here concerning parameter list translation is the same as the one above, just exchanging ``Lua'' for ``COM'' and vice-versa. That is, all ``in'' an ``in-out'' COM parameters are translated to parameters to the Lua function call (the output parameters are ignored). When the call finishes, the first return value is translated as the return value of the COM method and the other return values are translated as the ``in-out'' and ``out'' values, following the order they appear in the method's type information. Continuing the previous example, here we show the implementation of a method callable from COM:
implementation = {} -- This method receives TWO in/in-out parameters 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) 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 r1, r2, r3 = obj:TestShort(1,2)
lua_error
;
The run time errors can be divided into three types:
The third type of error is always translated into a COM exception returned to the server. To ease debugging, these errors are also logged (if the logging facility has been activated), as the server can silenty ignore these exceptions, specially in events.
If the LuaCOM library is compiled with VERBOSE
defined, then
a lot of informative messages are logged and all errors are displayed
within a dialog box. This helps debug errors inside events on the fly,
as these errors are commonly ignored by the server. Notice that this
options slows down LuaCOM and can generate very big log files.
The behaviour of LuaCOM for the other two types can be customized. There is a table called config inside the LuaCOM table. This table holds three fields related to error handling:
lua_error
. The default value for this field is false.
nil
where a return value is expected. The default value for this field is
true.
-- to make all LuaCOM errors runtime errors 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
All errors are also logged. Notice that some of the logged exceptions are not really errors: they are side-effects of the extensive use of exception handling inside LuaCOM code.
LuaCOM is responsible for converting values from COM to Lua and vice versa. Most of the types can be mapped from COM to Lua and vice versa without trouble. But there are some types for which the mapping is not obvious. LuaCOM then uses some predefined rules to do the type conversion. These rules must be known to avoid misinterpretation of the conversion results and to avoid errors.
true
and false
, but
does not works with the older convention (nil
and
non-nil
; see paragraph below).
-- This function alters the state of the of the window. -- state is a Lua boolean value -- window is a LuaCOM object function showWindow(window, state) if state then window.Visible = 1 -- this has the same result windows.Visible = -10 else window.Visible = 0 end end -- Shows window showWindow(window, 1) -- Hides window showWindow(window, nil)
A pointer to IDispatch is converted to a LuaCOMobject whose implementation is provided by this pointer. A LuaCOMobject is converted to COM simply passing its interface implementation to COM.
LuaCOM converts Lua tables to SAFEARRAY's and vice-versa. To be converted, Lua tables must be ``array-like'', that is, all of its elements must be or ``scalars'' or tables of the same length. These tables must also be ``array-like''. Here are some samples of how is this conversion done:
Lua table | Safe Array | |
table = {"name", "phone"} |
|
|
table = {{1,2},{4,9}} |
|
lua_error
or to a COM
exception, depending on who is the one being called.
LuaCOM deals with other objects besides COM Automation ones. Here we describe them briefly.
-- -- 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
The Extended Lua API method pairs allows the traversal of the enumeration using Lua's for statement. The sample above can be rewritten this way:
-- -- Sample use of enumerators -- -- Gets an instance word = luacom.GetObject("Word.Application") -- Prints the names of all open documents for index, doc in luacomE.pairs(word.Documents) do print(doc.Name) end
This object allows a COM object implemented using LuaCOM to send events to its client. It's used primarily when implementing COM object in Lua, so see chapter 4 for more information.
The registry information maps a ProgID to a CLSID, which is, in turn, mapped to a server. The type information describes the component, that is, which interfaces it exposes and what is the default interface.
LuaCOM simplifies these tasks providing some helper functions to deal with registration and instantiation of COM servers. LuaCOM suports both local (EXE) and in-process (DLL) servers.
Of course all this flexibility comes at some cost, primarily performance. Anyway, depending on the application, the performance drawback might be negligible.
LuaCOM does not solve all problems: there is still the need of a type library, which must be build using third party tools.
To avoid misunderstandings, here we'll supply the meaning we give to some terms used in this chapter. We don't provide formal definitions: we just want to ease the understanding of some concepts. To better understand these concepts, see COM's documentation.
There are some steps to build a COM server using LuaCOM:
This is the first step: to define what functionality the component will expose. This functionality is represented by an hierarchy of objects, rooted in the Application object. Each of these objects should implement an interface.
The objects to be exported are those belonging to the hierarchy rooted in the Application object. In Lua world, objects are ordinarily represented as tables or userdatas. So it's necessary to identify (or to implement) the Lua tables used to implement the objects to be exported.
The type library should contain entries for all the interfaces of exported objects and an entry for the CoClass, specifying the interface of the Application object and the interface used to send events.
The most common way to build a type library is to write an IDL describing the type library and them use an IDL compiler, such as Microsoft's© MIDL. Notice that all the interfaces must be dispinterfaces, that is, must inherit from IDispatch, and must have the flag oleautomation.
Here we must specify the information that is used by COM to locate the
component. See documentation of RegisterObject
.
Before being accessed by other applications, the component object must
be registered in the system registry. This can be done with the RegisterObject
API function.
This function receives a table of registration info for
the object. See the complete example for the fields of this table.
There are two different situations, which one demands different actions:
NewObject
to create a COM object and
bind it to the table of the Lua Application Object. Them this object must
be made available to other applications through ExposeObject
.
ImplInterface
. They can be implemented
in the initialization (and then be stored somewhere) or can be
implemented on-demand (that is, each time a COM object should be
return, a call to ImplInterface
is made).
Notice that the fields of the Lua table used to implement COM component will only be accessible if they are present in the type library. If not, they are invisible to COM.
If you are implementing your own server, instead of using the builtin support, your server
must call the COM initialization functions (OleInitialize or CoInitialize) before LuaCOM is
started. Other initialization task is the implementation and
exposition of the COM objects. This task can be greatly simplified
using the C/C++ LuaCOM API function luacom_detectAutomation
.
If you want to use the builtin support, the only initialization necessary is to call
the DetectAutomation
API function at the end of the script that implements your objects,
passing a table containing methods to register and expose your objects.
RevokeObject
for each
exposed object. Then it must call the COM termination functions AFTER
lua_close
has been called; otherwise fatal errors may occur.
A COM server built following the preceding guidelines can be used
as any other COM object, that is, using CoCreateInstance
,
CreateObject
or something like these.
The method NewObject
returns a connection point container
object. This object allows the component to send events to its clients
just calling methods on this object, passing the expected
parameters. Return values are not allowed yet.
This is an example of a Lua COM server. The example assumes this script is called testobj.lua:
-- This is the implementation of the COM object path_to_obj = "\\Path\\To\\Script\\" TestObj = {} function TestObj:showWindow() print("Show!") events:OnShow() end function TestObj:hideWindow() print("Hide!") events:OnHide() end COM = {} function COM:StartAutomation() -- creates the object using its default interface COMAppObject, events, e = luacom.NewObject(TestObj, "TEST.Test") -- This error will be caught by detectAutomation if COMAppObject == nil then error("NewObject failed: "..e) end -- Exposes the object cookie = luacom.ExposeObject(COMAppObject) if cookie == nil then error("ExposeObject failed!") end end function COM:Register() -- fills table with registration information local reginfo = {} reginfo.VersionIndependentProgID = "TEST.Test" reginfo.ProgID = reginfo.VersionIndependentProgID..".1" reginfo.TypeLib = "test.tlb" reginfo.CoClass = "Test" reginfo.ComponentName = "Test Component" reginfo.Arguments = "/Automation" reginfo.ScriptFile = path_to_script .. "testobj.lua" -- stores component information in the registry local res = luacom.RegisterObject(reginfo) if res == nil then error("RegisterObject failed!") end end -- Starts automation server luacom.DetectAutomation(COM)
Here is provided miscellaneous information specific to the current version of LuaCOM. Here are recorded the current limitations of LuaCOM, its known bugs, the history of modifications since the former version, technical details etc.
Here are listed the current limitations of LuaCOM, as of the current version, and information about future relaxation of this restrictions.
UnRegisterObject
'' method
yet. Objects registered with RegisterObject
must be
removed from the registry manually (or using another tool); this is
due to the next release;
Besides the enhancements listed in the sections 5.1 and 5.2, there are other planned enhancements:
LuaCOM is very similar to using other Automation-enabled languages or environments (Visual Basic©, VBA, VBScript©, pycom etc). Nevertheless, there are some subtle differences that might confuse the programmer: different syntax, unexpected behavior etc. To ease the task of the LuaCOM user, we grouped the information related to these isses here.
For more information, see section 3.2.1.
Some objects that have type information describing their interface (methods, properties, types of parameters etc), may add new methods and properties at runtime. This means that these methods and properties can only be accessed using the same mechanism LuaCOM uses for Generic COM object. This have some implications:
COM objects related to WMI have this behavior. For more information, see section 3.3.2.
Public lc as Object Set lc = CreateObject("MyCOMObject.InLuaCOM") lc.showWindow b = lc.getData(3) lc.Quit
But if one wants to received events generated by a COM object
implemented using LuaCOM, then it's necessary to use VB's
Public WithEvents
:
Public WithEvents obj as MyCOMObject.Application Set obj = CreateObject("MyCOMObject.Application") Private Sub obj_genericEvent() ' Put your event code here End Sub
Here there is a problem: when VB assigns the result of
CreateObject
to obj
variable, it tries to get an early
bound interface (as far as I know, VB only uses late-bound interfaces
with variables of type Object
). LuaCOM does not work with
early-bound interfaces (known as vtable). If you call any method using
the obj
variable, VB will throw an exception.
The solution we adopted was to accept a QueryInterface for a early-bound interface (thus allowing the use of Public WithEvents). Then the client must do a ``typecast'' to use correctly the COM object:
Public WithEvents obj_dummy as MyCOMObject.Application Public obj as Object Set obj_dummy = CreateObject("MyCOMObject.Application") Set obj = obj_dummy
This way the client may call methods of the COM object using the obj variable.
require
function;
luacom.GetObject("myfile.xls")
;
luacom
, although they are still accessible
globally as luacom_<function> in the Lua 4 version of the
library;
obj.Property(param)
'' is no longer supported. A
colon - ``:'' - must be used: ``obj:Property(param)
'';
void luacom_open(lua_State* L);
luacom
table and fills it with LuaCOM methods in the given
Lua state. Notice that it's necessary to initialize COM before,
using OleInitialize or CoInitialize or something like
that.
int main() { lua_State *L = lua_open(0); OleInitialize(NULL); luacom_open(L); . . . }
void luacom_close(lua_State* L);
int main() { lua_State *L = lua_open(0); OleInitialize(NULL); luacom_open(L); . . . luacom_close(L); lua_close(L); OleUninitialize(); }
int luacom_detectAutomation(lua_State *L, int argc, char *argv[]);
This function is simply a helper for those implementing Automation
servers using LuaCOM. Most of the work should be done by the Lua
code, using the methods RegisterObject
,
NewObject
, and ExposeObject
.
/* * com_object.cpp * * This sample C++ code initializes the libraries and * the COM engine to export a COM object implemented in Lua */ #include <ole2.h> // libraries extern "C" { #include <lua.h> #include <lualib.h> } #include <luacom.h> int main (int argc, char *argv[]) { int a = 0; CoInitialize(NULL); IupOpen(); lua_State *L = lua_open(0); lua_baselibopen (L); lua_strlibopen(L); lua_iolibopen(L); luacom_open(L); lua_dofile(L, "implementation.lua"); // Pushes the table containing the functions // responsible for the initialization of the // COM object lua_getglobal(L, "COM"); // detects whether the program was invoked for Automation, // registration or none of that int result = luacom_detectAutomation(L, argc, argv); switch(result) { case LUACOM_AUTOMATION: // runs the message loop, as all the needed initialization // has already been performed MessageLoop(); break; case LUACOM_NOAUTOMATION: // This only works as a COM server printf("Error. This is a COM server\n"); break; case LUACOM_REGISTER: // Notifies that the COM object has been // registered printf("COM object successfully registered."); break; case LUACOM_AUTOMATION_ERROR: // detectAutomation found /Automation or /Register but // the initialization Lua functions returned some error printf("Error starting Automation"); break; } luacom_close(L); lua_close(L); CoUninitialize(); return 0; } ------- -- implementation.lua -- -- This is a sample implementation of a COM server in Lua -- -- This is the implementation of the COM object TestObj = {} function TestObj:showWindow() dialog.show() end function TestObj:hideWindow() dialog.hide() end -- Here we create and populate the table to -- be used with detectAutomation COM = {} -- This functions creates the COM object to be -- exported and exposes it. function COM:StartAutomation() -- creates the object using its default interface COMAppObject, events, e = luacom.NewObject(TestObj, "TESTE.Teste") -- This error will be caught by detectAutomation if COMAppObject == nil then error("NewObject failed: "..e) end -- Exposes the object cookie = luacom.ExposeObject(COMAppObject) if cookie == nil then error("ExposeObject failed!") end end function COM:Register() -- fills table with registration information local reginfo = {} reginfo.VersionIndependentProgID = "TESTE.Teste" reginfo.ProgID = reginfo.VersionIndependentProgID..".1" reginfo.TypeLib = "teste.tlb" reginfo.CoClass = "Teste" reginfo.ComponentName = "Test Component" reginfo.Arguments = "/Automation" -- stores component information in the registry local res = luacom.RegisterObject(reginfo) if res == nil then error("RegisterObject failed!") end end
int luacom_IDispatch2LuaCOM(lua_State *L, void *pdisp_arg);
void CreateAndExport(lua_State* L) { // Creates the object IUnknown *obj = CreateObj(); // Gets the IDispatch IDispatch* pdisp = NULL; QueryInterface(IID_IDISPATCH, &pdisp); // pushes onto lua stack luacom_IDispatch2LuaCOM(L, (void *) pdisp); }
luacom_obj = luacom.CreateObject(ID, creation_context, untyped)
Parameter | Type |
---|---|
ProgID | String |
Return Item | Possible Values |
---|---|
luacom_obj | LuaCOM object
nil |
inet_obj = luacom.CreateObject("InetCtls.Inet") if inet_obj == nil then print("Error! Object could not be created!") end
implemented_obj = luacom.Connect(luacom_obj, implementation_table)
luacom_obj
, creates an instance of this interface whose
implementation is given by implementation_table
and creates a
connection point between the luacom_obj
and the implemented
source interface. Any calls made by the luacom_obj
to the
source interface implementation will be translated to Lua calls to
member function present in the implementation_table
. If the
method succeeds, the LuaCOM object implemented by
implementation_table
is returned; otherwise, nil
is
returned.
Notice that, to receive events, it's necessary to have a Windows message loop.
Parameter | Type |
---|---|
luacom_obj |
LuaCOM object |
implementation_table |
Table or userdata |
Return Item | Possible Values |
---|---|
implemented_obj | LuaCOM object
nil |
events_handler = {} function events_handler:NewValue(new_value) print(new_value) end events_obj = luacom.Connect(luacom_obj, events_handler)
implemented_obj = luacom.ImplInterface(impl_table, ProgID, interface_name)
Parameter | Type |
---|---|
impl_table |
table or userdata |
ProgID |
string |
interface_name |
string |
Return Item | Possible Values |
---|---|
implemented_obj | LuaCOM object
nil |
myobject = {} function myobject:MyMethod() print("My method!") end myobject.Property = "teste" luacom_obj = luacom.ImplInterface(myobject, "TEST.Test", "ITest") -- these are done via Lua myobject:MyMethod() print(myobject.Property) -- this call is done through COM luacom_obj:MyMethod() print(luacom_obj.Property)
impl_obj = luacom.ImplInterfaceFromTypelib( impl_table, typelib_path, interface_name, coclass_name)
Connect
, AddConnection
or
ExposeObject
. This parameter specifies the Component
Object class name to which the interface belongs, as one interface may
be used in more than one ``coclass''.
Parameter | Type |
---|---|
impl_table |
table or userdata |
typelib_path |
string |
interface_name |
string |
coclass_name |
(optional) string |
Return Item | Possible Values |
---|---|
implemented_obj | LuaCOM object
nil |
myobject = {} function myobject:MyMethod() print("My method!") end myobject.Property = "teste" luacom_obj = luacom.ImplInterfaceFromTypelib(myobject, "test.tlb", "ITest", "Test") -- these are done via Lua myobject:MyMethod() print(myobject.Property) -- this call is done through COM luacom_obj:MyMethod() print(luacom_obj.Property)
luacom_obj = luacom.GetObject(ProgID) luacom_obj = luacom.GetObject(moniker)
Parameter | Type |
---|---|
ProgID | String |
Return Item | Possible Values |
---|---|
luacom_obj | LuaCOM object
nil |
excel = luacom.GetObject("Excel.Application") if excel == nil then print("Error! Could not get object!") end
implemented_obj, events_sink, errmsg = luacom.NewObject(impl_table, ProgID)
ImplInterface
, doing
just a step further: it locates the default interface for the ProgID
and uses its type information. That is, this method creates a Lua
implementation of a COM object's default interface. This is useful
when implementing a complete COM object in Lua. It also creates a
connection point for sending events to the client application and
returns it as the second return value. If there are any problems in
the process (ProgID not found, default interface is not a
dispinterface etc), the method returns nil twice and returns the
error message as the third return value.
To send events to the client application, just call methods of the event sink table returned. The method call will be translated to COM calls to each connection. These calls may contain parameters (as specified in the type information).
Parameter | Type |
---|---|
impl_table |
table or userdata |
ProgID |
string |
Return Item | Possible Values |
---|---|
implemented_obj | LuaCOM object
nil |
event_sink | event sink table
nil |
errmsg | error message in the case of failure
nil |
myobject = {} function myobject:MyMethod() print("My method!") end myobject.Property = "teste" obj, evt, err = luacom.NewObject(myobject, "TEST.Test") -- these are done via Lua myobject:MyMethod() print(myobject.Property) -- this call is done through COM luacom_obj:MyMethod() print(luacom_obj.Property) -- here we sink events evt:Event1()
cookie = luacom.ExposeObject(luacom_obj)
ATTENTION: the object MUST be unregistered (using
RevokeObject
) before calling luacom_close or
lua_close, otherwise unhandled exceptions might occur.
Parameter | Type |
---|---|
luacom_obj | LuaCOM object |
Return Item | Possible Values |
---|---|
cookie | number
nil |
myobject = luacom.NewObject(impl_table, "Word.Application") cookie = luacom.ExposeObject(myobject) function end_of_application() luacom.RevokeObject(cookie) end
result = luacom.RegisterObject(registration_info)
The registration_info table must contain the following fields6.1:
mytypelib.tlb
, c:\app\test.tlb
, test.exe\1
(this
last one can be used when the type library is bound to the executable
as a resource).
This method is not a generic ``registering tool'' for COM components, as it assumes the component to be registered is implemented by the running executable during registration.
Parameter | Type |
---|---|
registration_info | table with registration information |
Return Item | Possible Values |
---|---|
result | nil or non-nil value |
-- Lua registration code function RegisterComponent() reginfo.VersionIndependentProgID = "TESTE.Teste" -- Adds version information reginfo.ProgID = reginfo.VersionIndependentProgID..".1" reginfo.TypeLib = "teste.tlb" reginfo.CoClass = "Teste" reginfo.ComponentName = "Test Component" reginfo.Arguments = "/Automation" local res = luacom.RegisterObject(reginfo) return res end
result = luacom.addConnection(client, server)
Parameter | Type |
---|---|
client | LuaCOM object |
server | LuaCOM object |
Return Item | Possible Values |
---|---|
result | number
nil |
obj = luacom.CreateObject("TEST.Test") event_sink = {} function event_sink:KeyPress(keynumber) print(keynumber) end event_obj = luacom.ImplInterface( event_sink, "TEST.Test", "ITestEvents") result = luacom.addConnection(obj, event_obj) if result == nil then print("Error!") exit(1) end
luacom.releaseConnection(client)
Parameter | Type |
---|---|
client | LuaCOM object |
obj = luacom.CreateObject("TEST.Test") event_sink = {} function event_sink:KeyPress(keynumber) print(keynumber) end event_obj = luacom.ImplInterface( event_sink, "TEST.Test", "ITestEvents") result = luacom.addConnection(obj, event_obj) if result == nil then print("Error!") exit(1) end . . . luacom.releaseConnection(obj)
progID = luacom.ProgIDfromCLSID(clsid)
Parameter | Type |
---|---|
clsid | string |
Return Item | Possible Values |
---|---|
progID | string
nil |
progid = luacom.ProgIDfromCLSID("{8E27C92B-1264-101C-8A2F-040224009C02}") obj = luacom.CreateObject(progid)
clsid = luacom.CLSIDfromProgID(progID)
ProgIDfromCLSID
.
luacom.ShowHelp(luacom_obj)
Parameter | Type |
---|---|
luacom_obj | LuaCOM object |
obj = luacom.CreateObject("TEST.Test") luacom.ShowHelp(obj)
iunknown = luacom.GetIUnknown(luacom_obj)
GetIUnknown
for the same LuaCOM object will return
the same userdata. This means that the reference count for the
IUnknown interface will be incremented only once (that is, the
first time the userdata is pushed) and will be decremented only when
all the references to that userdata go out of scope (that is, when the
userdata suffers garbage collection).
One possible use for this method is to check whether two LuaCOM objects reference the same COM object.
Parameter | Type |
---|---|
luacom_obj | LuaCOM object |
Return Item | Possible Values |
---|---|
iunknown | userdata with IUnknown metatable
nil |
-- Creates two LuaCOM objects for the same COM object -- (a running instance of Microsoft Word(R) ) word1 = luacom.GetObject("Word.Application") word2 = luacom.GetObject("Word.Application") -- These two userdata should be the same unk1 = luacom.GetIUnknown(word1) unk2 = luacom.GetIUnknown(word2) assert(unk1 == unk2)
answer = luacom.isMember(luacom_obj, member_name)
Parameter | Type |
---|---|
luacom_obj | LuaCOM object |
member_name | string |
Return Item | Possible Values |
---|---|
answer | nil or non-nil |
obj = luacom.CreateObject("MyObject.Test") if luacom.isMember(obj, "Test") then result = obj:Test() end
result = luacom.StartLog(log_file_name)
Parameter | Type |
---|---|
log_file_name | string |
Return Item | Possible Values |
---|---|
result | boolean |
ok = luacom.StartLog("luacomlog.txt") if not ok then print("log not opened") end
luacom.EndLog()
None.
None.
luacom.EndLog()
e = luacom.GetEnumerator(luacom_obj)
Parameter | Type |
---|---|
luacom_obj | LuaCOM object |
Return Item | Possible Values |
---|---|
e | enumerator object or nil |
-- Prints all sheets of an open Excel Application excel = luacom.GetObject("Excel.Application") e = luacom.GetEnumerator(excel.Sheets) s = e:Next() while s do print(s.Name) s = e:Next() end
The enumerator object is a proxy for the interface IEnumVARIANT. It can be obtained using the API method GetEnumerator.
The type library object is a proxy for the interface ITypeLib. It can be obtained using the API method LoadTypeLibrary or the type information object method GetTypeLib.
The type information object is a proxy for the interface ITypeInfo. It can be obtained using the API method GetTypeInfo or the type library object method GetTypeInfo.
This document was generated using the LaTeX2HTML translator Version 2002-2-1 (1.70)
Copyright © 1993, 1994, 1995, 1996,
Nikos Drakos,
Computer Based Learning Unit, University of Leeds.
Copyright © 1997, 1998, 1999,
Ross Moore,
Mathematics Department, Macquarie University, Sydney.
The command line arguments were:
latex2html -dir luacom-htmldoc -split 0 -show_section_numbers -local_icons -no_navigation -numbered_footnotes luacom.tex
The translation was initiated by Fabio Mascarenhas de Queiroz on 2004-09-13