3 whales of COM. The first whale: registry — Архив WASM.RU

Все статьи

3 whales of COM. The first whale: registry — Архив WASM.RU

Download materials for the article

This article was written a few years ago. Maybe it would still lie among other stuff on a CD if I didn't find it recently examining old material. Though I intended to write a series of three articles in due time and now there are only two ones (I don’t know will it be completed ever), and despite of some peoples' opinion that “COM is out of date now, up-to-date fancy is .NET ”, it seemed interesting to me myself when I had reread it, and I felt sorry that such a thing was getting lost. So I decided to make it available to everybody interested since it had been created already.

COM technology remains desired but too expencive and therefore inaccessible resource for many programmers. Just fleeting glance on all that conglomeration of objects, interfaces, rules of their interaction overburdened with implementation techniques in this or that programming language and cumbersome auxiliary constructions intended for "facilitation" of COM using can kill any desire to engage in this matter. And that lucky beggar who succeeded in making his or her way through all this maze and who began as though to use COM objects in his or her own programs often finds himself or herself quite helpless outside of his or her usual programming environment.

The author of this article turned out to be in somewhat similar situation in his time except that he just could not pass through all these helpfully made-up high-level constructions. Moving instead from the opposite side I discovered suddenly that seeming earlier inaccessible barrier begun to step back if one threw away a fairly good part of this overburdening trash. The problem with COM is that not the technology is difficult in itself or that it uses some unknown so far principles. Elements used are just ordinary and simple but there is a great number of them and they are mixed with a lot of other things not related to COM; all this hides and diffuses the essence of COM. In this article (or rather in series of three articles in compliance with the title) I'll try to describe my own experiments and the way I have passed in hope it will be helpful for somebody else.

It is presumed that the reader is familiar with the basic concepts of COM or at least has heard about them. You won't find here descriptions of component programming models, principles of encapsulation, polymorphism, inheritance etc. - unless of only incidentally mentioning. Instead you'll be presented with the low-level programming approach without excess theoretical abstractions - the "bottom view" onto COM architecture in a way. Maybe these articles will best fit to those who repeatedly began and gave up studying COM beeng not able to cope with abundance of heterogeneous information, but who kept desire to understand the essence and machinery of this technology, ant to those who worked with COM on high level but who wanted to look "under hood".

Several utilities created using MASM32 package are examined as examples. It is supposed that the reader is able to work with this package - issues of creating Win32 GUI applications won't be considered here. (Those who wish that can address to Iczelion's tutorials; specifically you must look through the sections about dialog boxes - 10 and 11. Moreover the source code in this article contains some mixture of high-level MASM constructions (.IF-.ELSE, INVOKE etc.) used for "ordinary" skeletons of Win32 applications and of quite low-level implementation of COM elements - for example, not even structures (not to mention macros) but a set of successive values is used in place of virtual tables. It is done intentionally to attract attention just to details relating to COM and to give a chance to "feel" them without drawing away to Win32 programming in general.

COM and registry

So, it's time to give up with preambles and pass on to our whales. One of the most important actively used bases without which COM infrastructure is impossible at all is a system database. The system registry plays this role on Windows platform. Before we advance further it's necessary to say a couple of words of warning.

