COM & Assembly

A. Purpose

Some of the basic technics used in the COM samples for assembly (Masm syntax) are described here.
It is not a must to understand these things to use COM with Masm or JWasm, but it might help.
Please note that no introduction to COM is given here. There exist other
places on the net for that purpose.
The source samples which will demonstrate the things described here are:

SimpleServer: "Hello world" COM sample.
Implements a dual interface with just one property. It is implemented as an in-proc server (=dll).

SimplestServer:
Unlike SimpleServer this sample implements a custom, nondual
interface, which works with VB, but not with WSH or VBScript.

ComExeSvr: This is mainly the
same as SimpleServer, but implemented as an out-of-process server (=exe).

These samples should demonstrate how "scriptable" COM components can be implemented in assembly.
None of them provides a GUI interface, however.
This requires quite some more COM interfaces to be implemented, which is beyond the scope of this introduction.

Some more sophisticated examples, all written in assembly (Masm style):

AsmCtrl, an example of a fully
functional, embeddable OCX control, written without the help of libraries or
complex macros.

AsmCtrlX, has quite the same
functionality like AsmCtrl, but uses a lot of helper macros.

RegView, a COM object to extend
Windows explorer. After installation it will allow to view the Windows
registry inside explorer.

ExcelHost, an example of an application
using automation to host COM objects.

IE Automation, demonstrates how to control
Internet Explorer by COM Automation.

OBJBASE.INC: this is an assembly version (Masm-style) of OBJBASE.H, which defines basic COM stuff.
This file is part of WinInc.

DISPHLP.INC: this contains helper macros which are intended to make the IDispatch
interface (almost) as easy to use with Masm/JWasm as it is with C(++).

B. Basic Macros in OBJBASE.INC

1. Macro BEGIN_INTERFACE

Macro BEGIN_INTERFACE starts definition of an interface. It has 2 parameters.
The first is required and is the name of the interface, the second is optional and
is the name of an interface to be inherited. An example is:

BEGIN_INTERFACE IShellFolder, IUnknown

This line will start declaration of interface IShellFolder, inheriting from IUnknown.
The code generated by this macro will look like:

IShellFolderVtbl struct
IUnknownVtbl <>

2. Macro STDMETHOD

Macro STDMETHOD defines a method inside current interface definition.
It has at least 1 parameter, which is the name of the method. Other parameters
may follow, but do NOT declare THIS pointer as a parameter. It is automatically
included.
Each STDMETHOD macro will result in two typedefs and one variable being included. First is a
function prototype type definition, second is a pointer to this function type
definition. The structure variable, which is defined at last, will get the type of the second type definition.
So Masm/JWasm will be able to check number and type of parameters for each call of this
method.
Example (from Interface IOleCommandTarget):

3. Macro END_INTERFACE

Macro END_INTERFACE has no parameters and finishes current interface definition.
In fact, the vtable definition is terminated and the interface itself -
which consists of a pointer to this vtable only - is defined. Example:

BEGIN_INTERFACE IShellFolder, IUnknown
...
END_INTERFACE

will be translated to the following lines (the bold ones come from END_INTERFACE):

where only the bold text comes from macro vf()!
This implicates that register edx MUST NOT be used in any form as a parameter.
Macro vf() is unable to detect usage of edx for any parameter behind the closing bracket,
so no error message is displayed in this case.

C. IDispatch Support Macros in DISPHLP.INC

1. IDispatch Interface

If an interface is derived from IDispatch then there exists another possible way
to call methods of this interface. That's by using method IDispatch::Invoke. Include
File DISPHLP.INC - supplied with COMView - offers some support macros for calling
methods this way. With these calling a method by IDispatch::Invoke is as simple as
calling it by vtable with macro vf().

2. Macro DEFINE_DISPMETHOD

This macro describes a method being called with IDispatch::Invoke.
In fact some text equates and a dummy procedure are defined.
Example (method "Close", interface "Window" with 3 variants as parameters and returning a BOOL):

will be generated. The dummy procedure is necessary to make Masm/JWasm check parameters when calling this method.
No code is generated, though, and "segment" _TEXT$02 MUST remain empty. This will result
in proc InvokeHelper - located immediately "behind" in "segment" _TEXT$02$1 - being entered
if proc Window_Close is being called.

3. Macro dm()

Macro dm() is used to call a method thru IDispatch::Invoke. It works only in conjunction
with macro DEFINE_INVOKEHELPER (see below). So if you use dm() and haven't coded DEFINE_INVOKEHELPER,
you will most likely get a linker error. Besides that macro dm() is used similar to macro vf(),
with exactly the same parameters.
If using the example above dm() would look like:

As you may notice, there is - besides "this" pointer pWindow - another hidden parameter, the "metadata" pointer addr Window_CloseMDData.
This "metadata" is defined for each method being used in code in section _TEXT$03, its value comes from
equates defined by macro DEFINE_DISPMETHOD and will be used by proc InvokeHelper.
External DEFINE_INVOKEHELPER_is_missing ensures that macro DEFINE_INVOKEHELPER will
exist in source code

