[Ericsson Utvecklings AB]

erl_com

MODULE

erl_com

MODULE SUMMARY

Comet gen_server with API to Erlang COM client.

DESCRIPTION

The erl_com module is a gen_server that exposes an API to the port program and port driver that is used to call COM services from Erlang.

There is a mapping between types in Erlang and types in COM. The following table shows how Erlang types are converted by the port program to COM types.



 COM type       Erlang type             Comment
 --------       -----------             -------
 VT_I4          integer()
 VT_U4          integer()
 VT_BOOL        true | false
 VT_BSTR        string()                 Strings are 
                                         translated between
                                         Ascii and Unicode
 VT_DATE        {integer(), integer(), integer()}       
                                         Same format as returned
                                         from now()
                {{Year, Month, Day}, {Hour, Min, Sec}}
                                         Date and time, 
                                         with integers in tuples
 VT_PTR         {vt_*, out}              Any output parameter, 
                                         including return value
 VT_I1          {vt_i1, integer()}
 VT_U1          {vt_u1, integer()}
 VT_I2          {vt_i2, integer()}
 VT_U2          {vt_u2, integer()}
 VT_R8          float()
 VT_R4          {vt_r4, float()}
 VT_CY          {vt_cy, float()}         Note that the precision
                                         is lower on float()
 VT_DECIMAL     {vt_decimal, float()}    -"-
 VT_UNKNOWN     integer()                Should be sent to
                                         package_interface
 VT_DISPATCH    integer()                -"-
 other types    unsupported

    

Some of the internal Erlang types map to types in COM. Most types in COM, however, have no corresponding type in Erlang. In these cases, a special tuple is used, of the form {ComType, Value}, where ComType is the corresponding type-name as defined in ole2.h in the Microsoft Windows SDK.

In the functions below, the ComInterface is used. It is a tagged tuple, that identifies a COM interface in the port driver.

ComInterface = {com_interface, pid(), ThreadNo, InterfaceNo}
ThreadNo = InterfaceNo = integer()
    

EXPORTS

start_program() -> {ok, Pid}
start_program(ServerName) -> {ok, Pid}
get_program(ServerName) -> {ok, Pid}

Types:

Pid = pid()
ServerName = atom()

Starts a new server, and initializes the COM port. Also starts one thread for running COM calls.

This function starts the COM port as a port-program, in a separate process. The erl_com gen_server uses (as usual in Erlang), a pipe to communicate with the port. This has the benifit that a crash in the COM port, will not crash the emulator.

Each erl_com server starts a separate port-program.

The server can be started with or without a registered name.

Normally, only one erl_com server is started on a node, using the get_program/1 call, with possibly several threads for several clients. The only reason to start more than one server on the same node is if one crashes, then the others will keep on running.

This way to launch Comet can be used when:

Since this way is safer, it is the preferred way of using comet.

start_driver() -> {ok, Pid}
start_driver(ServerName) -> {ok, Pid}
get_driver(ServerName) -> {ok, Pid}

Types:

Pid = pid()
ServerName = atom

Starts a new server, and initializes the COM port. Also starts one thread for running COM calls.

The port is loaded as a port driver. This is the most efficient way to use COM, since the com port resides in the same process as the Erlang emulator. However this also means that crashing COM-objects will bring down the emulator.

The server can be started with or without a registered name. There is no advantage of having two servers on the same node.

The get_driver/1 call, gets a named process, or starts one if no one is running.

This way to launch Comet should only be used in two situations:

get_or_start(Name, ProgramFlag) -> {ok, Pid}

Types:

Name = atom()
ProgramFlag = program | driver

Calls get_program or get_driver, depening on the ProgramFlag parameter.

stop(ServerRef) -> ok

Types:

ServerRef = Name | Pid
Name = atom()
Node = atom()
Pid = pid()
Thread = integer()
Error = {com_error, Errcode}
Errcode = string()

Shuts the erl_com server down. This will stop any threads. Interfaces should be released before.

(Remember COM has no garbage collection!)

new_thread(ServerRef | PrevComThread) -> ComThread

Types:

ServerRef = Name | Pid
PrevComThread = ComThread = {com_thread, ServerRef, ThreadNo}
Thread = integer()

Creates a new Windows thread that can be used to create and manipulate COM objects. This is done automatically after erl_com is started. One thread is created.

To allow COM calls to take time without blocking the emulator, erl_com allows multi-threaded execution. The maximum number of threads is 60. However, creating more than a few is not useful for practical reasons.

When a COM-thread is created, it is suspended with a select function (which is called WaitForMultipleObjects in the Win32 API). Calling any COM-functions from the thread, is done by setting up a parameter buffer and signaling an event, that wakes up the thread.

The return value is a tuple that includes Thread, a thread index that is an integer between 0 and 60, which is unique for each thread, and allocated incrementally. Thread index values will be reused if a thread is ended.

All COM calls are asynchronous from the emulators view, they are never called from the emulator main thread, and thus only blocks the calling Erlang process.