Experiments with modification of registry values will be described here. You must remember to be utterly cautious making such operations because you may fall in troubles. If the registry becomes corrupted the computer will cease from booting and information on disks may turn out to be lost. Therefore it is strongly recommended to create emergency repair disk (if it wasn't created earlier) before manipulations with the registry, make a backup copy of all important data on hard disks (including email and an address book) as well as backup registry files (for Win9* these are USER.DAT and SYSTEM.DAT in the Windows folder) in a separate folder. It's more complicated for WinNT/2k/XP installed on a NTFS partition. For WinXP it's necessary to create at least one additional recovery point.

It's only the exraordinary minimum of information; the description of registry repair methods would require a separate article in itself. There are series of books devoted to Windows [98, NT, 2000, XP etc.] registries; these are the books that you should address to when necessary.

Coming back to our whales. Let's consider a few small examples. If you have installed MS Excel on your system you can create the following file with the .vbs extension and launch it:

Set x = CreateObject("Excel.Application")
x.Workbooks.Add
x.Cells(1,1).Value = 5
x.Cells(1,2).Value = 10
x.Cells(1,3).Value = 15
x.Range("A1:C1").Select
Set ch = x.Charts.Add()
x.Visible = True
ch.Type = -4100

If you have installed Word XP you can launch another .vbs file:

Set w = CreateObject("Word.Application")
w.Visible = True
Set rng = w.Document.Add.Range(0,0)
With rng
	.InsertBefore "Text for WordXP"
	.ParagraphFormat.Alignment = 1
	With .Font
		.Name = "Arial"
		.Size = 16
		.Color = 200
	End With
End With

If the control "Calendar" was installed when installing MS Office you can create and examine in your browser the following html-file:

<HTML>
<BODY>
<OBJECT CLASSID="CLSID:8E27C92B-1264-101C-8A2F-040224009C02">
</OBJECT>
</BODY>
</HTML>

[By the way, if this control is indeed installed on your computer you can see it here below if you are reading the article in IE ]

You probably have noticed the continual repetition of this "if". It's an integral feature of the component software: unfortunately, you can't be sure for 100% that the needed component will be present on a user's platform. Besides it's insufficient simply to copy files for loading components. The programs must be installed either by using setup or by separate registering of components or creating the necessary records in a registry by hand.

After that you can address components by their names. In our case these are "Excel.Application", "Word.Application", and "CLSID:8E27C92B-1264-101C-8A2F-040224009C02". All this and other COM/OLE/ActiveX related information is stored in registry under "HKEY_LOCAL_MACHINE\Software\CLASSES" key. This key is so important that an alias was created for it as the separate root key "HKEY_CLASSES_ROOT".

Fig. 1. Registry editor.
Fig. 1. Registry editor.

The sturcture of COM related information in the registry

Now it's time to launch the registry editor (by entering "regedit" in the command prompt; see fig. 1) and to begin to examine these questions in practice. COM related data are joined in 5 groups: ProgID, CLSID, TypeLib, Interface, AppID. The majority of them have their own registry keys. The root key "HKEY_CLASSES_ROOT" stands for ProgID section. Here we can find the names "Excel.Application" and "Word.Application" that we had used earlier in our scripts together with the remaining 4 COM sections and with file extensions.

ProgID is so called program identifier, it is in fact only "user friendly" label for the "real" name of a component - CLSID. It is accepted that ProgID cannot ensure the true global uniqueness of the component name especially in the distributed internetwork environment so in the latter case only CLSIDs must be used as component identifiers. But on a local machine ProgID is widely used especially in scripts. However the component's CLSID is used for identification of the component in any case; for that crossreferences are established between CLSID and ProgID keys for the component.

Let's examine the key "Excel.Application" for example. It has a subkey CLSID, the default value of which is the required CLSID in a string representation. It means that "HKEY_CLASSES_ROOT\CLSID" contains the corresponding key storing all necessary information for loading the component. Certainly you have paid attention to another key near "Excel.Application" but with a number at the end (the number may differ on various machines; it is "Excel.Application.5" on my machine, for example). The key itself contains another subkey - CurVer. In fact it's "Excel.Application.5" that is ProgID and "Excel.Application" is so called VersionIndependentProgId - "component identifier, independent from the component version" (exactly these subkeys for crossreferences you can find in "HKEY_CLASSES_ROOT\CLSID" key for the corresponding component). Thus it is possible not to indicate the specific version of an installed component; and if in the course of time the component was upgraded, old scripts would successively work with the new versions if they had used VersionIndependentProgIDs as object names. The COM system has auxiliary funcions CLSIDFromProgID and ProgIDFromCLSID for getting one key using another.

In order to better understand this theory we can have some fun (but not forgetting warnings above). Let's make sure that the "real" name of a component is actually CLSID. For that create a new key under "HKEY_CLASSES_ROOT" and designate it with your own name for variety. Then copy the CLSID value under ProgID "Excel.Application" and paste it as the default value for our newly created CLSID. And now rewrite our script a bit:

Set x = CreateObject("MyName")
x.Visible = True

Naturally instead of "MyName" you must paste the name chosen for your ProgID. If all Ok Excel now readily responds to the new name.

The utility for ProgID

Examining the registry you can find a great number of interesting thins. Howerer, manual retrieving data following crossreferences is very boring especially when too many components. Combining pleasantness with the profit let's create for our searches a small utility that will translate given ProgID of the component to its CLSID and vice versa.

Fig. 2. The user interface of COM_1 utility
Fig.2. The user interface of COM_1 utility

The user interface design of the utility is shown in fig.2. It's implemented in the following resource file (COM_1.rc):

#define DS_MODALFRAME       0x80L
#define DS_CENTER           0x0800L
#define WS_POPUP            0x80000000L
#define WS_CAPTION          0x00C00000L
#define WS_SYSMENU          0x00080000L
#define ES_AUTOHSCROLL      0x0080L

#define IDC_EDIT1           1000
#define IDC_EDIT2           1001
#define IDB_ProgID          1002
#define IDB_CLSID           1003
#define IDC_STATIC          -1

MyDialog DIALOG DISCARDABLE  200, 200, 200, 66
STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "ProgID <-> CLSID"
FONT 8, "MS Sans Serif"
BEGIN
    DEFPUSHBUTTON   "Get ProgID",IDB_ProgID,35,46,50,14
    PUSHBUTTON      "Get CLSID",IDB_CLSID,108,46,50,14
    LTEXT           "CLSID:",IDC_STATIC,7,7,26,12
    LTEXT           "ProgID:",IDC_STATIC,7,27,26,12
    EDITTEXT        IDC_EDIT1,38,7,154,12,ES_AUTOHSCROLL
    EDITTEXT        IDC_EDIT2,38,27,154,12,ES_AUTOHSCROLL
END

The program's source code is in the file COM_1.asm. The beginning is standard; COM API funcions are located in OLE32.dll so you must include files supporting this module.

.386
.model flat,stdcall
option casemap:none

DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD

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

.data

DlgName	db "MyDialog",0
App		db "ProgID_CLSID",0
ms1		db "Enter CLSID here",0
ms2		db "Enter ProgID here",0
ms3		db "There is no ProgID for this CLSID",0
ms4		db "There is no CLSID for this ProgID",0
Err1		db "The wrong CLSID",0

.data?

hInstance	HINSTANCE ?
hEdit1	DWORD ?		; edit control for CLSID
hEdit2	DWORD ?		; edit control for ProgID
buf		db 256 dup(?)	; for ANSI strings
wbuf		db 512 dup(?)	; for Unicode strings
cls		db 16 dup(?)	; buffer for CLSID
bufaddr	DWORD ?

.const

IDC_EDIT1	equ 1000	; edit control for CLSID
IDC_EDIT2	equ 1001	; edit control for ProgID
IDB_ProgID	equ 1002	; "Get ProgID" button
IDB_CLSID	equ 1003	; "Get CLSID" button

The utility is implemented as a modal dialog box created from resource template:

.code

start:
invoke GetModuleHandle, NULL
mov    hInstance,eax
invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, 
addr  DlgProc, NULL
invoke ExitProcess,eax

DlgProc proc USES esi edi ebx,hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM 
.IF uMsg==WM_INITDIALOG
	; store handles for edit controls
	invoke GetDlgItem,hWnd,IDC_EDIT1
	mov hEdit1,eax
	invoke GetDlgItem,hWnd,IDC_EDIT2
	mov hEdit2,eax
	invoke SetFocus,hEdit1
	mov eax,FALSE
	ret
.ELSEIF uMsg==WM_CLOSE
	invoke EndDialog,hWnd,0

The algorithm is like that. On pressing "Get ProgID" button edit control for ProgID is cleared and the CLSID control text is read.

.ELSEIF uMsg==WM_COMMAND
	mov eax,wParam
	mov edx,wParam
	shr edx,16
	.if dx==BN_CLICKED
		.if ax==IDB_ProgID	; convert CLSID into ProgID
			mov buf,0
			invoke SetWindowText,hEdit2,ADDR buf
			invoke GetWindowText,hEdit1,ADDR buf,255

If the string is empty a message suggesting to enter the text is displayed:

.if eax==0	; GetWindowText - the string is empty
	invoke MessageBox,0,ADDR ms1,ADDR App,0
	invoke SendMessage,hEdit1,EM_SETSEL,0,-1
	invoke SetFocus,hEdit1
.else		; we've got the text

A bit of surprise here - COM uses Unicode strings. So we'll have to convert the text in the buffer buf using MultiByteToWideChar function. This function takes 6 parameters:

  • code page (in our case - ANSI, the parameter is CP_ACP);
  • flags indicating how to translate precomposed or composite wide characters; we don't need it - 0;
  • address of string to map;
  • number of bytes in string; if -1 the string is null terminated and the length is calculated automatically;
  • address of wide-character buffer;
  • size of wide-character buffer.

	invoke MultiByteToWideChar,CP_ACP,0,ADDR buf,-1,ADDR wbuf,510

One more complication. The function ProgIDFromCLSID accepts as an argument the address of a structure with the numeric representation of CLSID and not with the string representation. In more detail we'll discuss this nuance in the section about CLSID and now to convert the string representation into numeric one we'll use one more auxiliary COM API function - CLSIDFromString (where cls is a 16 bytes buffer left for CLSID structure):

	invoke CLSIDFromString,ADDR wbuf,ADDR cls
	.if eax==NOERROR
		invoke ProgIDFromCLSID,ADDR cls,ADDR bufaddr
		.if eax==S_OK

In bufaddr we've got string representation of ProgID in Unicode format, now we need to convert it back to ANSI using the function WideCharToMultiByte ("twin" of MultiByteToWideChar). It takes 8 arguments:

  • code page we need to convert the Unicode string to; in our case - again CP_ACP;
  • performance and mapping flags. We don't need them, so leave 0;
  • address of Unicode string;
  • number of characters in Unicode string. If -1 the string is null-terminated and the length is calculated automatically;
  • address of buffer for the translated string;
  • size of buffer for the translated string;
  • address of default symbol - we don't need it, leave 0;
  • address of flags for default symbol - 0.

		invoke WideCharToMultiByte,CP_ACP,0,bufaddr,-1,ADDR buf,255,0,0
		invoke SetWindowText,hEdit2,ADDR buf
	.else	; couldn't convert  CLSID to ProgID -
		; issue message
		invoke SetWindowText,hEdit2,ADDR ms3
	.endif

Further the block for handling errors of function CLSIDFromString goes which are considered as a wrong format of an entered CLSID string:

	.else	; error of CLSIDFromString
		invoke MessageBox,0,ADDR Err1,ADDR App,MB_OK OR MB_ICONERROR
		invoke SendMessage,hEdit1,EM_SETSEL,0,-1
		invoke SetFocus,hEdit1
	.endif	;NOERROR - CLSIDFromString
.endif	;GetWindowText

The block handling pressing "Get ProgID" button is complete. Pressing "Get CLSID" button is handled in a similar way.

.elseif ax==IDB_CLSID	;convert ProgID in CLSID string
	mov buf,0
	invoke SetWindowText,hEdit1,ADDR buf
	invoke GetWindowText,hEdit2,ADDR buf,255
	.if eax==0			; no text in control - error message
		invoke MessageBox,0,ADDR ms2,ADDR App,0
		invoke SendMessage,hEdit2,EM_SETSEL,0,-1
		invoke SetFocus,hEdit2
	.else				; got ProgID text - convert to Unicode
		invoke MultiByteToWideChar,CP_ACP,0,ADDR buf,-1,ADDR wbuf,510
					; convert ProgID into numeric CLSID
		invoke CLSIDFromProgID,ADDR wbuf,ADDR cls
		.if eax==S_OK	; got CLSID,
					; convert into stirng form
			invoke StringFromCLSID,ADDR cls,ADDR bufaddr
					; convert Unicode string into ANSI
			invoke WideCharToMultiByte,CP_ACP,0,bufaddr,-1,
ADDR  buf,255,0,0
			invoke CoTaskMemFree,bufaddr	; free memory,
					; allocated in call to StringFromCLSID
			invoke SetWindowText,hEdit1,ADDR buf
		.else			; failed to get CLSID from ProgID
			invoke SetWindowText,hEdit1,ADDR ms4
		.endif		;S_OK (CLSIDFromProgID)
	.endif			;GetWindowText
.endif				;ax==IDB_ProgID

After that the processing of dialog box messages is over:

		.endif	;BN_CLICKED
	.ELSE
		mov eax,FALSE
		ret
	.ENDIF	;uMsg
       mov eax,TRUE
       ret
DlgProc endp

end start

The files with sources of this utility (COM_1.rc, COM_1.asm, and makefile) are located in COM_1 folder of ComKit1.rar. The makefile is written with the assumption that MASM32 is installed in \masm32 folder on a current disk; if not it's necessary to edit the makefile.

CLSID key

All primary information about components is stored under the registry key "HKEY_CLASSES_ROOT\CLSID". A separate subkey is created for each component, the subkey name being a string representation of component's CLSID. As was previously mentioned CLSID (like other GUIDs, for example, interface identifiers (IID) or type libraries) has two representations: a numeric one and a string one. The numeric form is presented as a 128-bit value. Theoretically this value could be stored, say, in a XMM register; however COM was created in time of 16-bit machines when nobody knew about any XMM-registers. So the following structure was used for representation of this 128-bit value:

GUID	STRUCT
    Data1 	dd ?
    Data2 	dw ?
    Data3 	dw ?
    Data4 	db 8 dup(?)
GUID ENDS

But in the registry a string of the following format is used for representation of GUID:

{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}

where x stands for a 16-bit digit (0-F), and braces and hyphens must be present in place without any delimiting spaces. Actually such a record can cause confusion: note that the block of digits for Data4 field in the string representation is divided into two parts of 4 and 12 hexadecimal digits, and the part with 4 digits may be confused with the previous WORD-values. The first DWORD and the two following WORD values are recorded in the string representation as usual numbers, i.e. high bytes go first, whereas the third "WORD" (the first two bytes of Data4 field) is recorded in the "little-endian" order - the low byte goes first.

As we've already seen in the first utility source the functions CLSIDFromString and StringFromCLSID are used for converting CLSID from the string representation into the numeric one and vice versa correspondingly. There are choices for other GUID types - StringFromIID, IIDFromString, StringFromGUID2.

CLSID key stores in turn a number of subkeys. We know already two of them - ProgID and VersionIndependentProgID. It's worth mentioning only that the presence of ProgID for a component is not mandatory.

The simple universal COM client

Before we advance in examining CLSID section we'll need one more utility - a universal COM client. Its user interface is showed on fig.3. This client allows to load COM object with CLSID entered in the first edit control and to acquire the interface pointer with IID entered in the second edit control. The server type is indicated with a set of checkboxes (it's possible to check more than one type at once). IUnknown button allows to display the IID of the often used interface with the same name in the second edit control.

