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 >
Text File  |  2000-05-25  |  18KB  |  563 lines

  1. COM in ASM - Part 2
  2. ------------------------------------------------------------------------------
  3.  
  4. My previous atricle described how to use COM objects in your assembly
  5. language programs.  It described only how to call COM methods, but not how to
  6. create your own COM objects.  This article will describe how to do that.
  7.  
  8. This article will describe implementing COM Objects, using MASM syntax.  TASM
  9. or NASM assemblers will not be considered, however the methods can be easily
  10. applied to any assembler.  
  11.  
  12. This article will also not describe some of the more advanced features of COM
  13. such as reuse, threading, servers/clients, and so on.  These will presented
  14. in future articles.
  15.  
  16.  
  17. COM Interfaces Review
  18. ------------------------------------------------------------------------------
  19.  
  20. An interface definition specifies the interface's methods, their return types, 
  21. the number and types of their parameters, and what the methods must do.  Here 
  22. is a sample interface definition:
  23.  
  24. IInterface struct
  25.     lpVtbl  dd  ?
  26. IInterface ends
  27.  
  28. IInterfaceVtbl struct
  29.     ; IUnknown methods
  30.     STDMETHOD       QueryInterface, :DWORD, :DWORD, :DWORD
  31.     STDMETHOD       AddRef, :DWORD
  32.     STDMETHOD       Release, :DWORD
  33.     ; IInterface methods
  34.     STDMETHOD       Method1, :DWORD
  35.     STDMETHOD       Method2, :DWORD
  36. IInterfaceVtbl ends
  37.  
  38. STDMETHOD is used to simplify the interface declaration, and is defined as:
  39.  
  40. STDMETHOD MACRO name, argl :VARARG
  41.     LOCAL @tmp_a
  42.     LOCAL @tmp_b
  43.     @tmp_a TYPEDEF PROTO argl
  44.     @tmp_b TYPEDEF PTR @tmp_a
  45.     name @tmp_b ?
  46. ENDM
  47.  
  48. This macro is used to greatly simplify interface declarations, and so that the
  49. MASM invoke syntax can be used. (Macro originally by Ewald :)
  50.  
  51. Access to the interface's methods occurs through a pointer.  This pointer
  52. points to a table of function pointers, called a vtable. Here is a sample 
  53. method call:
  54.  
  55. mov     eax, [lpif]                            ; lpif is the interface pointer
  56. mov     eax, [eax]                             ; get the address of the vtable
  57. invoke  (IInterfaceVtbl [eax]).Method1, [lpif] ; indirect call to the function
  58. - or -
  59. invoke  [eax][IInterfaceVtbl.Method2], [lpif]  ; alternate notation
  60.  
  61. Two different styles of addressing the members are shown.  Both notations
  62. produce equivalent code, so the method used is a matter of personal
  63. preference.
  64.  
  65. All interfaces must inherit from the IUnknown interface.  This means that the
  66. first 3 methods of the vtable must be QueryInterface, AddRef, and Release.
  67. The purpose and implementation of these methods will be discussed later.
  68.  
  69.  
  70. GUIDS
  71. ------------------------------------------------------------------------------
  72.  
  73. A GUID is a Globally Unique ID.  A GUID is a 16-byte number, that is unique 
  74. to an interface.  COM uses GUID's to identify different interfaces from one
  75. another.  Using this method prevents name clashing as well as version
  76. clashing.  To get a GUID, you use a generator utility that is included with
  77. most win32 development packages.  
  78.  
  79. A GUID is represented by the following structure:
  80.  
  81. GUID STRUCT
  82.     Data1   dd ?
  83.     Data2   dw ?
  84.     Data3   dw ?
  85.     Data4   db 8 dup(?)
  86. GUID ENDS
  87.  
  88. A GUID is then defined in the data section:
  89. MyGUID GUID <3F2504E0h, 4f89h, 11D3h, <9Ah, 0C3h, 0h, 0h, 0E8h, 2Ch, 3h, 1h>>
  90.  
  91. Once a GUID is assigned to an interface and published, no furthur changes to 
  92. the interface definition are allowed.  Note, that this does mean that the 
  93. interface implementation may not change, only the definition.  For changes
  94. to the interface definition, a new GUID must be assigned.
  95.  
  96.  
  97. COM Objects
  98. ------------------------------------------------------------------------------
  99.  
  100. A COM object is simply an implementation of an interface.  Implentation 
  101. details are not covered by the COM standard, so we are free to implement our
  102. objects as we choose, so long as they satisfy all the requirements of the 
  103. interface definition.  
  104.  
  105. A typical object will contain pointers to the various interfaces it supports, 
  106. a reference count, and any other data that the object needs.  Here is a sample
  107. object definition, implemented as a structure:
  108.  
  109. Object struct
  110.     interface   IInterface  <?>     ; pointer to an IInterface
  111.     nRefCount   dd          ?       ; reference count
  112.     nValue      dd          ?       ; private object data
  113. Object ends
  114.  
  115. We also have to define the vtable's we are going to be using.  These tables
  116. must be static, and cannot change during run-time.  Each member of the vtable
  117. is a pointer to a method.  Following is a method for defining the vtable. 
  118.  
  119. @@IInterface segment dword
  120. vtblIInterface:
  121.     dd      offset IInterface@QueryInterface
  122.     dd      offset IInterface@AddRef
  123.     dd      offset IInterface@Release
  124.     dd      offset IInterface@GetValue
  125.     dd      offset IInterface@SetValue
  126. @@IInterface ends
  127.  
  128.  
  129. Reference Counting
  130. ------------------------------------------------------------------------------
  131.  
  132. COM object manage their lifetimes through reference counting.  Each object
  133. maintains a reference count that keeps track of how many instances of the
  134. interface pointer have been created.  The object is required to keep a 
  135. counter that supports 2^32 instances, meaning the reference count must be a
  136. DWORD.  
  137.  
  138. When the reference count drops to zero, the object is no longer in use, and
  139. it destroys itself.  The 2 IUnknown methods AddRef and Release handle the
  140. reference counting for a COM object.
  141.  
  142.  
  143. QueryInterface
  144. ------------------------------------------------------------------------------
  145.  
  146. The QueryInterface method is used by a COM object to determine if the object
  147. supports a given interface, and then if supported, to get the interface 
  148. pointer.  There are 3 rules to implementing the QueryInterface method:
  149.    
  150.     1. Objects must have an identity - a call to QueryInterface must always
  151.        return the same pointer value.
  152.     2. The set of interfaces of an object must never change - for example, if
  153.        a call to QueryInterface with on IID succeeds once, it must succeed 
  154.        always.  Likewise, if it fails once, it must fail always.
  155.     3. It must be possible to successfully query an interface of an object
  156.        from any other interface.
  157.  
  158. QueryInterface returns a pointer to a specified interface on an object to 
  159. which a client currently holds an interface pointer. This function must call 
  160. the AddRef method on the pointer it returns. 
  161.  
  162. Following are the QueryInterface parameters:
  163.     pif  : [in] a pointer to the calling interface
  164.     riid : [in] pointer to the IID of the interface being queried
  165.     ppv  : [out] pointer to the pointer of the interface that is to be set.  
  166.            If the interface is not supported, the pointed to value is set to 0
  167.  
  168. QueryInterface returns the following:
  169.    S_OK if the interface is supported
  170.    E_NOINTERFACE if not supported
  171.  
  172. Here is a simple assembly implementation of QueryInterface:
  173.  
  174. IInterface@QueryInterface proc uses ebx pif:DWORD, riid:DWORD, ppv:DWORD
  175.     ; The following compares the requested IID with the available ones.  
  176.     ; In this case, because IInterface inherits from IUnknown, the IInterface
  177.     ; interface is prefixed with the IUnknown methods, and these 2 interfaces
  178.     ; share the same interface pointer.
  179.     invoke  IsEqualGUID, [riid], addr IID_IInterface
  180.     or      eax,eax
  181.     jnz     @1
  182.     invoke  IsEqualGUID, [riid], addr IID_IUnknown
  183.     or      eax,eax
  184.     jnz     @1
  185.     jmp     @NoInterface
  186.     
  187. @1:     
  188.     ; GETOBJECTPOINTER is a macro that will put the object pointer into eax, 
  189.     ; when given the name of the object, the name of the interface, and the
  190.     ; interface pointer.
  191.     GETOBJECTPOINTER    Object, interface, pif    
  192.  
  193.     ; now get the pointer to the requested interface
  194.     lea     eax, (Object ptr [eax]).interface
  195.     
  196.     ; set *ppv with this interface pointer
  197.     mov     ebx, [ppv]
  198.     mov     dword ptr [ebx], eax
  199.     
  200.     ; increment the reference count by calling AddRef
  201.     GETOBJECTPOINTER    Object, interface, pif
  202.     mov     eax, (Object ptr [eax]).interface
  203.     invoke  (IInterfaceVtbl ptr [eax]).AddRef, pif
  204.     
  205.     ; return S_OK
  206.     mov     eax, S_OK
  207.     jmp     return
  208.  
  209. @NoInterface:
  210.     ; interface not supported, so set *ppv to zero
  211.     mov     eax, [ppv]
  212.     mov     dword ptr [eax], 0
  213.     
  214.     ; return E_NOINTERFACE
  215.     mov     eax, E_NOINTERFACE
  216.         
  217. return: 
  218.     ret
  219. IInterface@QueryInterface endp
  220.  
  221.  
  222. AddRef
  223. ------------------------------------------------------------------------------
  224.  
  225. The AddRef method is used to increment the reference count for an interface
  226. of an object.  It should be called for every new copy of an interface pointer
  227. to an object.  
  228.  
  229. AddRef takes no parameters, other than the interface pointer required for all
  230. methods.  AddRef should return the new reference count.  However, this value
  231. is to be used by callers only for testing purposes, as it may be unstable in
  232. certain situations.
  233.  
  234. Following is a simple implementation of the AddRef method:
  235.  
  236. IInterface@AddRef proc pif:DWORD
  237.     GETOBJECTPOINTER    Object, interface, pif
  238.     ; increment the reference count
  239.     inc     [(Object ptr [eax]).nRefCount]
  240.     ; now return the count
  241.     mov     eax, [(Object ptr [eax]).nRefCount]
  242.     ret
  243. IInterface@AddRef endp
  244.  
  245.  
  246. Release
  247. ------------------------------------------------------------------------------
  248.  
  249. Release decrements the reference count for the calling interface on a object. 
  250. If the reference count on the object is decrememnted to 0, then the object is 
  251. freed from memory.  This function should be called when you no longer need to 
  252. use an interface pointer
  253.  
  254. Like AddRef, Release takes only one parameter - the interface pointer.  It
  255. also returns the current value of the reference count, which, similarly, is to
  256. be used for testing purposess only
  257.  
  258. Here is a simple implementation of Release:
  259.  
  260. IInterface@Release proc pif:DWORD
  261.     GETOBJECTPOINTER    Object, interface, pif
  262.  
  263.     ; decrement the reference count
  264.     dec     [(Object ptr [eax]).nRefCount]  
  265.     
  266.     ; check to see if the reference count is zero.  If it is, then destroy
  267.     ; the object.
  268.     mov     eax, [(Object ptr [eax]).nRefCount]
  269.     or      eax, eax
  270.     jnz     @1
  271.  
  272.     ; free the object: here we have assumed the object was allocated with
  273.     ; LocalAlloc and with LMEM_FIXED option
  274.     GETOBJECTPOINTER    Object, interface, pif
  275.     invoke  LocalFree, eax
  276. @1:
  277.     ret
  278. IInterface@Release endp
  279.  
  280.  
  281. Creating a COM object
  282. ------------------------------------------------------------------------------
  283.  
  284. Creating an object consisits basically of allocating the memory for the 
  285. object, and then initializeing its data members.  Typically, the vtable 
  286. pointer is initialized and the reference count is zeroed.  QueryInterface 
  287. could then be called to get the interface pointer.
  288.  
  289. Other methods exist for creating objects, such as using CoCreateInstance, and
  290. using class factories.  These methods will not be discussed, and may be a 
  291. topic for a future article.
  292.  
  293.  
  294. COM implementatiion sample application
  295. ------------------------------------------------------------------------------
  296.  
  297. Here follows a sample implementation and usage of a COM object.  It shows how
  298. to create the object, call its methods, then free it.  It would probably be 
  299. very educational to assemble this and run it through a debugger.  
  300.  
  301.  
  302. .386
  303. .model flat,stdcall
  304.  
  305. include windows.inc
  306. include kernel32.inc 
  307. include user32.inc  
  308.  
  309. includelib kernel32.lib       
  310. includelib user32.lib   
  311. includelib uuid.lib
  312.  
  313. ;-----------------------------------------------------------------------------
  314.  
  315. ; Macro to simply interface declarations
  316. ; Borrowed from Ewald, http://here.is/diamond/
  317. STDMETHOD   MACRO   name, argl :VARARG
  318. LOCAL @tmp_a
  319. LOCAL @tmp_b
  320. @tmp_a  TYPEDEF PROTO argl
  321. @tmp_b  TYPEDEF PTR @tmp_a
  322. name    @tmp_b      ?
  323. ENDM
  324.  
  325. ; Macro that takes an interface pointer and returns the implementation 
  326. ; pointer in eax
  327. GETOBJECTPOINTER MACRO Object, Interface, pif
  328.     mov     eax, pif
  329.     IF (Object.Interface)
  330.         sub     eax, Object.Interface
  331.     ENDIF
  332. ENDM
  333.  
  334. ;-----------------------------------------------------------------------------
  335.  
  336. IInterface@QueryInterface   proto :DWORD, :DWORD, :DWORD
  337. IInterface@AddRef           proto :DWORD
  338. IInterface@Release          proto :DWORD
  339. IInterface@Get              proto :DWORD
  340. IInterface@Set              proto :DWORD, :DWORD
  341.  
  342. CreateObject                proto :DWORD
  343. IsEqualGUID                 proto :DWORD, :DWORD
  344.  
  345. externdef                   IID_IUnknown:GUID
  346.  
  347. ;-----------------------------------------------------------------------------
  348.  
  349. ; declare the interface prototype
  350. IInterface struct
  351.     lpVtbl  dd  ?
  352. IInterface ends
  353.  
  354. IInterfaceVtbl struct
  355.     ; IUnknown methods
  356.     STDMETHOD       QueryInterface, pif:DWORD, riid:DWORD, ppv:DWORD
  357.     STDMETHOD       AddRef, pif:DWORD
  358.     STDMETHOD       Release, pif:DWORD
  359.     ; IInterface methods
  360.     STDMETHOD       GetValue, pif:DWORD
  361.     STDMETHOD       SetValue, pif:DWORD, val:DWORD
  362. IInterfaceVtbl ends
  363.  
  364.  
  365. ; declare the object structure
  366. Object struct
  367.     ; interface object
  368.     interface   IInterface  <?>
  369.  
  370.     ; object data
  371.     nRefCount   dd          ?
  372.     nValue      dd          ?
  373. Object ends
  374.  
  375. ;-----------------------------------------------------------------------------
  376.  
  377. .data
  378. ; define the vtable
  379. @@IInterface segment dword
  380. vtblIInterface:
  381.     dd      offset IInterface@QueryInterface
  382.     dd      offset IInterface@AddRef
  383.     dd      offset IInterface@Release
  384.     dd      offset IInterface@GetValue
  385.     dd      offset IInterface@SetValue
  386. @@IInterface ends
  387.  
  388. ; define the interface's IID
  389. ; {CF2504E0-4F89-11d3-9AC3-0000E82C0301}
  390. IID_IInterface GUID <0cf2504e0h, 04f89h, 011d3h, <09ah, 0c3h, 00h, 00h, 0e8h, 02ch, 03h, 01h>>
  391.  
  392. ;-----------------------------------------------------------------------------
  393.  
  394. .code
  395. start:
  396. StartProc proc  
  397.     LOCAL   pif:DWORD       ; interface pointer
  398.  
  399.     ; call the SetValue method
  400.     mov     eax, [pif]
  401.     mov     eax, [eax]
  402.     invoke  (IInterfaceVtbl ptr [eax]).SetValue, [pif], 12345h
  403.     
  404.     ; call the GetValue method
  405.     mov     eax, [pif]
  406.     mov     eax, [eax]
  407.     invoke  (IInterfaceVtbl ptr [eax]).GetValue, [pif]
  408.     
  409.     ; release the object
  410.     mov     eax, [pif]
  411.     mov     eax, [eax]
  412.     invoke  (IInterfaceVtbl ptr [eax]).Release, [pif]
  413.  
  414.     ret
  415. StartProc endp
  416.  
  417. ;-----------------------------------------------------------------------------
  418.     
  419. IInterface@QueryInterface proc uses ebx pif:DWORD, riid:DWORD, ppv:DWORD
  420.     invoke  IsEqualGUID, [riid], addr IID_IInterface
  421.     test    eax,eax
  422.     jnz     @F
  423.     invoke  IsEqualGUID, [riid], addr IID_IUnknown
  424.     test    eax,eax
  425.     jnz     @F
  426.     jmp     @Error
  427.     
  428. @@:     
  429.     GETOBJECTPOINTER    Object, interface, pif
  430.     lea     eax, (Object ptr [eax]).interface
  431.     
  432.     ; set *ppv
  433.     mov     ebx, [ppv]
  434.     mov     dword ptr [ebx], eax
  435.     
  436.     ; increment the reference count
  437.     GETOBJECTPOINTER    Object, interface, pif
  438.     mov     eax, (Object ptr [eax]).interface
  439.     invoke  (IInterfaceVtbl ptr [eax]).AddRef, [pif]
  440.     
  441.     ; return S_OK
  442.     mov     eax, S_OK
  443.     jmp     return
  444.  
  445. @Error:
  446.     ; error, interface not supported
  447.     mov     eax, [ppv]
  448.     mov     dword ptr [eax], 0
  449.     mov     eax, E_NOINTERFACE
  450.         
  451. return: 
  452.     ret
  453. IInterface@QueryInterface endp
  454.  
  455.  
  456. IInterface@AddRef proc pif:DWORD
  457.     GETOBJECTPOINTER    Object, interface, pif
  458.     inc     [(Object ptr [eax]).nRefCount]
  459.     mov     eax, [(Object ptr [eax]).nRefCount]
  460.     ret
  461. IInterface@AddRef endp
  462.  
  463.  
  464. IInterface@Release proc pif:DWORD
  465.     GETOBJECTPOINTER    Object, interface, pif
  466.     dec     [(Object ptr [eax]).nRefCount]  
  467.     mov     eax, [(Object ptr [eax]).nRefCount]
  468.     or      eax, eax
  469.     jnz     @1
  470.     ; free object
  471.     mov     eax, [pif]
  472.     mov     eax, [eax]
  473.     invoke  LocalFree, eax
  474. @1:
  475.     ret
  476. IInterface@Release endp
  477.  
  478.  
  479. IInterface@GetValue proc pif:DWORD
  480.     GETOBJECTPOINTER    Object, interface, pif
  481.     mov     eax, (Object ptr [eax]).nValue
  482.     ret
  483. IInterface@GetValue endp
  484.  
  485.  
  486. IInterface@SetValue proc uses ebx pif:DWORD, val:DWORD
  487.     GETOBJECTPOINTER    Object, interface, pif
  488.     mov     ebx, eax
  489.     mov     eax, [val]
  490.     mov     (Object ptr [ebx]).nValue, eax  
  491.     ret
  492. IInterface@SetValue endp
  493.  
  494. ;-----------------------------------------------------------------------------
  495.  
  496. CreateObject proc uses ebx ecx pobj:DWORD
  497.     ; set *ppv to 0
  498.     mov     eax, pobj
  499.     mov     dword ptr [eax], 0
  500.  
  501.     ; allocate object
  502.     invoke  LocalAlloc, LMEM_FIXED, sizeof Object
  503.     or      eax, eax
  504.     jnz     @1
  505.     ; alloc failed, so return
  506.     mov     eax, E_OUTOFMEMORY
  507.     jmp     return
  508. @1: 
  509.  
  510.     mov     ebx, eax
  511.     mov     (Object ptr [ebx]).interface.lpVtbl, offset vtblIInterface
  512.     mov     (Object ptr [ebx]).nRefCount, 0
  513.     mov     (Object ptr [ebx]).nValue, 0
  514.  
  515.     ; Query the interface
  516.     lea     ecx, (Object ptr [ebx]).interface
  517.     mov     eax, (Object ptr [ebx]).interface.lpVtbl
  518.     invoke  (IInterfaceVtbl ptr [eax]).QueryInterface, 
  519.             ecx, 
  520.             addr IID_IInterface, 
  521.             [pobj]
  522.     cmp     eax, S_OK
  523.     je      return
  524.  
  525.     ; error in QueryInterface, so free memory
  526.     push    eax     
  527.     invoke  LocalFree, ebx
  528.     pop     eax
  529.     
  530. return:
  531.     ret
  532. CreateObject endp
  533.  
  534. ;-----------------------------------------------------------------------------
  535.  
  536. IsEqualGUID proc rguid1:DWORD, rguid2:DWORD
  537.     cld
  538.     mov     esi, [rguid1]
  539.     mov     edi, [rguid2]
  540.     mov     ecx, sizeof GUID / 4
  541.     repe    cmpsd
  542.     xor     eax, eax
  543.     or      ecx, ecx
  544.     setz    eax
  545.     ret
  546. IsEqualGUID endp
  547.  
  548. end start
  549.  
  550.  
  551. Conclusion
  552. ------------------------------------------------------------------------------
  553.  
  554. We have (hopefully) seen how to implement a COM object.  We can see that it
  555. is a bit messy to do, and adds quite some overhead to our programs.  However,
  556. it can also add great flexibility and power to our programs.  For more 
  557. information on this subject, i have set up a small COM page on my site: 
  558. http://lordlucifer.cjb.net
  559.  
  560. Remember that COM defines only interfaces, and implementation is left to the
  561. programmer.  This article presents only one possible implementation.  This is
  562. not the only method, nor is it the best one.  The reader should feel free to 
  563. experiment with other methods.