Statistics

Members: 1927
News: 293
Web Links: 1
Visitors: 3926808

Who's Online

We have 3 guests online
Damn Vulnerable LinuxDamn Vulnerable Linux (DVL) is a Linux-based (modified Damn Small Linux) tool for IT-Security & IT-Anti- Security and Attack & Defense. [CLICK HERE FOR MORE INFOS! ]

Featured Conference Video

T16-Recon2006-Joe_Stewart-OllyBonE.gif OllyBone - Semi-Automatic Unpacking on IA-32. View the conference video here!
Home arrow Articles - Black Hat Methods arrow Assembly arrow Accessing COM Objects from Assembly
Accessing COM Objects from Assembly
User Rating: / 1
PoorBest 
Written by Ernest Murphy   


The COM (Component Object Model) is used by the Windows Operation system in increasing ways. For example, the shell.dll uses COM to access some of its API methods. The IShellLink and IPersistFile interfaces of the shell32.dll will be demonstrated to create a shortcut shell link. A basic understanding of COM is assumed. The code sample included is MASM specific.

 

 

Introduction

COM may seem complicated with its numerous details, but in use these complications disappear into simple function calls. The hardest part is understanding the data structures involved so you can define the interfaces.

I apologize for all the C++ terminology used in here. While COM is implementation neutral, it borrows much terminology from C++ to define itself.

In order to use the COM methods of some object, you must first instance or create that object from its coclass, then ask it to return you a pointer to it's interface. This process is performed by the API function CoCreateInstance. When you are done with the interface you call it's Release method, and COM and the coclass will take care of unloading the coclass.

Assessing COM Methods

To use COM methods you need to know before hand what the interface looks like. Even if you "late bind" through an IDispatch interface, you still need to know what IDispatch looks like.

An COM interface is just table of pointers to functions. Let's start with the IUnknown interface. If you were to create a component that simply exports the IUnknown interface, you have a fully functional COM object (albeit on the level of "Hello World"). IUnknown has the 3 basic methods of every interface, since all interfaces inherit from IUnknown. Keep in mind all an interface consists of is a structure of function pointers. For IUnknown, it looks like this:

IUnknown STRUCT DWORD

; IUnknown methods

    QueryInterface                  IUnknown_QueryInterface                 ?
AddRef                          IUnknown_AddRef                         ?
Release                         IUnknown_Release                        ?
IUnknown                ENDS

That's it, just 12 bytes long. It holds 3 DWORD pointers to the procedures that actually implement the methods. It is the infamous "vtable" you may have heard of. The pointers are defined as such so we can have MASM do some type checking for us when compiling our calls.

Since the vtable holds the addresses of functions, or pointers, these pointers are typedefed in our interface definition as such:

IUnknown_QueryInterface                 typedef ptr IUnknown_QueryInterfaceProto
IUnknown_AddRef                         typedef ptr IUnknown_AddRefProto
IUnknown_Release                        typedef ptr IUnknown_ReleaseProto

Finally, we define the function prototypes as follows:

IUnknown_QueryInterfaceProto            typedef PROTO :DWORD, :DWORD, :DWORD
IUnknown_AddRefProto                    typedef PROTO :DWORD
IUnknown_ReleaseProto                   typedef PROTO :DWORD

In keeping with the MASM32 practice of "loose" type checking, function parameters are just defined as DWORDs. Lots of work to set things up, but it does keeps lots of errors confined to compile time, not run time. In practice, you can wrap up your interface definitions in include files and keep them from cluttering up your source code.

One rather big compilation on defining an interface: MASM cannot resolve forward references like this, so we have to define them backwards, by defining the function prototype typedefs first, and the interface table last. The sample program later on defines the interfaces this way.