Fig.3. The user interface of COM_2 utility
Fig.3. The user interface of COM_2

Here's the corresponding resource file (COM_2.rc):

#include "\masm32\include\resource.h"

#define IDC_EDIT1		1001
#define IDC_EDIT2		1002
#define IDC_CHECK1	1003
#define IDC_CHECK2	1004
#define IDC_CHECK3	1005
#define IDC_CHECK4	1006
#define IDC_BUTTON1	1007
#define IDC_STATIC	-1

MyDialog DIALOGEX 0, 0, 214, 90
STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Simple COM client"
FONT 8, "MS Sans Serif"
BEGIN
    LTEXT           "CLSID:",IDC_STATIC,7,10,26,8,0,WS_EX_RIGHT
    LTEXT           "IID:",IDC_STATIC,7,28,26,8,0,WS_EX_RIGHT
    GROUPBOX        "Server type:",IDC_STATIC,7,45,142,38
    EDITTEXT        IDC_EDIT1,40,7,167,14,ES_AUTOHSCROLL
    EDITTEXT        IDC_EDIT2,40,26,167,14,ES_AUTOHSCROLL
    CONTROL         "InProc server",IDC_CHECK1,"Button",BS_AUTOCHECKBOX | 
                    WS_TABSTOP,15,55,58,10
    CONTROL         "InProc handler",IDC_CHECK2,"Button",BS_AUTOCHECKBOX | 
                    WS_TABSTOP,15,68,62,10
    CONTROL         "Local server",IDC_CHECK3,"Button",BS_AUTOCHECKBOX | 
                    WS_TABSTOP,80,55,55,10
    CONTROL         "Remoute server",IDC_CHECK4,"Button",BS_AUTOCHECKBOX | 
                    WS_TABSTOP,80,68,66,10
    PUSHBUTTON      "IUnknown",IDC_BUTTON1,157,49,50,14
    DEFPUSHBUTTON   "Connect",IDOK,157,67,50,14
END

So as not to write out standard defines every time it's advisable to copy "resource.h" from the 10th lesson of Iczelion into "Include" folder of MASM32 and then put into rc-file the proper reference as it's done in this case.

The implementation is in COM_2.asm:

.386
.model flat,stdcall
option casemap:none

DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD

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

.data

DlgName	db "MyDialog",0
app		db "SimpleClient",0
ms1		db "Enter CLSID here",0
ms2		db "Enter IID here",0
szUnk		db "{00000000-0000-0000-C000-000000000046}",0
mscheck	db "The server type must be indicated",0
msclsid	db "Enter CLSID here",0
msiid		db "Enter IID here",0
msok	db "Got interface pointer to:",13,10,"CLSID:",9,"%s",13,10,"IID:",9,"%s",0
mserr		db "CoCreateInstance failed:",13,10,"HRESULT==%Xh",0
err1		db "The wrong CLSID",0
err2		db "The wrong IID",0

.data?

