Revised Dec 26 2000 for inclusion as part of MASM32
Revised July 10 2000 for the new form of coinvoke.
Sample code for this article is available at ...\COM\examples\shortcut
Abstract:
原理:
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.
COM may seem complicated with it's 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 deleting the object and unloading the coclass.
A COM object is referred to as the SERVER. The program that calls up a COM object so it may use it is referred to as the CLIENT.
COM对象被称为SERVER(服务器)。调用COM对象的程序被称为CLIENT(客户)
Assessing COM Methods
访问COM函数
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. A 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:
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:
Finally, we define the function prototypes as follows:
最后,我们如下定义函数原型:
QueryInterface_Proto typedef PROTO :DWORD, :DWORD, :DWORD
AddRef_Pointer typedef PROTO :DWORD
Release_Pointer 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, we will wrap up these interface definitions in include files and keep them from cluttering up the 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 include files for the example 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. The final structure looks like this:
CoCreateInstance
There is a lot of indirection using this structure, it can drive you batty trying to write code to properly reference and de-reference these elements. Macros to simplify this task will be defined.
When the client makes a call to the COM library to create a COM object, it passes in the address where it wants the object pointer to be placed. This initial pointer is generically referred to as "ppv," from the C++ speak "pointer to pointer to (void)," where (void) means an unspecified type. It holds the address of another pointer ("pv"), and this pointer refers to a whole table of pointers, one table entry for each function of the interface.
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 (ppv2, pointer to an Interface) to the other interface (pIID, pointer to a Interface Identifying GUID) we are interested in. In C, QueryInterface has a prototype that would look like so:
; get pointer to the object
mov eax, ppv
; and use it to find the interface structure
mov edx, [eax]
; push the function parameters onto the stack
push OFFSET ppv2
push OFFSET IID_ISomeOtherInterface
push dword ppv
; and then call that method
call dword ptr [eax + 0]
This may be accomplished using the built-in MASM 'invoke' macro as such:
这个可以使用MASM内建的“invoke”宏来完成,就像这样:
; get pointer to the object
mov eax, ppv
; and use it to find the interface structure
mov edx, [eax]
; and then call that method
invoke (IUnknown PTR [edx]).IUnknown_QueryInterface, ppv,
ADDR IID_SomeOtherInterface, ADDR ppv_new
I hope you find this as wonderfully simple as I do.
我希望你能和我一样发现这些相当简单。
Note we must pass in the pointer we used, this lets the interface know which object (literally "this" object) we are using.
注意我们必须传递我们使用的指针,这个使得接口知道哪个对象(严格的说,“这个”对象)我们正在使用。
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.
One more semi-obscure point. Notice I changed the interface method name from simply "QueryInterface" to "IUnknown_QueryInterface". This is a bit of name decoration I've found necessary. When you get to larger COM projects with many similar interfaces you will run into a problem, that is different interfaces with identical method names. This is quite valid, in fact it's called polymorphism, but can confuse the compiler a bit.
Without this name decoration scheme things will be safe until you have two different interfaces with identical method names but different parameters to that method. This is more common then you might first think, but just consider how many interfaces might have a PRINT method.
We can simplify a COM invoke further with a macro. This coinvoke macro is part of the oaidl.inc file.
我们可以通过宏来进一步简化COM的调用。这个coinvoke宏是oaidl.inc文件的一部分。
;---------------------------------------------------------------------
; coinvoke MACRO
;
; invokes an arbitrary COM interface
;
; revised 12/29/00 to check for edx as a param and force compilation error
; (thanks to Andy Car for a how-to suggestion)
; revised 7/18/00 to pass pointer in edx (not eax) to avoid confusion with
; parmas passed with ADDR (Jeremy Collake's excellent suggestion)
; revised 5/4/00 for member function name decoration
; see http://ourworld.compuserve.com/homepages/ernies_world/coinvoke.htm
;
; 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)
;
coinvoke MACRO pInterface:REQ, Interface:REQ, Function:REQ, args:VARARG
LOCAL istatement, arg
FOR arg, ;; run thru args to see if edx is lurking in there
IFIDNI <&arg>,
.ERR
ENDIF
ENDM
istatement CATSTR ,<_>,<&Function, pInterface>
IFNB ;; add the list of parameter arguments if any
istatement CATSTR istatement, <, >, <&args>
ENDIF
mov edx, pInterface
mov edx, [edx]
istatement
ENDM
;---------------------------------------------------------------------
Thus, the same QueryInterface method as before can be invoked in a single line:
Note that now the name decoration is done for us by the macro.
注意同样的名字装饰由宏来替我们完成了。
The only 'gotcha' (well, the most obvious) is that no parameters to a COM call should be passed in edx as this register is used to handle 'this' the object reference. Using edx as a parameter will generate a compile error.
Using IShellFile and IPersistFile from shell32.dll
使用shell32.dll中IShellFile和IPersistFile
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."
shell32.dll提供了一个简单容易的办法来创建快捷方式。然而,它使用COM接口来提供这项服务。下面的例子是基于MSDN的“Internet Tools and Technologies”的“Shell Links”部分。
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,
signs 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:
shell link是快捷方式的微软说法。包含在一个link(.lnk)文件中的信息有:
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.
QueryInterface IShellLink for an IID_IPersistFile interface.
QueryInterface IShellLink得到IID_IPersistFile接口。
Call IShellLink.SetPath to specify where the shortcut target is
调用IShellLink.SetPath 来指定快捷方式指向的目标。
Call IShellLink.SetIconLocation to specify which icon to use
调用IShellLink.SetIconLocation来指定使用哪个图标。
Call IPersistFile.Save to save our new shortcut .lnk file.
调用IPersistFile.Save来保存我们的新的快捷方式.lnk文件。
finally,
最后
Call IPersistFile.Release
调用IPersistFile.Release
Call IShellLink.Release
调用IShellLink.Release
This 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 "SUCCEEDED" 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.
This program does very little, as a good tutorial program 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 is in ...\COM\examples\shortcut. 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).
快捷方式的示例代码位于...\COM\examples\shortcut。它由一些为了获得可执行文件的文件名的完整路径的“hack code”开始,而且用相同的路径创建了一个字符串。它把文件改变为"Shortcut To ShellLink.lnk"。这几个字符串被传递给shell link接口,而且它被保存了(用COM的语言,被持久化了)。
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.
This program is similar to earlier published tutorial, but has been edited for some additional clarity. The interfaces are defined in a separate include file to reduce clutter. It may be built in MASM32 by using the ...\COM\bin\BLDDLL.BAT file supplied.
Additional note: Iczelion has quite a useful file in his tutorials named resource.h. It is quite useful when using rc.exe to compile resource files. I use it so much I have moved it to my /masm32/include/ folder. You need to either move your copy there, or change the path in the rsrc.rc file to build it properly.
(THE book for 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 Rom, 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?)
Various hardcore articles on low-level COM and ATL techniques. Coded in C++
"Using COM in Assembly Language" Bill Tyler
http://thunder.prohosting.com/~asm1/
Assembly Language Journal, Apr-June 99
代码
MakeLink.asm
;---------------------------------------------------------------------
; MakeLink.asm ActiveX simple client to demonstrate basic concepts
; written & (c) copyright April 5, 2000 by Ernest Murphy
;
; contact the author at [email protected]
;
; 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
include \masm32\com\include\oaidl.inc
include \masm32\com\include\shlobj.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\ole32.lib
;---------------------------------------------------------------------
CoCreateLink PROTO :DWORD, :DWORD
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
; IPersistFile Interface
IPersistFile STRUCT DWORD
IPersistFile_QueryInterface comethod3 ?
IPersistFile_AddRef comethod1 ?
IPersistFile_Release comethod1 ?
IPersistFile_GetClassID comethod2 ?
IPersistFile_IsDirty comethod1 ?
IPersistFile_Load comethod3 ?
IPersistFile_Save comethod3 ?
IPersistFile_SaveCompleted comethod2 ?
IPersistFile_GetCurFile comethod2 ?
IPersistFile ENDS
;---------------------------------------------------------------------
.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.
; addapted from MSDN article "Shell Links"
; deleted useless "description" method
; added set icon location method
LOCAL pwsz :DWORD
LOCAL psl :DWORD
LOCAL ppf :DWORD
LOCAL hResult :DWORD
LOCAL hHeap :DWORD
.data
CLSID_ShellLink GUID sCLSID_ShellLink
IID_IShellLink GUID sIID_IShellLink
IID_IPersistFile GUID {00000010bH, 00000H, 00000H, \
{0C0H, 000H, 000H, 000H, 000H, 000H, 000H, 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
; Get a pointer to the IShellLink interface.
invoke CoCreateInstance, ADDR CLSID_ShellLink, NULL,
CLSCTX_INPROC_SERVER,
ADDR IID_IShellLink, ADDR psl
mov hResult, eax
test eax, eax
.IF SUCCEEDED
; Query IShellLink for the IPersistFile
; interface for saving the shortcut
coinvoke psl, IShellLink, QueryInterface, ADDR IID_IPersistFile, ADDR ppf
mov hResult, eax
test eax, eax
.IF SUCCEEDED
; Set the path to the shortcut target
coinvoke psl, IShellLink, SetPath, pszPathObj
mov hResult, eax
; add the description, use first icon found
coinvoke psl, IShellLink, SetIconLocation, pszPathObj, 0
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
rsrc.rc
// resource.h may be found in Iczelion's tut #10-1
// it is well worth moving to your /masm32/include/ folder
#include "\masm32\include\resource.h"
#define IDC_ICON1 1001
#define IDC_ICON2 1002
IDI_ICON1 ICON MOVEABLE PURE LOADONCALL DISCARDABLE "PLANE.ICO"
IDI_ICON2 ICON MOVEABLE PURE LOADONCALL DISCARDABLE "TRFFC14.ICO"