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).
- ComExeSvr2: This sample adds support
for events.
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.
- IE Webbrowser Container, demonstrates how the
Internet Explorer ActiveX control may be used.
The include files which are refered to below are
-
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):
STDMETHOD QueryStatus, :ptr GUID, :DWORD, :ptr OLECMD, :ptr OLECMDTEXT
|
will be translated to the following 3 lines:
protoIOleCommandTarget_QueryStatus typedef proto :ptr, :ptr GUID, :DWORD, :ptr OLECMD, :ptr OLECMDTEXT
pIOleCommandTarget_QueryStatus typedef ptr protoIOleCommandTarget_QueryStatus
QueryStatus pIOleCommandTarget_QueryStatus ?
|
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):
IShellFolderVtbl struct
...
IShellFolderVtbl ends
pIShellFolderVtbl typedef ptr IShellFolderVtbl
IShellFolder struct
lpVtbl pIShellFolderVtbl ?
IShellFolder ends
|
4. Macro vf()
Macro vf() is used to call a method of an interface.
It requires 3 parameters, which are:
- interface pointer
- name of interface
- name of method to call
The macro is always used in conjunction with Masm's internal "invoke" macro, so for example:
invoke vf(pUnknown, IUnknown, QueryInterface), addr IID_IOleObject, addr m_pOleObject
|
The above line will in a first step be evaluated into:
mov edx, pUnknown
mov edx, [edx].IUnknown.pVtbl
invoke [edx].IUnknownVtbl.QueryInterface, pUnknown, addr IID_IOleObject, addr m_pOleObject
|
After invoke itself is evaluated, the code will look like:
mov edx, pUnknown
mov edx, [edx].IUnknown.pVtbl
lea eax, m_pOleObject
push eax
push offset IID_IOleObject
push pUnknown
call [edx].IUnknownVtbl.QueryInterface
|
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):
DEFINE_DISPMETHOD Window, Close, 0115h, METHOD, VT_BOOL, VT_VARIANT, VT_VARIANT, VT_VARIANT
|
will finally result in following equates being defined
Window_CloseDispId equ 0115h
Window_CloseType equ DISPATCH_METHOD
Window_CloseMD textequ <3, VT_VARIANT, VT_VARIANT, VT_VARIANT, 1, 2>
Window_CloseArgs textequ <th:LPDISPATCH, arg1:VARIANT, arg2:VARIANT, arg3:VARIANT, pRet:ptr BOOL>
|
and a dummy procedure
_TEXT$02 segment dword public 'CODE'
Window_Close proc pMD:ptr, pDisp:LPDISPATCH, arg1:VARIANT, arg2:VARIANT, arg3:VARIANT, pRet:ptr BOOL
Window_Close endp
_TEXT$02 ends
|
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:
invoke dm(pWindow, Window, Close), variant1, variant2, variant3
|
which is evaluated into (bold text from macro dm())
ifndef Window_CloseMDData
_TEXT$03 segment dword public 'CODE'
Window_CloseMDData label byte
dd Window_CloseDispId
dw Window_CloseMD
_TEXT$03 ends
extern DEFINE_INVOKEHELPER_is_missing:abs
endif
invoke Window_Close, addr Window_CloseMDData, pWindow, variant1, variant2, variant3
|
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.
Finally a sample for an event table:
BEGIN_EVENTS DWebBrowserEvents2
DEFINE_DISPEVENT OnQuit
END_EVENTS
|
will create the following code:
DWebBrowserEvents2_EventTab label dword
ifndef DWebBrowserEvents2_OnQuitMDData
_TEXT$03 segment dword public 'CODE'
DWebBrowserEvents2_OnQuitMDData label byte
dd DWebBrowserEvents2_OnQuitDispId
dw DWebBrowserEvents2_OnQuitMD
_TEXT$03 ends
endif
dd DWebBrowserEvents2_OnQuitMDData ;metadata describing arguments expected
dd _DWebBrowserEvents2_OnQuit ;address of event proc
dd -1
|
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:
- Create@CEvents :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.
- Connect@CEvents :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.
- Disconnect@CEvents :ptr CEvents
disconnects from server object.
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.
Example: Let's assume an event dispinterface is defined as:
DEFINE_DISPMETHOD _EventInterface, OnClick, 01h, METHOD, , VT_I4, VT_I4
DEFINE_DISPMETHOD _EventInterface, OnClose, 02h, METHOD, ,
|
To fire event OnClick one have to code:
invoke FIREEVENT(pConnectionPoint, _EventInterface, OnClick), dwXPos, dwYPos
|
which will result in code:
ifndef _EventInterface_OnClickMDData
_TEXT$03 segment dword public 'CODE'
_EventInterface_OnClickMDData label byte
dd _EventInterface_OnClickDispId
dw _EventInterface_OnClickMD
_TEXT$03 ends
endif
invoke FireEvent, addr _EventInterface_OnClickMDData, pConnectionPoint, dwXPos, dwYPos
|
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.
|
|
|
|
|