hInstance	HINSTANCE ?
hEdit1	DWORD ?		; "CLSID"
hEdit2	DWORD ?		; "IID"
hCheck1	DWORD ?		; "Inproc server"
hCheck2	DWORD ?		; "Inproc handler"
hCheck3	DWORD ?		; "Local server"
hCheck4	DWORD ?		; "Remoute server"
ctx		DWORD ?		; server type
szclsid	db 64 dup(?)	; buffer for CLSID string
sziid		db 64 dup(?)	; buffer for IID string
buf		db 256 dup(?)	; for ANSI strings
wbuf		db 512 dup(?)	; for Unicode strings
cls		db 16 dup(?)	; CLSID of component
iid		db 16 dup(?)	; IID of called interface
pUnk		DWORD ?		; ptr to IUnknown interface

.const

IDC_EDIT1	equ 1001	; "CLSID"
IDC_EDIT2	equ 1002	; "IID"
IDC_CHECK1	equ 1003	; "Inproc server"
IDC_CHECK2	equ 1004	; "Inproc handler"
IDC_CHECK3	equ 1005	; "Local server"
IDC_CHECK4	equ 1006	; "Remoute server"
IDC_BUTTON1	equ 1007	; "IUnknown"

.code

start:
	invoke GetModuleHandle, NULL
	mov    hInstance,eax
	invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL
	invoke ExitProcess,eax

DlgProc proc USES esi edi ebx,hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM 
.IF uMsg==WM_INITDIALOG
	; store handles of controls
	invoke GetDlgItem,hWnd,IDC_EDIT1
	mov hEdit1,eax
	invoke GetDlgItem,hWnd,IDC_EDIT2
	mov hEdit2,eax
	invoke GetDlgItem,hWnd,IDC_CHECK1
	mov hCheck1,eax
	invoke GetDlgItem,hWnd,IDC_CHECK2
	mov hCheck2,eax
	invoke GetDlgItem,hWnd,IDC_CHECK3
	mov hCheck3,eax
	invoke GetDlgItem,hWnd,IDC_CHECK4
	mov hCheck4,eax
	invoke SetFocus,hEdit1

Since this time we need to enable the COM library, it's also necessary to call the function CoInitialize. In the end of the program it's necessary to call CoUninitialize correspondingly.

	invoke CoInitialize,0
	mov eax,FALSE
	ret
.ELSEIF uMsg==WM_CLOSE
	invoke CoUninitialize
	invoke EndDialog,hWnd,0

The core of this utility is made up of CoCreateInstance function that makes all work - locates the proper COM object, loads it, requests the indicated interface and returns a pointer to it. The bulk of code handling WM_COMMAND collects data for CoCreateInstance for passing as its arguments and checks the entered values. CoCreateInstance takes 5 arguments:

  • address of structure containing CLSID of the requested object (we get this value in the first edit control);
  • pointer to the so called outer IUnknown in the case if an object is being created as part of an aggregate. If not (as in our case) the value is 0;
  • context for running code: flags indicating whether an object server is inprocess, local, remoute or it is an inprocess handler (this value is calculated basing on checking the state of checkboxes);
  • address of structure containing IID of the interface which pointer we want to get to. The IID value we get in the second edit contol;
  • address of pointer variable that receives the requested interface pointer (or 0, if the interface is not implemented).

When pressing IUnknown button IID of the corresponding interface is copied into the second edit control (this value is hardcoded as szUnk string). So, here's handling of commands:

.ELSEIF uMsg==WM_COMMAND
	mov eax,wParam
	mov edx,wParam
	shr edx,16
	.if dx==BN_CLICKED
		.if ax==IDC_BUTTON1		; button 'IUnknown'
invoke SetWindowText,hEdit2,offset szUnk
		.elseif ax==IDOK		; button 'Connect'
						; get CLSID
invoke GetWindowText,hEdit1,offset szclsid,64
.if eax==0					; no text - display message
	invoke MessageBox,0,offset msclsid,offset app,MB_ICONEXCLAMATION
	invoke SendMessage,hEdit1,EM_SETSEL,0,-1
	invoke SetFocus,hEdit1
	mov eax,TRUE
	ret
.else						; got text - translate into ANSI and
						; convert to numeric form, storing in cls
invoke MultiByteToWideChar,CP_ACP,0,offset szclsid,-1,offset wbuf,510
	invoke CLSIDFromString,offset wbuf,offset cls
	.if eax!=NOERROR			; error of CLSIDFromString is considered
						; as wrong format of entered CLSID string
		invoke MessageBox,0,offset err1,offset app,MB_ICONERROR
		invoke SendMessage,hEdit1,EM_SETSEL,0,-1
		invoke SetFocus,hEdit1
		mov eax,TRUE
		ret
	.endif				; NOERROR - CLSIDFromString
.endif					; GetWindowText (hEdit1)

						; get IID
invoke GetWindowText,hEdit2,offset sziid,64
.if eax==0	; no text - issue message
	invoke MessageBox,0,offset msiid,offset app,MB_ICONEXCLAMATION
	invoke SendMessage,hEdit2,EM_SETSEL,0,-1
	invoke SetFocus,hEdit2
	mov eax,TRUE
	ret
.else						; got IID text, translate into ANSI, 
						; then convert to numeric form, storing in iid
	invoke MultiByteToWideChar,CP_ACP,0,offset sziid,-1,offset wbuf,510
	invoke IIDFromString,offset wbuf,offset iid
	.if eax!=NOERROR			; error of IIDFromString is considered as
						; wrong format of entered IID string
		invoke MessageBox,0,offset err2,offset app,MB_ICONERROR
		invoke SendMessage,hEdit2,EM_SETSEL,0,-1
		invoke SetFocus,hEdit2
		mov eax,TRUE
		ret
	.endif				; NOERROR - IIDFromString
.endif					; GetWindowText (hEdit2)

		; chech the state of checkboxes and determine the type of server,
		; storing the calculated value in ctx
mov ctx,0
invoke IsDlgButtonChecked,hWnd,IDC_CHECK1
.if eax==BST_CHECKED
	or ctx,CLSCTX_INPROC_SERVER
.endif
invoke IsDlgButtonChecked,hWnd,IDC_CHECK2
.if eax==BST_CHECKED
	or ctx,CLSCTX_INPROC_HANDLER
.endif
invoke IsDlgButtonChecked,hWnd,IDC_CHECK3
.if eax==BST_CHECKED
	or ctx,CLSCTX_LOCAL_SERVER
.endif
invoke IsDlgButtonChecked,hWnd,IDC_CHECK4
.if eax==BST_CHECKED
	or ctx,CLSCTX_REMOTE_SERVER
.endif
.if ctx==0				; no server type is selected - display message
	invoke MessageBox,0,offset mscheck,offset app,MB_ICONEXCLAMATION
	invoke SetFocus,hCheck1
	mov eax,TRUE
	ret
.endif

Now the necessary parameters are collected: CLSID is in cls, IID - in iid, the server type - in ctx. CoCreateInstance locates COM object with the given CLSID and instantiates it, requesting pointer to IID on it. If the object implements an interface with the given IID, it places the pointer to it in a variable, address of which was passed to CoCreateInstance as the last parameter, and the function itself returns S_OK, If the object doesn't implement this interface or an object with the given CLSID isn't registered in the registry, or if the registered server type isn't indicated in the flag of server type, an error message is returned.

invoke CoCreateInstance,offset cls,0,ctx,offset iid,offset pUnk
mov ecx,eax
.if eax==S_OK	; The object is successively created and a pointer to the 
		; requested interface is returned in  pUnk. Issue message 
		; with CLSID of the object and IID of the interface
	invoke wsprintf,offset buf,offset msok,offset szclsid,offset sziid
	invoke MessageBox,0,offset buf,offset app,MB_ICONINFORMATION

