home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Reverse Code Engineering RCE CD +sandman 2000
/
ReverseCodeEngineeringRceCdsandman2000.iso
/
RCE
/
LordLucifer
/
win32asm
/
tutorials
/
com_2.txt
< prev
next >
Wrap
Text File
|
2000-05-25
|
18KB
|
563 lines
COM in ASM - Part 2
------------------------------------------------------------------------------
My previous atricle described how to use COM objects in your assembly
language programs. It described only how to call COM methods, but not how to
create your own COM objects. This article will describe how to do that.
This article will describe implementing COM Objects, using MASM syntax. TASM
or NASM assemblers will not be considered, however the methods can be easily
applied to any assembler.
This article will also not describe some of the more advanced features of COM
such as reuse, threading, servers/clients, and so on. These will presented
in future articles.
COM Interfaces Review
------------------------------------------------------------------------------
An interface definition specifies the interface's methods, their return types,
the number and types of their parameters, and what the methods must do. Here
is a sample interface definition:
IInterface struct
lpVtbl dd ?
IInterface ends
IInterfaceVtbl struct
; IUnknown methods
STDMETHOD QueryInterface, :DWORD, :DWORD, :DWORD
STDMETHOD AddRef, :DWORD
STDMETHOD Release, :DWORD
; IInterface methods
STDMETHOD Method1, :DWORD
STDMETHOD Method2, :DWORD
IInterfaceVtbl ends
STDMETHOD is used to simplify the interface declaration, and is defined as:
STDMETHOD MACRO name, argl :VARARG
LOCAL @tmp_a
LOCAL @tmp_b
@tmp_a TYPEDEF PROTO argl
@tmp_b TYPEDEF PTR @tmp_a
name @tmp_b ?
ENDM
This macro is used to greatly simplify interface declarations, and so that the
MASM invoke syntax can be used. (Macro originally by Ewald :)
Access to the interface's methods occurs through a pointer. This pointer
points to a table of function pointers, called a vtable. Here is a sample
method call:
mov eax, [lpif] ; lpif is the interface pointer
mov eax, [eax] ; get the address of the vtable
invoke (IInterfaceVtbl [eax]).Method1, [lpif] ; indirect call to the function
- or -
invoke [eax][IInterfaceVtbl.Method2], [lpif] ; alternate notation
Two different styles of addressing the members are shown. Both notations
produce equivalent code, so the method used is a matter of personal
preference.
All interfaces must inherit from the IUnknown interface. This means that the
first 3 methods of the vtable must be QueryInterface, AddRef, and Release.
The purpose and implementation of these methods will be discussed later.
GUIDS
------------------------------------------------------------------------------
A GUID is a Globally Unique ID. A GUID is a 16-byte number, that is unique
to an interface. COM uses GUID's to identify different interfaces from one
another. Using this method prevents name clashing as well as version
clashing. To get a GUID, you use a generator utility that is included with
most win32 development packages.
A GUID is represented by the following structure:
GUID STRUCT
Data1 dd ?
Data2 dw ?
Data3 dw ?
Data4 db 8 dup(?)
GUID ENDS
A GUID is then defined in the data section:
MyGUID GUID <3F2504E0h, 4f89h, 11D3h, <9Ah, 0C3h, 0h, 0h, 0E8h, 2Ch, 3h, 1h>>
Once a GUID is assigned to an interface and published, no furthur changes to
the interface definition are allowed. Note, that this does mean that the
interface implementation may not change, only the definition. For changes
to the interface definition, a new GUID must be assigned.
COM Objects
------------------------------------------------------------------------------
A COM object is simply an implementation of an interface. Implentation
details are not covered by the COM standard, so we are free to implement our
objects as we choose, so long as they satisfy all the requirements of the
interface definition.
A typical object will contain pointers to the various interfaces it supports,
a reference count, and any other data that the object needs. Here is a sample
object definition, implemented as a structure:
Object struct
interface IInterface <?> ; pointer to an IInterface
nRefCount dd ? ; reference count
nValue dd ? ; private object data
Object ends
We also have to define the vtable's we are going to be using. These tables
must be static, and cannot change during run-time. Each member of the vtable
is a pointer to a method. Following is a method for defining the vtable.
@@IInterface segment dword
vtblIInterface:
dd offset IInterface@QueryInterface
dd offset IInterface@AddRef
dd offset IInterface@Release
dd offset IInterface@GetValue
dd offset IInterface@SetValue
@@IInterface ends
Reference Counting
------------------------------------------------------------------------------
COM object manage their lifetimes through reference counting. Each object
maintains a reference count that keeps track of how many instances of the
interface pointer have been created. The object is required to keep a
counter that supports 2^32 instances, meaning the reference count must be a
DWORD.
When the reference count drops to zero, the object is no longer in use, and
it destroys itself. The 2 IUnknown methods AddRef and Release handle the
reference counting for a COM object.
QueryInterface
------------------------------------------------------------------------------
The QueryInterface method is used by a COM object to determine if the object
supports a given interface, and then if supported, to get the interface
pointer. There are 3 rules to implementing the QueryInterface method:
1. Objects must have an identity - a call to QueryInterface must always
return the same pointer value.
2. The set of interfaces of an object must never change - for example, if
a call to QueryInterface with on IID succeeds once, it must succeed
always. Likewise, if it fails once, it must fail always.
3. It must be possible to successfully query an interface of an object
from any other interface.
QueryInterface returns a pointer to a specified interface on an object to
which a client currently holds an interface pointer. This function must call
the AddRef method on the pointer it returns.
Following are the QueryInterface parameters:
pif : [in] a pointer to the calling interface
riid : [in] pointer to the IID of the interface being queried
ppv : [out] pointer to the pointer of the interface that is to be set.
If the interface is not supported, the pointed to value is set to 0
QueryInterface returns the following:
S_OK if the interface is supported
E_NOINTERFACE if not supported
Here is a simple assembly implementation of QueryInterface:
IInterface@QueryInterface proc uses ebx pif:DWORD, riid:DWORD, ppv:DWORD
; The following compares the requested IID with the available ones.
; In this case, because IInterface inherits from IUnknown, the IInterface
; interface is prefixed with the IUnknown methods, and these 2 interfaces
; share the same interface pointer.
invoke IsEqualGUID, [riid], addr IID_IInterface
or eax,eax
jnz @1
invoke IsEqualGUID, [riid], addr IID_IUnknown
or eax,eax
jnz @1
jmp @NoInterface
@1:
; GETOBJECTPOINTER is a macro that will put the object pointer into eax,
; when given the name of the object, the name of the interface, and the
; interface pointer.
GETOBJECTPOINTER Object, interface, pif
; now get the pointer to the requested interface
lea eax, (Object ptr [eax]).interface
; set *ppv with this interface pointer
mov ebx, [ppv]
mov dword ptr [ebx], eax
; increment the reference count by calling AddRef
GETOBJECTPOINTER Object, interface, pif
mov eax, (Object ptr [eax]).interface
invoke (IInterfaceVtbl ptr [eax]).AddRef, pif
; return S_OK
mov eax, S_OK
jmp return
@NoInterface:
; interface not supported, so set *ppv to zero
mov eax, [ppv]
mov dword ptr [eax], 0
; return E_NOINTERFACE
mov eax, E_NOINTERFACE
return:
ret
IInterface@QueryInterface endp
AddRef
------------------------------------------------------------------------------
The AddRef method is used to increment the reference count for an interface
of an object. It should be called for every new copy of an interface pointer
to an object.
AddRef takes no parameters, other than the interface pointer required for all
methods. AddRef should return the new reference count. However, this value
is to be used by callers only for testing purposes, as it may be unstable in
certain situations.
Following is a simple implementation of the AddRef method:
IInterface@AddRef proc pif:DWORD
GETOBJECTPOINTER Object, interface, pif
; increment the reference count
inc [(Object ptr [eax]).nRefCount]
; now return the count
mov eax, [(Object ptr [eax]).nRefCount]
ret
IInterface@AddRef endp
Release
------------------------------------------------------------------------------
Release decrements the reference count for the calling interface on a object.
If the reference count on the object is decrememnted to 0, then the object is
freed from memory. This function should be called when you no longer need to
use an interface pointer
Like AddRef, Release takes only one parameter - the interface pointer. It
also returns the current value of the reference count, which, similarly, is to
be used for testing purposess only
Here is a simple implementation of Release:
IInterface@Release proc pif:DWORD
GETOBJECTPOINTER Object, interface, pif
; decrement the reference count
dec [(Object ptr [eax]).nRefCount]
; check to see if the reference count is zero. If it is, then destroy
; the object.
mov eax, [(Object ptr [eax]).nRefCount]
or eax, eax
jnz @1
; free the object: here we have assumed the object was allocated with
; LocalAlloc and with LMEM_FIXED option
GETOBJECTPOINTER Object, interface, pif
invoke LocalFree, eax
@1:
ret
IInterface@Release endp
Creating a COM object
------------------------------------------------------------------------------
Creating an object consisits basically of allocating the memory for the
object, and then initializeing its data members. Typically, the vtable
pointer is initialized and the reference count is zeroed. QueryInterface
could then be called to get the interface pointer.
Other methods exist for creating objects, such as using CoCreateInstance, and
using class factories. These methods will not be discussed, and may be a
topic for a future article.
COM implementatiion sample application
------------------------------------------------------------------------------
Here follows a sample implementation and usage of a COM object. It shows how
to create the object, call its methods, then free it. It would probably be
very educational to assemble this and run it through a debugger.
.386
.model flat,stdcall
include windows.inc
include kernel32.inc
include user32.inc
includelib kernel32.lib
includelib user32.lib
includelib uuid.lib
;-----------------------------------------------------------------------------
; Macro to simply interface declarations
; Borrowed from Ewald, http://here.is/diamond/
STDMETHOD MACRO name, argl :VARARG
LOCAL @tmp_a
LOCAL @tmp_b
@tmp_a TYPEDEF PROTO argl
@tmp_b TYPEDEF PTR @tmp_a
name @tmp_b ?
ENDM
; Macro that takes an interface pointer and returns the implementation
; pointer in eax
GETOBJECTPOINTER MACRO Object, Interface, pif
mov eax, pif
IF (Object.Interface)
sub eax, Object.Interface
ENDIF
ENDM
;-----------------------------------------------------------------------------
IInterface@QueryInterface proto :DWORD, :DWORD, :DWORD
IInterface@AddRef proto :DWORD
IInterface@Release proto :DWORD
IInterface@Get proto :DWORD
IInterface@Set proto :DWORD, :DWORD
CreateObject proto :DWORD
IsEqualGUID proto :DWORD, :DWORD
externdef IID_IUnknown:GUID
;-----------------------------------------------------------------------------
; declare the interface prototype
IInterface struct
lpVtbl dd ?
IInterface ends
IInterfaceVtbl struct
; IUnknown methods
STDMETHOD QueryInterface, pif:DWORD, riid:DWORD, ppv:DWORD
STDMETHOD AddRef, pif:DWORD
STDMETHOD Release, pif:DWORD
; IInterface methods
STDMETHOD GetValue, pif:DWORD
STDMETHOD SetValue, pif:DWORD, val:DWORD
IInterfaceVtbl ends
; declare the object structure
Object struct
; interface object
interface IInterface <?>
; object data
nRefCount dd ?
nValue dd ?
Object ends
;-----------------------------------------------------------------------------
.data
; define the vtable
@@IInterface segment dword
vtblIInterface:
dd offset IInterface@QueryInterface
dd offset IInterface@AddRef
dd offset IInterface@Release
dd offset IInterface@GetValue
dd offset IInterface@SetValue
@@IInterface ends
; define the interface's IID
; {CF2504E0-4F89-11d3-9AC3-0000E82C0301}
IID_IInterface GUID <0cf2504e0h, 04f89h, 011d3h, <09ah, 0c3h, 00h, 00h, 0e8h, 02ch, 03h, 01h>>
;-----------------------------------------------------------------------------
.code
start:
StartProc proc
LOCAL pif:DWORD ; interface pointer
; call the SetValue method
mov eax, [pif]
mov eax, [eax]
invoke (IInterfaceVtbl ptr [eax]).SetValue, [pif], 12345h
; call the GetValue method
mov eax, [pif]
mov eax, [eax]
invoke (IInterfaceVtbl ptr [eax]).GetValue, [pif]
; release the object
mov eax, [pif]
mov eax, [eax]
invoke (IInterfaceVtbl ptr [eax]).Release, [pif]
ret
StartProc endp
;-----------------------------------------------------------------------------
IInterface@QueryInterface proc uses ebx pif:DWORD, riid:DWORD, ppv:DWORD
invoke IsEqualGUID, [riid], addr IID_IInterface
test eax,eax
jnz @F
invoke IsEqualGUID, [riid], addr IID_IUnknown
test eax,eax
jnz @F
jmp @Error
@@:
GETOBJECTPOINTER Object, interface, pif
lea eax, (Object ptr [eax]).interface
; set *ppv
mov ebx, [ppv]
mov dword ptr [ebx], eax
; increment the reference count
GETOBJECTPOINTER Object, interface, pif
mov eax, (Object ptr [eax]).interface
invoke (IInterfaceVtbl ptr [eax]).AddRef, [pif]
; return S_OK
mov eax, S_OK
jmp return
@Error:
; error, interface not supported
mov eax, [ppv]
mov dword ptr [eax], 0
mov eax, E_NOINTERFACE
return:
ret
IInterface@QueryInterface endp
IInterface@AddRef proc pif:DWORD
GETOBJECTPOINTER Object, interface, pif
inc [(Object ptr [eax]).nRefCount]
mov eax, [(Object ptr [eax]).nRefCount]
ret
IInterface@AddRef endp
IInterface@Release proc pif:DWORD
GETOBJECTPOINTER Object, interface, pif
dec [(Object ptr [eax]).nRefCount]
mov eax, [(Object ptr [eax]).nRefCount]
or eax, eax
jnz @1
; free object
mov eax, [pif]
mov eax, [eax]
invoke LocalFree, eax
@1:
ret
IInterface@Release endp
IInterface@GetValue proc pif:DWORD
GETOBJECTPOINTER Object, interface, pif
mov eax, (Object ptr [eax]).nValue
ret
IInterface@GetValue endp
IInterface@SetValue proc uses ebx pif:DWORD, val:DWORD
GETOBJECTPOINTER Object, interface, pif
mov ebx, eax
mov eax, [val]
mov (Object ptr [ebx]).nValue, eax
ret
IInterface@SetValue endp
;-----------------------------------------------------------------------------
CreateObject proc uses ebx ecx pobj:DWORD
; set *ppv to 0
mov eax, pobj
mov dword ptr [eax], 0
; allocate object
invoke LocalAlloc, LMEM_FIXED, sizeof Object
or eax, eax
jnz @1
; alloc failed, so return
mov eax, E_OUTOFMEMORY
jmp return
@1:
mov ebx, eax
mov (Object ptr [ebx]).interface.lpVtbl, offset vtblIInterface
mov (Object ptr [ebx]).nRefCount, 0
mov (Object ptr [ebx]).nValue, 0
; Query the interface
lea ecx, (Object ptr [ebx]).interface
mov eax, (Object ptr [ebx]).interface.lpVtbl
invoke (IInterfaceVtbl ptr [eax]).QueryInterface,
ecx,
addr IID_IInterface,
[pobj]
cmp eax, S_OK
je return
; error in QueryInterface, so free memory
push eax
invoke LocalFree, ebx
pop eax
return:
ret
CreateObject endp
;-----------------------------------------------------------------------------
IsEqualGUID proc rguid1:DWORD, rguid2:DWORD
cld
mov esi, [rguid1]
mov edi, [rguid2]
mov ecx, sizeof GUID / 4
repe cmpsd
xor eax, eax
or ecx, ecx
setz eax
ret
IsEqualGUID endp
end start
Conclusion
------------------------------------------------------------------------------
We have (hopefully) seen how to implement a COM object. We can see that it
is a bit messy to do, and adds quite some overhead to our programs. However,
it can also add great flexibility and power to our programs. For more
information on this subject, i have set up a small COM page on my site:
http://lordlucifer.cjb.net
Remember that COM defines only interfaces, and implementation is left to the
programmer. This article presents only one possible implementation. This is
not the only method, nor is it the best one. The reader should feel free to
experiment with other methods.