end_thread(ComThread) -> ok

Types:

ComThread = {com_thread, ServerRef, ThreadNo}
ThreadNo = integer()

Ends a thread previously created with new_thread. If the thread has any interfaces, these must be released before the thread is ended, otherwise resource leakage can occur. (Remember COM has no garbage collection!)

The thread is signaled and will exit. The thread index will be marked as available, internally in the port program.

create_object(ThreadOrServer, Class) -> ComInterface
create_object(ThreadOrServer, Class, Ctx) -> ComInterface
create_object(ThreadOrServer, Class, RefID) -> ComInterface
create_object(ThreadOrServer, Class, RefID, Ctx) -> ComInterface
create_dispatch(ThreadOrServer, Class) -> ComInterface
create_dispatch(ThreadOrServer, Class, Ctx) -> ComInterface

Types:

ThreadOrServer = ComThread | ServerRef
ServerRef = Pid | Name
Pid = pid()
Name = atom()
ComThread = {com_thread, Pid, ThreadNo}
Class = string()
RefID = string()
Ctx = integer()
ThreadNo = integer()
InterfaceNum = integer()

This function creates a COM object. It calls the Win32 API function, CoCreateInstance. Refer to Windows documentation. The string Class can be either a GUID for a class, or a COM program string. Values for the Ctx are defined in erl_com.hrl. If no Ctx is given, all flags are set to one (using any local service).

When successful, this function creates a COM object, and returns a tuple ComInterface, which is a handle for the object, that is used for calling methods, and releasing the object.

The create_dispatch variant creates an object with the IDispatch interface. The interface wanted can be specified in the RefID parameters.

get_object(ThreadOrServer, Name) -> ComInterface
get_object(ThreadOrServer, Name, Interface) -> ComInterface
get_dispatch(ThreadOrServer, Name) -> ComInterface

Types:

ThreadOrServer = ComThread | ServerRef
ServerRef = Pid | Name
Pid = pid()
Name = atom()
ComThread = {com_thread, Pid, ThreadNo}
Name = string()
Interface = string()

This function gets a COM object. It calls the Win32 API function, CoGetObject. Refer to Windows documentation. The string name is a name that is used to get the object using a moniker. The bindOptions parameter of CoGetObject always contains default values.

When successful, this function references a COM object, and returns a tuple ComInterface, which is a handle for the object, that is used for calling methods, and releasing the object.

The get_dispatch variant gets an object with the IDispatch interface. Other interface wanted can be specified in the Interface parameter.

query_interface(ComInterface, Iid)

Types:

Iid = string()

Calls query_interface on the given interface. Note that in COM, an object is also considered an interface.

This function is used to see what interfaces an object implements and to do down-casting.

release(ComInterface)

In COM, all interfaces are reference-counted. The release function decrements the reference counter, and releases the interface (or object) if it reaches zero. Note that it is important to release all objects created, and interfaces acquired. Otherwise resource leaking will occur. Future versions of comet may provide for GC of COM objects.

This function in erl_com also returns the ComInterface tuple, after release it is not allowed to use the ComInterface.

com_call(ComInterface, MethodOffs)
com_call(ComInterface, MethodOffs, Pars)

Types:

MethodOffs = integer()
Pars = list()

This is the way to call a method in a virtual COM interface. Beware that the parameter types must match the types in the COM interface function. Any type errors, or bad parameter counts, will crash the COM driver.

Note that return values are handled with out parameters when using com_call/3. (As opposed to invoke/3).

This function should not be called explicitly, only from generated code (see com_gen).

EXPORTS

invoke(ComInterface, MethodName, Pars)
invoke(ComInterface, MethodID, Pars)

Types:

MethodName = string()
MethodID = integer()

There are two ways to call a method in a COM interface. A dispatch-interface, has a method invoke, that is used to call methods. This method is intended for interpreted languages. The invoke method is safer than com_call, but also slower.

In many cases, the overhead of using invoke, is not significant. Therefore, it should be preferred, since it has parameter checking, better error messages, etc.

The return vaule sometimes needs a bit of processing. In particular, an interface is returned as an integer only, and the function package_interface must be called (see below).

property_get(ComInterface, MethodID)
property_get(ComInterface, MethodID, [Parameters])
property_get(ComInterface, MethodName)
property_get(ComInterface, MethodName, [Parameters])

To get a property value through the dispatch-interface, this function is used.

property_put(ComInterface, MethodName, Value)
property_put(ComInterface, MethodName, [Parameters], Value)
property_put(ComInterface, MethodID, Value)
property_put(ComInterface, MethodID, [Parameters], Value)

To set a property value through the dispatch-interface, this function is used.

property_put_ref(ComInterface, MethodName, Value)
property_put_ref(ComInterface, MethodName, [Parameters], Value)
property_put_ref(ComInterface, MethodID, Value)
property_put_ref(ComInterface, MethodID, [Parameters], Value)

To set a property reference through the dispatch-interface, this function is used.