At this place all "useful" work of our application is up - we free the object. It's necessary to do it using COM facilities, i.e. using obtained interface pointer. For that it's necessary to call a Release method of the IUnknown interface using the obtained interface pointer (pUnk). Absolutely all COM interfaces are inherited from IUnknown, therefore the Release method may be called by means of the same technique for all COM objects.

A pointer to an object interface is an address of a structure storing data related to the state of an object instance. Only indirect access to this data is possible by calling methods of the proper interface so each method receives an address of this structure (a so called "this" pointer in terms of C++ language) as the first parameter. It's possible to say about this structure only that its first field stores a pointer to another structure - "virtual table" - containing addresses of functions implementing methods of the interface. To call this or that method one needs to find an address of its implementation. The address of the method is stored in the virtual table with the well-known and constant offset. Machinery of this process we'll examine in more detail in the next article and now just the code:

	mov eax,pUnk
	push eax		; ptr to object instance ("this")
	mov eax,[eax]		; get ptr to virtual table,
	call dword ptr [eax+8]	; then ptr to the third function
				; in this table (Release) and call it

.else			; error of CoCreateInstance
	invoke wsprintf,offset buf,offset mserr,ecx
	invoke MessageBox,0,offset buf,offset app,MB_ICONERROR
.endif			; eax==S_OK (CoCreateInstance)

At this place handling of WM_COMMAND and of any other messages is completed.

			.endif	;ax==IDOK

		.endif	;BN_CLICKED

	.ELSE	; uMsg is not handled
		mov eax,FALSE
		ret
	.ENDIF	; uMsg
       mov eax,TRUE
       ret
DlgProc endp

end start

Subkeys of CLSID key

Now it's possible to continue experiments with the registry. The major subkeys of "HKEY_CLASSES_ROOT\CLSID" are a number of subkeys allowing to locate a server in the system: InprocServer, InprocServer32, InprocHandler, InprocHandler32, LocalServer, LocalServer32. Each of the server types is represented with two subkeys; the suffix "32" means that the server is 32-bit, the subkey without this suffix indicates that the server is 16-bit (for backward compatibility). The case of the remote server is handled in a particular way; we'll discuss it in more details in the section about AppID.

Let's begin to play tricks. Special utilities such as guidgen etc. are used for getting unique GUIDs when creating new interfaces and components. Microsoft has reserved a large number of handy GUIDs with sets of zeros for its own use; but we'll handle it yet cooler - we'll use GUIDs like {00000000-0000-0000-0000-000000000001}. Why to bother ourselves seeking for our component among the bulk of digits, when we can comfortably put it in the very beginning of the section? It would be possible (and the author had done it indeed at first) to use GUID with all zeros but it's the special reserved GUID_NULL used in special cases; to avoid trouble it's better to leave it alone.

Create the key {00000000-0000-0000-0000-000000000001} under "HKEY_CLASSES_ROOT\CLSID". The key may have a default value which represents the textual description of the component for a user. Note that it isn't ProgID (though some component programmers use ProgID as the component description). You may put here some string, for example, "My favourite component". Then create a subkey with the name "LocalServer32". The default value of this subkey stores just the full pathname to the file implementing the server. Write here "c:\windows\calc.exe" (replace "windows" with the windows folder on your machine). Now launch our client (COM_2.exe), enter CLSID, press IUnknown, check "Local server" as the server, press "Connect" and... You may close the calculator. And our client is like hung - but you must have been ready to this, because the calculator has no idea about such an occasion that it became a COM component. While the COM system is still waiting for a responce from the calculator, our client sits inside CoCreateInstance at that time. Though about half a minute later it'll return the timeout error and our client will display the corresponding message box.

I shan't describe here all other experiments - the way is showed, the utilities are created - experiment! COM technology may be understood only in practice. But don't forget about the warning concerning accuracy when working with registry in the heat of enthusiasm.

Let's consider other subkeys. The important ones are TreatAs and AutoTreatAs because they allow to change the server of a component. If there is a subkey TreatAs (its default value contains another CLSID) under the key with the given CLSID then an object will be created with this other CLSID regardless of presence of subkeys InprocServer, LocalServer, etc. Make the following experiment. Create a subkey "TreatAs" under our key {00000000-0000-0000-0000-000000000001}. Put CLSID for the component Excel.Application (using the utility COM_1.exe to convert ProgID to CLSID) as the value of this subkey. Now if you'll try to load our component using COM_2.exe, Excel will be loaded (though it remains hidden. To make sure it runs look into the task manager).

The subkey TreatAs permits one server to emulate the other one. The function CoGetTreatAsClass allows to get a value of this key and the function CoTreatAsClass - to assign this value or delete it. If the subkey TreatAs is being deleted using CoTreatAsClass this function examines the value of the subkey AutoTreatAs. If a key with this name does exist its value is copied in the subkey TreatAs; if not the TreatAs subkey is deleted alltogether. And the subkey AutoTreatAs may be created or deleted only by hand using registry API functions. Thus the value of preceding CLSID may be stored in the AutoTreatAs subkey if this server was emulated at first by one server and then is emulated by the other providing in a sense "permanent" emulation (as opposed to "temporary" emulation using TreatAs subkey).

Being the main source of data about the component the "HKEY_CLASSES_ROOT\CLSID" key contains subkeys with crossreferences to other keys. We've already talked about ProgID and VersionIndependentProgID subkeys. The TypeLib subkey stores GUID that is an identifier of the type library for the given component; the corresponding subkey is located under "HKEY_CLASSES_ROOT\TypeLib". AppID subkey stores GUID identifying the subkey of another section - "HKEY_CLASSES_ROOT\AppID" - and indicates that the object is distributed (DCOM). These subkeys will be described later in more details.

There is a few other subkeys; we shan't examine them all. We'll mention only about those that are in a sense flags indicating types of components. The presence of "Control" subkey indicates that the component is a control; "Insertable" indicates that the object can be embedded in OLE containers; "OLEScript" - that the object is a scripting engine; "DocObject" - a document object; "Printable" - the object can be printed; "Programmable" - the object is an automation server (and accordingly it can be managed with scripts); "Ole1Class" - OLE 1.0 object (old version).

The utility for examination of object types

Let's create another small utility that will display a list of objects registered in the registry and relating to one of seven types listed at the end of the previous paragraph. The user interface of the utility is shown in fig.4. The type of an object can be chosen in the dropdown list; after pressing the "List" button descriptions of proper objects are displayed in the listview (these descriptions are default values for corresponding CLSID keys). If there is no description the string will be left empty.

Fig.4. The user interface of COM_3 utility
Fig.4. The user interface of COM_3 utility

To save space we shan't give here the full source code of the application; the respective files are located in COM_3 folder of ComKit1.rar. Instead let's examine only some of essential aspects concerning the logic of program operation.

invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,ADDR SubKey1,0,KEY_ALL_ACCESS,ADDR HKey
.if eax==ERROR_SUCCESS 

First the registry key "HKEY_CLASSES_ROOT\CLSID" is opened; its handle is stored in HKey variable. On successful opening of the key the main loop starts enumerating subkeys stored in the opened key.

EnumLoop:
	mov sbksz,255
	invoke RegEnumKeyEx,HKey,idx,ADDR SubKeyBuf,ADDR sbksz,0,0,0,0
	.if eax==ERROR_NO_MORE_ITEMS
		jmp OutLoop
	.elseif eax!=ERROR_SUCCESS
		invoke MessageBox,0,ADDR Err2,ADDR App,MB_OK OR MB_ICONERROR
		jmp OutLoop
	.endif