4. Macro DEFINE_INVOKEHELPER

Macro DEFINE_INVOKEHELPER defines proc InvokeHelper and public DEFINE_INVOKEHELPER_is_missing.
This proc is not called directly.
But in fact every call generated with macro dm() will hopefully result in a call of this procedure.
That's because InvokeHelper will be located in section _TEXT$02$1, which the linker should place
immediately behind section _TEXT$02, where dummy procs generated by macro DEFINE_DISPMETHOD are located

What will proc InvokeHelper do? Depending on first parameter, the "metadata" pointer,
it will get the other parameters from stack, convert it to variants and put them in a dynamically build
array of variants. After that it will call IDispatch::Invoke and, if a return parameter is
expected, convert returned variant into appropriate return type and store it.
Finally InvokeHelper clears the stack and returns to caller.
For more details please take a look in include file DISPHLP.INC.

D. Client Event Support macros in DISPHLP.INC

1. Macro BEGIN_EVENTS

This macro will define an event table. It requires one parameter, which should be
the name of the event interface. A label xxx_EventTab will be defined, where
xxx stands for the interface name.

2. Macro DEFINE_DISPEVENT

This macro defines an entry in the current event table.
One or two parameters are expected.
First will be the name of a method for which event notifications
should be received.
Second - optional - parameter is local name of the event procedure.
The macro can only occure between macros BEGIN_EVENTS and END_EVENTS.

3. Macro END_EVENTS

This macro will finish definition of an event table. It defines a DWORD with value
-1 to indicate the end of the table.

4. Macro DEFINE_EVENTHELPER

Macro DEFINE_EVENTHELPER defines an IDispatch interface which will act as event sink object.
As well the following procedures are defined:

[email protected] :LPVOID
just will create a CEvent object, the parameter supplied will be used as a Cookie
and delivered as first parameter to the event procs defined. It doesn't need to be an IDispatch or
even IUnknown object.

[email protected] :ptr CEvents, pServer:LPUNKNOWN, pEventInterfaceIID:REFIID, pEventTab:LPVOID
connects to a server object. Parameter pServer is server object to connect to,
pEventInterfaceIID is interface IID of outgoing interface for which events are requested and pEvents is
a pointer to an event table defined with macros BEGIN_EVENTS, DEFINE_DISPEVENT and END_EVENTS.

E. Server Event Support macros in DISPHLP.INC

1. Macro FIREEVENT()

Macro FIREEVENT() requires 3 parameters and is used only in conjunction with macro invoke, so
its syntax is similiar to macros vf() or dm(). As well, it requires macro DEFINE_FIREEVENTHELPER
to be inserted somewhere in the code, since it calls procedure FireEvent.

2. Macro DEFINE_FIREEVENTHELPER

Macro DEFINE_FIREEVENTHELPER defines public proc FireEvent, which will be called from inside
macro FIREEVENT(). It moves all parameters to an array of variants, prepares a DISPPARAMS
structure and will notify all connected sinks of the event. For this to work the server
must support interface IEnumConnections.

F. Screen Shots

The following screenshots are done inside COMView, which
is a powerful tool to view and handle COM objects.

1. SimpleServer

a. COMView displays the type library of the "SimpleServer" class. The "CanCreate"
attribute is a hint that an object of this type could be created:

b. The type information for ISimpleServer interface reveals that it is derived from
IDispatch and that there is just 1 additional property with name "Property1" (properties are implemented
by a pair of methods to get/set their value) available:

c. COMView created a "SimpleServer" object. Note the presence of the IDispatch interface:

d. COMView showing the properties of the just created "SimpleServer" object:

2. SimplestServer

a. Now the same procedure done with the "SimplestServer" object. The type
library already shows a difference, there is no "dual" interface implemented:

b. The type information for ISimplestServer is quite different with just 2 entries.
The interface is directly derived from IUnknown, however, but the 3 IUnknown methods
QueryInterface(), AddRef() and Release() are not shown here:

c. The "SimplestServer" object has been created. No IDispatch interface is implemented.
This forces the hosting application to use the vtable mechanism to "talk" to the object:

d. Properties of "SimplestServer" object. COMView displays "vtable mode is on" on the
status line, indicating that no IDispatch is available.

3. ComExeSvr2

a. The "ComExeSvr2" object is different from SimpleServer in 2 regards: it is
an out-of-process server and it has an event interface implemented. The
type library also shows that the event interface has its own type information (_ComExeSvr2Event)
and that it is not marked as "dual":

b. The type information for _ComExeSvr2Event. There is no vtable for this interface,
it is "dispatchonly", which is common practice for event interfaces. The interface
contains just 1 method:

c. The "ComExeSvr2" object has been created.
COMView displays event interfaces in the "outgoing interfaces" part
of the dialog screen. Compared to "SimpleServer" there are additional
interfaces in the first list. IMarshal, IClientSecurity and IMultiQI
are created automatically by COM because this server runs as a separate
process.