please dont rip this site

Language Asm Masmwin Com COM_2.TXT

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.

file: /Techref/language/asm/masmwin/com/com_2.txt, 17KB, , updated: 1999/10/8 16:03, local time: 2024/3/28 06:38,
TOP NEW HELP FIND: 
44.203.235.24:LOG IN

 ©2024 These pages are served without commercial sponsorship. (No popup ads, etc...).Bandwidth abuse increases hosting cost forcing sponsorship or shutdown. This server aggressively defends against automated copying for any reason including offline viewing, duplication, etc... Please respect this requirement and DO NOT RIP THIS SITE. Questions?
Please DO link to this page! Digg it! / MAKE!

<A HREF="http://techref.massmind.org/Techref/language/asm/masmwin/com/com_2.txt"> language asm masmwin com com_2</A>

Did you find what you needed?

 

Welcome to massmind.org!

 

Welcome to techref.massmind.org!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  .