On any enumeration error the loop is being broken and if the error is not because the enumeration is up, the proper message is displayed. For each new key found the two strings are constructed: "CLSID\{clsid of component}" and "CLSID\{clsid of component}\<type of object>", where <type of object> represents one of the seven strings describing the type of the object chosen from the dropdown list.

	invoke lstrcpy,ADDR SubKey2,ADDR SubKey1
	invoke lstrcpy,ADDR SubKey3,ADDR SubKey1
	invoke lstrcat,ADDR SubKey2,ADDR s
	invoke lstrcat,ADDR SubKey3,ADDR s
	invoke lstrcat,ADDR SubKey2,ADDR SubKeyBuf
	invoke lstrcat,ADDR SubKey3,ADDR SubKeyBuf
	invoke lstrcat,ADDR SubKey3,ADDR s
	invoke lstrcat,ADDR SubKey3,ADDR findbuf
	invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,ADDR SubKey3,0,
KEY_ALL_ACCESS,ADDR HKey2
.if eax==ERROR_SUCCESS

Then an attempt is made to open the registry key "CLSID\{clsid of component}\<type of object>". The successful attempt means that the key with the given CLSID has a subkey with the chosen name; in this case the other prepared key ("CLSID\{clsid of component}") is opened and the default value is requested which is then sent to the listview and the loop continues with the next iteration:

	invoke RegCloseKey,HKey2
	invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,ADDR SubKey2,0,
KEY_ALL_ACCESS,ADDR HKey3
	invoke RegQueryValueEx,HKey3,0,0,0,ADDR namebuf,ADDR bsz
	invoke RegCloseKey,HKey3
	invoke SendMessage,hLst,LB_ADDSTRING,0,ADDR namebuf
.endif	;RegOpenKeyEx (2)
	inc idx
	jmp EnumLoop
OutLoop:
	invoke RegCloseKey,HKey

Interface section

Unlike other keys "HKEY_CLASSES_ROOT\Interface" relates not to individual components but to the system in the whole. Given here data about interfaces is used on standard marshaling. There are not so many subkeys: BaseInterface, NumMethods, ProxyStubCLSID, and ProxyStubCLSID32. BaseInterface stores the name of the interface which the given interface is inherited from; if it isn't indicated IUnknown is used as the base interface. NumMethods stores the number of methods in the interface. ProxyStubCLSID(32) is like InprocServer(32): it stores the full pathname to the server (dll) implementing auxiliary objects (so called proxies and stubs), which are used by the systm on marshaling of methods of the given interface. As usual the suffix "32" in the name means the 32-bit server and its absence means the 16-bit one.

Detailed discussion of marshaling and related problems is beyond the scope of this article; something will be told in the second article, and now we'll give the description of a useful utility using this registry section. Its user interface is shown in fig.5. On pressing "List" button the application creates an instance of the COM object with CLSID indicated in the edit box. Then the created object is requested for all possible interfaces names of which can be found in the system registry. The names of all interfaces implemented by this object are displayed in the listview.

Fig.5. The user interface of COM_4 utility
Fig.5. The user interface of COM_4 utility

Here again only key features of the cource code will be given; source files are located in the folder COM_4 of ComKit1.rar.

Since the application uses the COM library CoInitialize procedure is called on initilizing of the dialog box and on its closing CoUninitialize is called appropriately. On pressing "List" button the listview window is cleared and CLSID is got from editbox by means we already know:

	invoke GetWindowText,hEd,ADDR buffer,255
	invoke MultiByteToWideChar,CP_ACP,0,ADDR buffer,511,ADDR wbuf,255
	invoke CLSIDFromString,ADDR wbuf,ADDR cls

Then an instance of the object with the given CLSID is created; IUnknown is requested as the first interface (its IID is stored in IUnk) and the object context permits any type of the server:

	invoke CoCreateInstance,ADDR cls,0, CLSCTX_SERVER,ADDR IUnk,ADDR pUnk

If the pointer is obtained (in pUnk) successfully the registry key "HKEY_CLASSES_ROOT\Interface" is opened and the main loop of enumeration of subkeys (i.e. interfaces) is entered:

	invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,ADDR SubKey1,0,
KEY_ALL_ACCESS,ADDR HKey
	.if eax==ERROR_SUCCESS
Enum1:
	mov sbksz,255
	invoke RegEnumKeyEx,HKey,idx,ADDR SubKeyBuf,ADDR sbksz,0,0,0,0
	.if eax==ERROR_NO_MORE_ITEMS
		jmp Ex_2
	.elseif eax!=ERROR_SUCCESS
		invoke MessageBox,0,ADDR Err2,ADDR App,MB_OK OR MB_ICONERROR
		jmp Ex_2

The loop is exited when the enumeration function returns an error; if it isn't the error because of lack of keys for further enumeration then an error message is displayed. Each new enumerated key (IID of an interface in the string representation) is converted into numeric form (if an error takes place when converting then this key subsequently is simply ignored and the loop continues).

	.else
		invoke MultiByteToWideChar,CP_ACP,0,ADDR SubKeyBuf,255,ADDR wbuf,255
		invoke CLSIDFromString,ADDR wbuf,ADDR cls
		.if eax!=NOERROR
			jmp Cont1
		.endif

Now it's necessary to request our object for the interface with the just got IID. To do it we need to call QueryInterface method of IUnknown interface with the following arguments:

  • pointer to an instance of the current object ("this");
  • address of a structure where IID of the requested interface is stored;
  • address of output variable that receives the requested interface pointer.

Address of the QueryInterface method is located at the very beginning of the virtual table (with offset 0):

	lea eax,pItfc	; pItfc receives ptr to new interface
	push eax
	lea eax,cls		; requested IID is in cls
	push eax
	mov eax,pUnk		; ptr to IUnknown interface
				; of our object ("this")
	push eax	
	mov eax,[eax]		; get address of the virtual table
	call dword ptr [eax]	; call the first function
				; from the virtual table (QueryInterface)

In the case of successful receiving of the new interface pointer QueryInterface returns S_OK. We need just the fact that the object implements this interface, the interface itself is not required; therefore we release just received pointer right away calling Release method on it:

.if eax==S_OK
	mov eax,pItfc		; ptr to requested interface
	push eax		; put as the first (and the only) parameter;
	mov eax,[eax]		; address of virtual table
	call dword ptr [eax+8]	; call the 3rd method from the table (Release)

At the same time a kind of "Interface\{IID of interface}" string is created and the respective registry key is opened requesting the default value of this key (the interface name). This name is added into the listview.

	invoke lstrcpy,ADDR SubKey2,ADDR SubKey1
	invoke lstrcat,ADDR SubKey2,ADDR s
	invoke lstrcat,ADDR SubKey2,ADDR SubKeyBuf
	invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,ADDR SubKey2,0,
KEY_ALL_ACCESS,ADDR HKey2
	.if eax==ERROR_SUCCESS
		mov bsz,255
		invoke RegQueryValueEx,HKey2,0,0,0,ADDR namebuf,ADDR bsz
		.if eax!=ERROR_SUCCESS
			invoke MessageBox,0,ADDR Err5,ADDR App,MB_OK OR MB_ICONERROR
		.endif
		invoke RegCloseKey,HKey2
		invoke SendMessage,hLst,LB_ADDSTRING,0,ADDR namebuf
	.endif	;RegOpenKeyEx (2)
.endif	;S_OK (QueryIntervace)

