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/}
|