To actually use an interface, you need a pointer to it. The CoCreateInstance API can be used to return us this indirect pointer to an interface structure. It is one level removed from the vtable itself, and actually points to the "object" that holds the interface. (This would be clearer had I been creating the interface instead of using one. Please wait for a future article for that). The place this pointer points to in the object points to the interface structure. Thus, this pointer is generically named "ppv", for "pointer to pointer to (void)," where (void) means an unspecified type.

For example, say we used CoCreateInstance and successfully got an interface pointer ppv, and wanted to see if it supports some other interface. We can call its QueryInterface method and request a new ppv to the other interface we are interested in. Such a call would look like this:

mov eax, ppv            ; get pointer to the object
mov edx, [eax]          ; and use it to find the interface structure
; and then call that method
invoke (IUnknown PTR [edx]).QueryInterface, ppv, 
ADDR IID_SomeOtherInterface, ADDR ppv_new

I hope you find this as wonderfully simple as I do. IID_SomeOtherInterface holds the GUID of the interface we desire, and ppv_new is a new pointer we can use to access it. Also note we must pass in the pointer we used, this lets the interface know which object (literally "this" object) we are using.

Incidentally, in a previous APJ article on COM, there was an error in how a COM interface is invoked. THIS was left out of the COM call. The program seemed to work, because the COM invoke was invoked from the main code, not from a procedure, and did not require a return call before calling ExitProcess. Had this COM invoke been done from a procedure, a stack error crash would have resulted.

Note the register must be type cast (IUnknown PTR [edx]). This lets the compiler know what structure to use to get the correct offset in the vtable for the .QueryInterface function (in this case it means an offset of zero from edx). Actually, the information contained by the interface name and function name called disappear at compile time, all that is left is a numeric offset from an as of yet value unspecified pointer.

We can simplify a COM invoke further with a macro:

coinvoke MACRO pInterface:REQ, Interface:REQ, Function:REQ, args:VARARG

        LOCAL istatement, arg
;; invokes an arbitrary COM interface 
;; pInterface    pointer to a specific interface instance
;; Interface     the Interface's struct typedef
;; Function      which function or method of the interface to perform
;; args          all required arguments 
;;                   (type, kind and count determined by the function)
istatement TEXTEQU <invoke (Interface PTR[eax]).&Function, pInterface>
FOR arg, <args>
; build the list of parameter arguments
istatement CATSTR istatement, <, >, <&arg>
ENDM
mov eax, pInterface
mov eax, [eax]
istatement

ENDM

Thus, the same QueryInterface method as before can be invoked in a single line:

coinvoke ppv ,IUnknown, QueryInterface,

ADDR IID_SomeOtherInterface, ADDR ppnew

The return parameter for every COM call is an hResult, a 4 byte return value in eax. It is used to signal success or failure. Since the most significant digit is used to indicate failure, you can test the result with a simple:

.IF !SIGN?

; function passed
.ELSE

; function failed
.ENDIF

Again, this can be simplified with some more simple macros:

    SUCCEEDED    TEXTEQU     <!!SIGN?>
FAILED      TEXTEQU     <!!SUCCEEDED>

(The not ! sign must be doubled since that symbol has special meaning in MASM macros)

That's about all you need to fully invoke and use interfaces from COM objects from assembly. These techniques work with any COM or activeX object.

Back to the Real Word: Using IShellFile and IPersistFile from shell32.dll


The shell32.dll provides a simple, easy way to make shell links (shortcuts). However, it uses a COM interface to provide this service. The sample below is based on the MSDN "Shell Links" section for "Internet Tools and Technologies." This may be a strange place to find documentation, but there it is.