In case if QueryInterface or RegOpenKeyEx returns error this key is simply ignored and the loop continues.

Cont1:
	inc idx
	jmp Enum1
	.endif	;RegEnumKeyEx
Ex_2:
	invoke RegCloseKey,HKey

After exiting the loop Release method is called on pUnk pointer to IUnknown interface that was received on creating the object instance (in a similar manner it was done earlier).

TypeLib section

"HKEY_CLASSES_ROOT\TypeLib" contains keys the names of which represent the string form of identifiers (GUIDs) of istalled in the system type libraries. Subkeys TypeLib of components under "HKEY_CLASSES_ROOT\CLSID" reference these keys but there are no backreferences. Type libraries are mandatory for components that use late binding and automation; in particular they are widely used with ActiveX controls. Other components may not have type libraries.

Each key under TypeLib contains a hierarchical structure of subkeys. Subkeys of type library version are stored on the first level, being represented in the string form like major.minor (digits corresponding to the version). In turn a version subkey contains HelpDir subkeys (that store full pathnames to help files), Flags (flag of type library) and the string representation of the locale identifier (lcid). The <lcid> key contains yet one or two additional subkeys: win16 and/or win32 - full pathnames to type libraries for the corresponding platform.

Let's consider one more application as an example of work with a type library (fig.6). The dialog box of the application has only one control - a listview. Descriptions of components are displayed in the first column and identifiers of the corresponding type libraries are displayed next to them in the second column. On double click on component name either general info about the given component stored in its type library is displayed or help file related to this component (if any) is opened.

Fig.6. The user interface of COM_5 utility
Fig.6. The user interface of COM_5 utility

The application works as follows (details of implementation of GUI are omitted; full source code is located in COM_5 folder of ComKit1.rar). On handling WM_INITDIALOG message the registry key "HKEY_CLASSES_ROOT\CLSID" is opened, then the main loop enumerating stored there subkeys is entered:

invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,ADDR SubKey1,0,KEY_ALL_ACCESS,ADDR HKey
	.if eax==ERROR_SUCCESS
EnumLoop:
		lea esi,buf
		mov sbksz,255
		invoke RegEnumKeyEx,HKey,idx,ADDR SubKeyBuf,ADDR sbksz,0,0,0,0
		.if eax==ERROR_NO_MORE_ITEMS
			jmp OutLoop
		.elseif eax!=ERROR_SUCCESS
			invoke MessageBox,0,ADDR Err2,ADDR App,MB_OK OR MB_ICONERROR
			jmp OutLoop
		.endif

As in the previous utiltity if RegEnumKeyEx returns an error the loop is being broken. The two strings in the form of "CLSID\{clsid of component}" and "CLSID\{clsid of component}\TypeLib" are created for each new key, then an attempt is made to open the registry key with the second string as the name.

	invoke lstrcpy,ADDR SubKey2,ADDR SubKey1
	invoke lstrcpy,ADDR SubKey3,ADDR SubKey1
	invoke lstrcat,ADDR SubKey2,ADDR s
	invoke lstrcat,ADDR SubKey3,ADDR s
	invoke lstrcat,ADDR SubKey2,ADDR SubKeyBuf
	invoke lstrcat,ADDR SubKey3,ADDR SubKeyBuf
	invoke lstrcat,ADDR SubKey3,ADDR s
	invoke lstrcat,ADDR SubKey3,ADDR findbuf
	invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,ADDR SubKey3,0,
KEY_ALL_ACCESS,ADDR HKey3

The successful opening of TypeLib key means the component has a type library. In this case a value of "CLSID\{clsid of component}" key is requested and the corresponding string is placed in the first column of the listview and a value of "CLSID\{clsid of component}\TypeLib" subkey (the string representation of the type library identifier) is placed in the second column:

.if eax==ERROR_SUCCESS
invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,
ADDR SubKey2,0,KEY_ALL_ACCESS,ADDR HKey2
	.if eax==ERROR_SUCCESS
mov bsz,255
invoke RegQueryValueEx,HKey2,0,0,0,ADDR buf,ADDR bsz
		mov eax,cnt
mov item.iItem,eax
mov item.iSubItem,0
invoke SendMessage,hList,LVM_INSERTITEM,0,ADDR item
	mov bsz,255
	invoke RegQueryValueEx,HKey3,0,0,0,ADDR buf,ADDR bsz
		.if eax==ERROR_SUCCESS
			mov item.iSubItem,1
			invoke SendMessage,hList,LVM_SETITEM,0,ADDR item
		.endif
		invoke RegCloseKey,HKey2
		inc cnt
	.endif	;RegOpenKeyEx (SubKey2)
	invoke RegCloseKey,HKey3
.endif	;RegOpenKeyEx (SubKey3)
inc idx
jmp EnumLoop
OutLoop:
	invoke RegCloseKey,HKey

A double click on an element of the listview is handled as WM_NOTIFY message. The proper code is located in the separate procedure FindHelp, the address of NMLISTVIEW structure with info about the clicked list element being passed in ebx registry:

.ELSEIF uMsg==WM_NOTIFY
	mov ebx,lParam
	assume ebx:ptr NMHDR
	.if [ebx].code==NM_DBLCLK
		call FindHelp
	.endif
	assume ebx:nothing 

FindHelp procedure begins with checking whether double click is acually made on a listview element:

FindHelp proc 
	assume ebx:ptr NMLISTVIEW
	mov eax,[ebx].iItem
	mov item.iItem,eax
	.if eax==-1
		ret
	.endif
	assume ebx:nothing

Now it's possible to copy into buf contents of the second column of the proper element (GUID of the type library in the text representation).

	mov item.imask,LVIF_TEXT
	mov item.iSubItem,1
	mov item.pszText,OFFSET buf
	mov item.cchTextMax,255
	invoke SendMessage,hList,LVM_GETITEM,0,ADDR item

Then using the given value the name of the registry key "TypeLib\{GUID of type library}" is created and the key is opened.

	invoke lstrcpy,ADDR SubKey2,ADDR cl2
	invoke lstrcat,ADDR SubKey2,ADDR s
	invoke lstrcat,ADDR SubKey2,ADDR buf
	invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,ADDR SubKey2,
0,KEY_ALL_ACCESS,ADDR HKey

It's necessary to handle the not very handy form of representation of type library data. To get a pathname to the file containing the type library, first we need to indicate the version but it's impossible to say anything about it in advance. The trouble is that the function LoadRegTypeLib requires indicating of the exact major version and refuses to load type libraries with other versions. Therefore we'll need to examine version subkeys on hand and pick out the major version from the subkey name.

	.if eax==ERROR_SUCCESS
		mov idx,0
		mov mjver,0	; for max value of majversion
TlibLoop:
		mov sbksz,255
		; enumerate subkeys under 'HKCR\TypeLib\{GUID}' 
		; (as majversion.minversion)
		invoke RegEnumKeyEx,HKey,idx,ADDR SubKeyBuf,ADDR sbksz,0,0,0,0
		.if eax==ERROR_SUCCESS
			xor eax,eax
			mov al,SubKeyBuf	; the first digit of subkey (i.e. majversion)
			sub eax,30h		; convert the symbol into value
			cmp mjver,eax
			jge Tlib1		; choose the greater value for version
			mov mjver,eax
Tlib1:
			inc idx
			jmp TlibLoop
		.endif	;RegEnumKeyEx
		invoke RegCloseKey,HKey

