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()
start_program() -> {ok, Pid}
start_program(ServerName) -> {ok, Pid}
get_program(ServerName) -> {ok, Pid}
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}
Pid = pid()ServerName = atomStarts 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}
Name = atom()ProgramFlag = program | driver Calls get_program or get_driver, depening on
the ProgramFlag parameter.
ServerRef = Name | PidName = 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
ServerRef = Name | PidPrevComThread = 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.
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
ThreadOrServer = ComThread | ServerRefServerRef = Pid | NamePid = 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
ThreadOrServer = ComThread | ServerRefServerRef = Pid | NamePid = 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)
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.
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)
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).
invoke(ComInterface, MethodName, Pars)
invoke(ComInterface, MethodID, Pars)
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
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
DispatchInterface = ComInterfaceMethodName = 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
VirtualOrDispatch = virtual | dispatchTypeName = string()TypeInfo = EnumInfo | InterfaceInfo | ClassInfoEnumInfo = {enum, virtual, TypeId, [EnumMember...], [Subtype...]}TypeId = {Name, IID}Name = IID = string()EnumMember = {EnumName, EnumValue}EnumValue = integer()ClassInfo = {coclass, virtual, TypeId, [], []}InterfaceInfo = DispatchInfo | VirtualInfoDispatchInfo = {dispatch, IntfKind, TypeId, [Func...], [Subtype...]}VirtualInfo = {interface, IntfKind, TypeId, [Func...], [Subtype...]}IntfKind = dual | dispatch | virtualFunc = {FuncName, [InvKind], FuncType, IdOrOffset, [Parameter...], ReturnValue}EnumName = FuncName = string()InvKind = func | property_get | property_put | property_put_refFuncType = virtual | purevirtual | nonvirtual | static | dispatchIdOrOffset = integerReturnValue = ComType | voidParameter = {ParamName, [ParamFlag...], ComType, DefaultValueParamName = string()ParamFlag = in | out | lcid | retval | optional | has_default | has_custom_dataComType = vt_i4 | vt_str | ... see aboveDefaultValue = {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
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.
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.)
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
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)
ComEnum = ComInterfaceFun = 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.)