The "Shell Links" article may be found at {http://msdn.microsoft.com/library/psdk/shellcc/shell/Shortcut.htm}

For this tutorial we will access the following members of the IShellLink and the IPersistFile interfaces. Note every interface includes a "ppi" interface parameter, this is the interface that we calling to (it is the THIS parameter). (The following interface information is a copy of information published by Microsoft)

IShellLink::QueryInterface, ppi, ADDR riid, ADDR ppv * riid: The identifier of the interface requested. To get access to the * ppv: The pointer to the variable that receives the interface. Description: Checks if the object also supports the requested interface. If so, assigns the ppv pointer with the interface's pointer.

IShellLink::Release, ppi
Description: Decrements the reference count on the IShellLink interface.

IShellLink:: SetPath, ppi, ADDR szFile
* pszFile: A pointer to a text buffer containing the new path for the shell link object.
Description: Defines where the file the shell link points to.

IShellLink::SetIconLocation, ppi, ADDR szIconPath, iIcon * pszIconPath: A pointer to a text buffer containing the new icon path. * iIcon: An index to the icon. This index is zero based. Description: Sets which icon the shelllink will use.

IPersistFile::Save, ppi, ADDR szFileName, fRemember * pszFileName: Points to a zero-terminated string containing the absolute path of the file to which the object should be saved. * fRemember: Indicates whether the pszFileName parameter is to be used as the current working file. If TRUE, pszFileName becomes the current file and the object should clear its dirty flag after the save. If FALSE, this save operation is a "Save A Copy As ..." operation. In this case, the current file is unchanged and the object should not clear its dirty flag. If pszFileName is NULL, the implementation should ignore the fRemember flag. Description: Perform a save operation for the ShellLink object, or saves the shell link are creating.

IPersistFile::Release, ppi
Description: Decrements the reference count on the IPersistFile interface.

These interfaces contain many many more methods (see the full interface definitions in the code below), but we only need concentrate on those we will actually be using.

A shell link is the MS-speak name for a shortcut icon. The information contained in a link (.lnk) file is:

1 - The file path and name of the program to shell.

2 - Where to obtain the icon to display for the shortcut (usually from the

          executable itself), and which icon in that file to use. We will use 
the first icon in the file
3 - A file path and name where the shortcut should be stored.

The use of these interfaces is simple and straightforward. It goes like this:

  • Call CoCreateInstance CLSID_ShellLink for a IID_IShellLink interface
  • Queryinterface IShellLink for an IID_IPersistFile interface.
  • Call IShellLink.SetPath to specify where the shortcut target is
  • Call IShellLink.SetIconLocation to specify which icon to use
  • Call IPersistFile.Save to save our new shortcut .lnk file.
  • Call IPersistFile.Release
  • Call IShellLink.Release

The last two steps will releases our hold on these interfaces, which will automatically lead to the dll that supplied them being unloaded.

Again, the hard part in this application was finding documentation. What finally found broke the search open was using Visual Studio "Search in Files" to find "IShellLink" and " IPersistFile" in the /include area of MSVC. This lead me to various .h files, from which I hand translated the interfaces from C to MASM.

Another handy tool I could have used is the command line app "FindGUID.exe," which looks through the registry for a specific interface name or coclass, or will output a list of every class and interface with their associated GUIDs. Finally, the OLEView.exe application will let you browse the registry type libraries and mine them for information. However, these tools come with MSVC and are proprietary.

Take care when defining an interface. Missing vtable methods lead to strange results. Essentially COM calls, on one level, amount to "perform function (number)" calls. Leave a method out of the vtable definition and you call the wrong interface. The original IShellLink interface definition I used from a inc file I downloaded had a missing function. The calls I made generated a "SUCEEDED" hResult, but in some cases would not properly clean the stack (since my push count did not match the invoked function's pop count), thus lead to a GPF as I exited a procedure. Keep this in mind if you ever get similar "weird" results.

MakeLink.asm, a demonstration of COM


This program does very little, as all good tutorial programs should. When run, it creates a shortcut to itself, in the same directory. It can be amusing to run from file explorer and watch the shortcut appear. Then you can try the shortcut and watch it's creation time change.

The shell link tutorial code follows. It begins with some "hack code" to get the full file name path of the executable, and also makes a string with the same path that changes the file to "Shortcut To ShellLink.lnk" These strings are passed to the shell link interface, and it is saved (or persisted in COM-speak).

The CoCreateLink procedure used to actually perform the COM methods and perform this link creation has been kept as general as possible, and may have reuse possibilities in other applications.

;--------------------------------------------------------------------- ; MakeLink.asm ActiveX simple client to demonstrate basic concepts ; written & (c) copyright April 5, 2000 by Ernest Murphy ;
; contact the author at { This e-mail address is being protected from spam bots, you need JavaScript enabled to view it } ;

;               may be reused for any educational or
;               non-commercial application without further license

;--------------------------------------------------------------------- .386
.model flat, stdcall
option casemap:none

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\ole32.inc

includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\ole32.lib

;--------------------------------------------------------------------- CoCreateLink PROTO :DWORD, :DWORD

;--------------------------------------------------------------------- ; Interface definitions

; IUnknown Interface

IUnknown_QueryInterfaceProto            typedef PROTO :DWORD, :DWORD, :DWORD
IUnknown_AddRefProto                    typedef PROTO :DWORD
IUnknown_ReleaseProto                   typedef PROTO :DWORD
IUnknown_QueryInterface                 typedef ptr IUnknown_QueryInterfaceProto
IUnknown_AddRef                         typedef ptr IUnknown_AddRefProto
IUnknown_Release                        typedef ptr IUnknown_ReleaseProto
IUnknown                STRUCT DWORD
; IUnknown methods
QueryInterface                      IUnknown_QueryInterface             ?
AddRef                              IUnknown_AddRef                     ?
Release                             IUnknown_Release                    ?
IUnknown                ENDS

; IShellLink Interface
IShellLink_IShellLink_GetPathProto typedef PROTO :DWORD, :DWORD, :DWORD, :DWORD, :DWORD

IShellLink_GetIDListProto              typedef PROTO :DWORD, :DWORD
IShellLink_SetIDListProto              typedef PROTO :DWORD, :DWORD
IShellLink_GetDescriptionProto      typedef PROTO :DWORD, :DWORD, :DWORD
IShellLink_SetDescriptionProto      typedef PROTO :DWORD, :DWORD
IShellLink_GetWorkingDirectoryProto     typedef PROTO :DWORD, :DWORD, :DWORD
IShellLink_SetWorkingDirectoryProto     typedef PROTO :DWORD, :DWORD
IShellLink_GetArgumentsProto       typedef PROTO :DWORD, :DWORD, :DWORD
IShellLink_SetArgumentsProto       typedef PROTO :DWORD, :DWORD
IShellLink_GetHotkeyProto           typedef PROTO :DWORD, :DWORD
IShellLink_SetHotkeyProto           typedef PROTO :DWORD, :WORD 
IShellLink_GetShowCmdProto          typedef PROTO :DWORD, :DWORD
IShellLink_SetShowCmdProto          typedef PROTO :DWORD, :DWORD
IShellLink_GetIconLocationProto     typedef PROTO :DWORD, :DWORD, :DWORD, :DWORD
IShellLink_SetIconLocationProto     typedef PROTO :DWORD, :DWORD, :DWORD
IShellLink_SetRelativePathProto     typedef PROTO :DWORD, :DWORD, :DWORD
IShellLink_ResolveProto             typedef PROTO :DWORD, :DWORD, :DWORD
IShellLink_SetPathProto             typedef PROTO :DWORD, :DWORD
IShellLink_GetPath              typedef ptr IShellLink_IShellLink_GetPathProto  
IShellLink_GetIDList            typedef ptr IShellLink_GetIDListProto
IShellLink_SetIDList            typedef ptr IShellLink_SetIDListProto
IShellLink_GetDescription       typedef ptr IShellLink_GetDescriptionProto
IShellLink_SetDescription       typedef ptr IShellLink_SetDescriptionProto

IShellLink_GetWorkingDirectory typedef ptr IShellLink_GetWorkingDirectoryProto IShellLink_SetWorkingDirectory typedef ptr IShellLink_SetWorkingDirectoryProto

IShellLink_GetArguments         typedef ptr IShellLink_GetArgumentsProto
IShellLink_SetArguments         typedef ptr IShellLink_SetArgumentsProto
IShellLink_GetHotkey            typedef ptr IShellLink_GetHotkeyProto
IShellLink_SetHotkey            typedef ptr IShellLink_SetHotkeyProto
IShellLink_GetShowCmd           typedef ptr IShellLink_GetShowCmdProto
IShellLink_SetShowCmd           typedef ptr IShellLink_SetShowCmdProto
IShellLink_GetIconLocation      typedef ptr IShellLink_GetIconLocationProto
IShellLink_SetIconLocation      typedef ptr IShellLink_SetIconLocationProto
IShellLink_SetRelativePath      typedef ptr IShellLink_SetRelativePathProto
IShellLink_Resolve              typedef ptr IShellLink_ResolveProto
IShellLink_SetPath              typedef ptr IShellLink_SetPathProto
IShellLink              STRUCT DWORD
QueryInterface                      IUnknown_QueryInterface             ?
AddRef                              IUnknown_AddRef                     ?
Release                             IUnknown_Release                    ?
GetPath                             IShellLink_GetPath                  ?
GetIDList                           IShellLink_GetIDList                ?
SetIDList                           IShellLink_SetIDList                ?
GetDescription                      IShellLink_GetDescription           ?
SetDescription                      IShellLink_SetDescription           ?
GetWorkingDirectory                 IShellLink_GetWorkingDirectory      ?    
SetWorkingDirectory                 IShellLink_SetWorkingDirectory      ?
GetArguments                        IShellLink_GetArguments             ?
SetArguments                        IShellLink_SetArguments             ?
GetHotkey                           IShellLink_GetHotkey                ?
SetHotkey                           IShellLink_SetHotkey                ?
GetShowCmd                          IShellLink_GetShowCmd               ?
SetShowCmd                          IShellLink_SetShowCmd               ?
GetIconLocation                     IShellLink_GetIconLocation          ?
SetIconLocation                     IShellLink_SetIconLocation          ?
SetRelativePath                     IShellLink_SetRelativePath          ?
Resolve                             IShellLink_Resolve                  ?
SetPath                             IShellLink_SetPath                  ?
IShellLink              ENDS

; IPersistFile Interface

IPersistFile_GetClassIDProto        typedef PROTO :DWORD, :DWORD
IPersistFile_IsDirtyProto           typedef PROTO :DWORD
IPersistFile_LoadProto              typedef PROTO :DWORD, :DWORD, :DWORD
IPersistFile_SaveProto              typedef PROTO :DWORD, :DWORD, :DWORD
IPersistFile_SaveCompletedProto     typedef PROTO :DWORD, :DWORD 
IPersistFile_GetCurFileProto        typedef PROTO :DWORD, :DWORD
IPersistFile_GetClassID             typedef ptr IPersistFile_GetClassIDProto
IPersistFile_IsDirty                typedef ptr IPersistFile_IsDirtyProto
IPersistFile_Load                   typedef ptr IPersistFile_LoadProto
IPersistFile_Save                   typedef ptr IPersistFile_SaveProto
IPersistFile_SaveCompleted          typedef ptr IPersistFile_SaveCompletedProto
IPersistFile_GetCurFile             typedef ptr IPersistFile_GetCurFileProto
IPersistFile            STRUCT DWORD
QueryInterface                  IUnknown_QueryInterface         ?
AddRef                          IUnknown_AddRef                  ?
Release                         IUnknown_Release            ?
GetClassID                      IPersistFile_GetClassID         ?
IsDirty                         IPersistFile_IsDirty            ?
Load                            IPersistFile_Load               ?
Save                            IPersistFile_Save               ?
SaveCompleted                   IPersistFile_SaveCompleted      ?
GetCurFile                      IPersistFile_GetCurFile         ?
IPersistFile            ENDS

;--------------------------------------------------------------------- coinvoke MACRO pInterface:REQ, Interface:REQ, Function:REQ, args:VARARG

LOCAL istatement, arg
;; invokes an arbitrary COM interface ;; pInterface pointer to a specific interface instance

    ;; Interface     the Interface's struct typedef
;; Function      which function or method of the interface to perform
;; args          all required arguments 
;;                   (type, kind and count determined by the function)

istatement TEXTEQU <invoke (Interface PTR[eax]).&Function, pInterface> FOR arg, <args>

        ; build the list of parameter arguments
istatement CATSTR istatement, <, >, <&arg>

ENDM
mov eax, pInterface
mov eax, [eax]
istatement
ENDM

; equate primitives

SUCEEDED    TEXTEQU     <!!SIGN?>
FAILED      TEXTEQU     <!!SUCEEDED>

MakeMessage MACRO Text:REQ

; macro to display a message box
; the text to display is kept local to ; this routine for ease of use
LOCAL lbl
LOCAL sztext
jmp lbl
sztext:

db Text,0
lbl:

invoke MessageBox,NULL,sztext,ADDR szAppName,MB_OK ENDM

;--------------------------------------------------------------------- .data

szAppName BYTE "Shell Link Maker", 0

szLinkName        BYTE        "Shortcut to MakeLink.lnk", 0
szBKSlash             BYTE         "\", 0
hInstance         HINSTANCE   ?
Pos               DWORD       ?
szBuffer1           BYTE           MAX_PATH DUP(?)
szBuffer2           BYTE           MAX_PATH DUP(?)

;----------------------------------------------------------------------- .code
start:

;--------------------------------------------- ; this bracketed code is just a 'quick hack' ; to replace the filename from the filepathname ; with the 'Shortcut to' title
;

invoke GetModuleHandle, NULL
mov hInstance, eax

invoke GetModuleFileName, NULL, ADDR szBuffer1, MAX_PATH invoke lstrcpy, ADDR szBuffer2, ADDR szBuffer1 ; Find the last backslash '\' and change it to zero mov edx, OFFSET szBuffer2
mov ecx, edx
.REPEAT

        mov al, BYTE PTR [edx]
.IF al == 92 ; "\"
mov ecx, edx
.ENDIF
inc edx

.UNTIL al == 0
mov BYTE PTR [ecx+1], 0
invoke lstrcpy, ADDR szBuffer2, ADDR szLinkName ;----------------------------------------------

; here is where we call the proc with the COM methods

invoke CoInitialize, NULL
MakeMessage "Let's try our Createlink." invoke CoCreateLink, ADDR szBuffer1, ADDR szBuffer2 MakeMessage "That's all folks !!!"
invoke CoUninitialize
invoke ExitProcess, NULL

;----------------------------------------------------------------------- CoCreateLink PROC pszPathObj:DWORD, pszPathLink:DWORD ; CreateLink - uses the shell's IShellLink and IPersistFile interfaces ; to create and store a shortcut to the specified object. ; Returns the hresult of calling the member functions of the interfaces. ; pszPathObj - address of a buffer containing the path of the object. ; pszPathLink - address of a buffer containing the path where the ; shell link is to be stored.
; adapted from MSDN article "Shell Links" ; deleted useless "description" method ; added set icon location method

LOCAL pwsz :DWORD
LOCAL psl :DWORD
LOCAL ppsl :DWORD
LOCAL ppf :DWORD
LOCAL pppf :DWORD
LOCAL hResult :DWORD
LOCAL hHeap :DWORD

.data

CLSID_ShellLink     GUID        <0021401H, 0000H, 0000H,                  \
<0C0H, 00H, 00H, 00H, 00H, 00H, 00H, 046H>>
IID_IShellLink      GUID        <00214EEH, 0000H, 0000H,                  \ 
<0C0H, 00H, 00H, 00H, 00H, 00H, 00H, 046H>>
IID_IPersistFile    GUID        <000010BH, 0000H, 0000H,                  \ 
<0C0H, 00H, 00H, 00H, 00H, 00H, 00H, 046H>>

.code

; first, get some heap for a wide buffer invoke GetProcessHeap
mov hHeap, eax
invoke HeapAlloc, hHeap, NULL, MAX_PATH * 2 mov pwsz, eax
; and set up some local pointers (we can't use ADDR on local vars) lea eax, psl
mov ppsl, eax
lea eax, ppf
mov pppf, eax
; Get a pointer to the IShellLink interface. invoke CoCreateInstance, ADDR CLSID_ShellLink, NULL,

                             CLSCTX_INPROC_SERVER, 
ADDR IID_IShellLink, ppsl

mov hResult, eax
test eax, eax
.IF SUCEEDED

        ; Query IShellLink for the IPersistFile 
; interface for saving the shortcut
coinvoke psl, IShellLink, QueryInterface, ADDR IID_IPersistFile, pppf
mov hResult, eax
test eax, eax
.IF SUCEEDED 
; Set the path to the shortcut target 
coinvoke psl, IShellLink, SetPath, pszPathObj
mov hResult, eax
; add the  description. 
coinvoke psl, IShellLink, SetIconLocation, pszPathObj, 0 
; use first icon found
mov hResult, eax
; change string to Unicode. (COM typically expects Unicode strings)
invoke MultiByteToWideChar, CP_ACP, 0, pszPathLink, -1, pwsz, MAX_PATH
; Save the link by calling IPersistFile::Save
coinvoke ppf, IPersistFile, Save, pwsz, TRUE
mov eax, hResult
; release the IPersistFile ppf pointer
coinvoke ppf, IPersistFile, Release
mov hResult, eax
.ENDIF
; release the IShellLink psl pointer
coinvoke psl, IShellLink, Release
mov hResult, eax

.ENDIF
; free our heap space
invoke HeapFree, hHeap, NULL, pwsz
mov eax, hResult ; since we reuse this variable over and over,

; it contains the last operations result ret
CoCreateLink ENDP
;-----------------------------------------------------------

end start
;-----------------------------------------------------------------------

Bibliography

"Inside COM, Microsoft's Component Object Model" Dale Rogerson

Copyright 1997, Paperback - 376 pages CD-ROM edition Microsoft Press; ISBN: 1572313498 (THE fundamental book on understanding how COM works on a fundamental level. Uses C++ code to illustrate basic concepts as it builds simple fully functional COM object)

"Automation Programmer's Reference : Using ActiveX Technology to Create

Programmable Applications" (no author listed) Copyright 1997, Paperback - 450 pages Microsoft Press; ISBN: 1572315849
(This book has been available online on MSDN in the past, but it is cheap enough for those of you who prefer real books you can hold in your hand. Defines the practical interfaces and functions that the automation libraries provide you, but is more of a reference book then a "user's guide")

Microsoft Developers Network

{http://msdn.microsoft.com}

"Professional Visual C++ 5 ActiveX/Com Control Programming" Sing Li

and Panos Economopoulos
Copyright April 1997, Paperback - 500 pages (no CD, files available online) Wrox Press Inc; ISBN: 1861000375
(Excellent description of activeX control and control site interfaces. A recent review of this book on Amazon.com stated "These guys are the type that want to rewrite the world's entire software base in assembler." Need I say more?)

"sean's inconsequential homepage"

{http://www.eburg.com/~baxters/}
Various hardcore articles on low-level COM and ATL techniques. Coded in C++

"Using COM in Assembly Language" Bill Tyler

Assembly Language Journal, Apr-June 99 Mr Tyler keeps a web site at:
{http://thunder.prohosting.com/~asm1/}