The number of major version is obtained; now it's possible to convert the string representation of GUID into the numeric one and pass it to LoadRegTypeLib function that accepts the following arguments:

  • address of a sturcture holding GUID of the library being loaded;
  • major version number (the exact value must be indicated; in this case it's in the mjver variable);
  • minor version number (may be 0 here, because the system will load any library having minor version number greater than the indicated one);
  • national language code of the library being loaded (lcid; may be neutral - 0);
  • address of a variable that receives a pointer to ITypeLib interface on return.

	invoke MultiByteToWideChar,CP_ACP,0,ADDR buf,-1,ADDR wbuf,510
	invoke CLSIDFromString,ADDR wbuf,ADDR TlibGuid
	.if eax==NOERROR
		invoke LoadRegTypeLib,ADDR TlibGuid,mjver,0,0,ADDR pTlib
		.if eax==S_OK	

The returned value S_OK means we've got a valid pointer to ITypeLib interface. We need GetDocumentation method; it accepts the following arguments:

  • index of the type description; if -1, then the documentation for the library itself is returned (just that we need);
  • address of a variable that receives a pointer to a BSTR string with the name of the requested element (in this case - of the type library itself);
  • address of a variable that receives a pointer to a BSTR string with the description of the requested element;
  • address of a variable that receives the Help context identifier;
  • address of a variable that receives a pointer to a BSTR string that contains the fully qualified name of the Help file.

If you don't need any type of data you may pass 0 as the value of the proper argument. The address of GetDocumentation function is located in the virtual table of the ITypeLib interface with offset 24h.

	push OFFSET HelpFile
	push 0				; don't need context, pBstrHelpCtx=NULL
	push OFFSET DocString
	push OFFSET CompName
	push -1				; index (-1='the type library itself')
	mov eax,pTlib
	push eax			; ‘this’ ptr
	mov eax,[eax]			; virtual table
	call dword ptr [eax+24h]	; GetDocumentation

0 in HelpFile on return means there is no help file. In this case a simple message box with the received data is displayed:

.if HelpFile==0	
	invoke WideCharToMultiByte,CP_ACP,0,CompName,-1,ADDR SubKeyBuf,255,0,0
	invoke lstrcpy,ADDR wbuf,ADDR SubKeyBuf
	invoke lstrcat,ADDR wbuf,ADDR CRLF
	invoke WideCharToMultiByte,CP_ACP,0,DocString,-1,ADDR SubKeyBuf,255,0,0
	invoke lstrcat,ADDR wbuf,ADDR SubKeyBuf
	invoke lstrcat,ADDR wbuf,ADDR CRLF
	invoke lstrcat,ADDR wbuf,ADDR ms1
	invoke MessageBox,0,ADDR wbuf,ADDR App,MB_OK or MB_ICONINFORMATION

But if the Help file is indicated the corresponding string is converted from Unicode into ANSI and its address is passed as an argument to ShellExecute function (in order to have possibility to load both hlp- and chm-files):

.else			; load and show Help file
	invoke WideCharToMultiByte,CP_ACP,0,HelpFile,-1,ADDR buf,255,0,0
	invoke ShellExecute,0,ADDR cmd,ADDR buf,0,0,SW_SHOW
	.if eax<33			; error of ShellExecute 
		invoke wsprintf,ADDR wbuf,ADDR fmt,ADDR buf
		invoke MessageBox,0,ADDR wbuf,ADDR App,MB_OK or MB_ICONERROR
	.endif
.endif		;HelpFile==0

BSTR strings must be freed aftewords to avoid memory leaks:

	invoke SysFreeString,HelpFile	
	invoke SysFreeString,CompName
	invoke SysFreeString,DocString

On error of GetDocumentation method the proper message is displayed. In any case the ITypeLib interface pointer (pTlib) must be freed after that:

	.else				; eroor invoking GetDocumentation
		invoke MessageBox,0,ADDr Err6,ADDR App,MB_OK or MB_ICONERROR
	.endif					; GetDocumentation
	mov eax,pTlib
	push eax				; ‘this’ ptr
	mov eax,[eax]				; virtual table
	call dword ptr [eax+8]			; function #3 (Release)

The function is concluded with handling of other errors:

		.else		; error of LoadRegTypeLib
			invoke MessageBox,0,ADDR Err5,ADDR App,MB_OK or MB_ICONERROR
		.endif			; LoadRegTypeLib==S_OK
	.else				; error of CLSIDFromString
		invoke MessageBox,0,ADDR Err4,ADDR App,MB_OK or MB_ICONERROR
	.endif				; CLSIDFromString==NOERROR
.else					; error of RegOpenKeyEx
	invoke MessageBox,0,ADDR Err7,ADDR App,MB_OK or MB_ICONERROR
	ret
.endif			; RegOpenKeyEx==ERROR_SUCCESS
ret
FindHelp endp

You can find out many interesting things using this utility. Try it!

AppID section

The registry key "HKEY_CLASSES_ROOT\AppID" jointly with the key "HKEY_LOCAL_MACHINE\Software\Microsoft\OLE" determine settings for the distributed COM (DCOM) system.

The distributed COM system deals with data transmission via network and right away faces questions of authentication, authorization and other problems of data protection. The general configuration scheme is usually as follows: values of default security parameters are described under "HKEY_LOCAL_MACHINE\Software\Microsoft\OLE" key. The key "HKEY_CLASSES_ROOT\AppID" contains subkeys (GUID) that can store security parameters for separate groups of components (in this case "HKEY_CLASSES_ROOT\CLSID\{clsid of component}\AppID" containes a reference to the corresponding key under "HKEY_CLASSES_ROOT\AppID"). The values under AppID prevail over the respective default values. Besides the security parameters and other related to distributed system parameters may be indicated explicitly when creating component using the function CoCreateInstanceEx or like. In this case the explicitly specified values are used.

"HKEY_LOCAL_MACHINE\Software\Microsoft\OLE" key may have the following named values:

  • EnableDCOM - permits (Y or y) or prohibits (N or n) remoute clients to launch components on this system;
  • DefaultLaunchPermission - defines the default list of principals (ACL) who can launch components on this machine;
  • DefaultAccessPermission – defines the default list of principals (ACL) who can access loaded objects;
  • LegacyAuthenticationLevel – sets the default authentication level;
  • LegacyImpersonationLevel – sets the default impersonation level;
  • LegacyMutualAuthentication – permits (Y or y) or prohibits (any other value or the lack of this named value) mutual authentication;
  • LegacySecureReferences – indicates whether invocations of AddRef and Release methods are secured (Y or y) or not (any other value or the lack of this named value).

The keys "HKEY_CLASSES_ROOT\AppID\{GUID}" can have the following named values:

  • RemoteServerName – the name of the server where the component is to be launched;
  • ActivateAtStorage – indicates that the component must be launched on the same machine as object data;
  • LocalService – indicates that the component is implemented as Win32 service;
  • ServiceParameters – is used in common with LocalService. The value of this parameter is passed on invocation of the proper service as parameters;
  • RunAs - sets an application to run only as a given user;
  • LaunchPermission - determins the list of principals (ACL) who can launch the application;
  • AccessPermission - determins the list of principals (ACL) who can access objects of the given class;
  • DllSurrogate - contains the full pathname to the wrapper application (exe) in which the remote inprocess (dll) server is to be activated;
  • AuthenticationLevel - sets the authentication level for AppID.

The indicated in the registry values may be overlapped through the explicit call to CoInitializeSecurity function.

Sorry for my English; I hope you did understand it.

2002-2013 (c) wasm.ru