home *** CD-ROM | disk | FTP | other *** search
- 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.