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 = 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}
Name = atom()
ProgramFlag = program | driver
Calls get_program
or get_driver
, depening on
the ProgramFlag
parameter.
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
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.
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 | 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, CoCreateFunction
. 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.
Note that this is the only way to get COM objects in
erl_com
, currently there is no access to the class
object. This might change in future versions.
The create_dispatch
variant creates an object with
the IDispatch
interface. The interface wanted can be
specified in the RefID
parameters.
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 = 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
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
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.)