package_interface(ThreadOrInterface, NewIntfNum) -> NewComInterface

Types:

ThreadOrInterface = ComThread | ComInterface

This function converts an interface number, as returned from erl_com when interface-returning COM calls are made, into an interface tuple. This interface tuple can be used in other COM calls.

Note that this function is called in generated code (see com_gen).

get_method_id(DispatchInterface, MethodName) -> MethodID

Types:

DispatchInterface = ComInterface
MethodName = string()
MethodID = integer()

Finds the ID of a method (or property), given its name. The interface must be a dispatch-interface.

get_interface_info(ComInterface, VirtualOrDispatch) -> TypeInfo
get_interface_info(ComInterface, TypeName, VirtualOrDispatch) -> TypeInfo

Types:

VirtualOrDispatch = virtual | dispatch
TypeName = string()
TypeInfo = EnumInfo | InterfaceInfo | ClassInfo
EnumInfo = {enum, virtual, TypeId, [EnumMember...], [Subtype...]}
TypeId = {Name, IID}
Name = IID = string()
EnumMember = {EnumName, EnumValue}
EnumValue = integer()
ClassInfo = {coclass, virtual, TypeId, [], []}
InterfaceInfo = DispatchInfo | VirtualInfo
DispatchInfo = {dispatch, IntfKind, TypeId, [Func...], [Subtype...]}
VirtualInfo = {interface, IntfKind, TypeId, [Func...], [Subtype...]}
IntfKind = dual | dispatch | virtual
Func = {FuncName, [InvKind], FuncType, IdOrOffset, [Parameter...], ReturnValue}
EnumName = FuncName = string()
InvKind = func | property_get | property_put | property_put_ref
FuncType = virtual | purevirtual | nonvirtual | static | dispatch
IdOrOffset = integer
ReturnValue = ComType | void
Parameter = {ParamName, [ParamFlag...], ComType, DefaultValue
ParamName = string()
ParamFlag = in | out | lcid | retval | optional | has_default | has_custom_data
ComType = vt_i4 | vt_str | ... see above
DefaultValue = {ComType, Value} | {}
SubType = TypeId

How about that? If it looks complicated it's because it is.

The get_interface_info is used to retrieve information from a COM type library. It is actually a misnomer, it's not just for interfaces, but also for enums and coclasses. Other types of types are unsupported by comet (currently).

Given an interface and a type name, it fetches most of the information available in the typelibrary, using the ITypeInfo and ITypeLib interfaces. It is kind of an erlang version of the OLE/COM object viewer in the Windows SDK. An interface can be listed as a dispatch or a virtual interface.

This function is used by com_gen to provide erlang stub generation from type libraries.

To understand its output, refer to the COM documentation on ITypeInfo and ITypeLib, or to a book.

There is currently no way in comet to retrieve information from a type-library without creating at least one object from it. This might be improved in later releases.

get_typelib_info(ComInterface) -> TypeLibInfo

Types:

TypeLibInfo = {TypeLibName, [TypeInfo...]}
TypeInfo = {TypeKind, TypeName, IID}
TypeKind = enum | record | module | interface | dispatch | coclass | alias | union

The get_typelib_info function lists all types in a COM type library. It is used by com_gen to generate stub code.

Note that only enums, interfaces (including dispatch interfaces) and classes can be used in get_interface_info.

test(ComInterface) -> []

The test function simply makes the COM port to a DebugBreak() Win32 API call. This breaks into the debugger (such as Visual C++). It is really handy to debug COM interfaces written in C. It is also useful for finding bugs in comet. (Luckily, there are no bugs left in the code.)

enum(ComInterface) -> ComEnum

Types:

ComEnum = ComInterface

This is a utility function that calls the DISPID_ENUM property on a COM-object, and returns the result as an interface, suitable for next and nexti.

next(ComEnum) -> Variant
nexti(ComEnum) -> ComInterface
intfenum_next(ComEnum) -> ComInterface

Types:

ComEnum = ComInterface

The next function calls the Next method on an IEnumVARIANT interface. The nexti function does this too, and also packages the result with package_interface (often the Variant result is known to be an interface).

The intfenum_next calls the Next method on an IEnumIUnknown, the only difference is the size of the result.

When the iterator reaches the end, an empty tuple {} is returned, this is a value that cannot be in a variant.

map_enum(ComEnum, Fun)
map_enumi(ComEnum, IFun)
map_intfenumi(ComEnum, IFun)

Types:

ComEnum = ComInterface
Fun = fun(Variant)
IFun = fun(ComInterface)

These functions maps over a COM iterator (Com enum) and applies the given fun, the values are collected in a list.

The interface functions (map_enumi and map_intfenumi) uses nexti to iterate. They also releases the interface return from nexti. (This means that the value parameter of the fun shouldn't be returned or stored anywhere.)

AUTHORS

Jakob Cederlund - support@erlang.ericsson.se

comet 1.1.1
Copyright © 1991-2002 Ericsson Utvecklings AB