3 whales of COM. The first whale: registry

Дата публикации 26 дек 2006

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:

Код (Text):
  1. Set x = CreateObject("Excel.Application")
  2. x.Workbooks.Add
  3. x.Cells(1,1).Value = 5
  4. x.Cells(1,2).Value = 10
  5. x.Cells(1,3).Value = 15
  6. x.Range("A1:C1").Select
  7. Set ch = x.Charts.Add()
  8. x.Visible = True
  9. ch.Type = -4100
  10.  

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

Код (Text):
  1. Set w = CreateObject("Word.Application")
  2. w.Visible = True
  3. Set rng = w.Document.Add.Range(0,0)
  4. With rng
  5.     .InsertBefore "Text for WordXP"
  6.     .ParagraphFormat.Alignment = 1
  7.     With .Font
  8.         .Name = "Arial"
  9.         .Size = 16
  10.         .Color = 200
  11.     End With
  12. End With
  13.  

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

Код (Text):
  1. &ltHTML&gt
  2. &ltBODY&gt
  3. &ltOBJECT CLASSID="CLSID:8E27C92B-1264-101C-8A2F-040224009C02"&gt
  4. &lt/OBJECT&gt
  5. &lt/BODY&gt
  6. &lt/HTML&gt
  7.  

[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:

Код (Text):
  1. Set x = CreateObject("MyName")
  2. x.Visible = True
  3.  

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):

Код (Text):
  1. #define DS_MODALFRAME       0x80L
  2. #define DS_CENTER           0x0800L
  3. #define WS_POPUP            0x80000000L
  4. #define WS_CAPTION          0x00C00000L
  5. #define WS_SYSMENU          0x00080000L
  6. #define ES_AUTOHSCROLL      0x0080L
  7.  
  8. #define IDC_EDIT1           1000
  9. #define IDC_EDIT2           1001
  10. #define IDB_ProgID          1002
  11. #define IDB_CLSID           1003
  12. #define IDC_STATIC          -1
  13.  
  14. MyDialog DIALOG DISCARDABLE  200, 200, 200, 66
  15. STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
  16. CAPTION "ProgID <-> CLSID"
  17. FONT 8, "MS Sans Serif"
  18. BEGIN
  19.     DEFPUSHBUTTON   "Get ProgID",IDB_ProgID,35,46,50,14
  20.     PUSHBUTTON      "Get CLSID",IDB_CLSID,108,46,50,14
  21.     LTEXT           "CLSID:",IDC_STATIC,7,7,26,12
  22.     LTEXT           "ProgID:",IDC_STATIC,7,27,26,12
  23.     EDITTEXT        IDC_EDIT1,38,7,154,12,ES_AUTOHSCROLL
  24.     EDITTEXT        IDC_EDIT2,38,27,154,12,ES_AUTOHSCROLL
  25. END
  26.  

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.

Код (Text):
  1. .386
  2. .model flat,stdcall
  3. option casemap:none
  4.  
  5. DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD
  6.  
  7. include \masm32\include\windows.inc
  8. include \masm32\include\user32.inc
  9. include \masm32\include\kernel32.inc
  10. include \masm32\include\ole32.inc
  11. includelib \masm32\lib\user32.lib
  12. includelib \masm32\lib\kernel32.lib
  13. includelib \masm32\lib\ole32.lib
  14.  
  15. .data
  16.  
  17. DlgName db "MyDialog",0
  18. App     db "ProgID_CLSID",0
  19. ms1     db "Enter CLSID here",0
  20. ms2     db "Enter ProgID here",0
  21. ms3     db "There is no ProgID for this CLSID",0
  22. ms4     db "There is no CLSID for this ProgID",0
  23. Err1        db "The wrong CLSID",0
  24.  
  25. .data?
  26.  
  27. hInstance   HINSTANCE ?
  28. hEdit1  DWORD ?     ; edit control for CLSID
  29. hEdit2  DWORD ?     ; edit control for ProgID
  30. buf     db 256 dup(?)   ; for ANSI strings
  31. wbuf        db 512 dup(?)   ; for Unicode strings
  32. cls     db 16 dup(?)    ; buffer for CLSID
  33. bufaddr DWORD ?
  34.  
  35. .const
  36.  
  37. IDC_EDIT1   equ 1000    ; edit control for CLSID
  38. IDC_EDIT2   equ 1001    ; edit control for ProgID
  39. IDB_ProgID  equ 1002    ; "Get ProgID" button
  40. IDB_CLSID   equ 1003    ; "Get CLSID" button
  41.  

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

Код (Text):
  1. .code
  2.  
  3. start:
  4. invoke GetModuleHandle, NULL
  5. mov    hInstance,eax
  6. invoke DialogBoxParam, hInstance, ADDR DlgName,NULL,
  7. addr  DlgProc, NULL
  8. invoke ExitProcess,eax
  9.  
  10. DlgProc proc USES esi edi ebx,hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
  11. .IF uMsg==WM_INITDIALOG
  12.     ; store handles for edit controls
  13.     invoke GetDlgItem,hWnd,IDC_EDIT1
  14.     mov hEdit1,eax
  15.     invoke GetDlgItem,hWnd,IDC_EDIT2
  16.     mov hEdit2,eax
  17.     invoke SetFocus,hEdit1
  18.     mov eax,FALSE
  19.     ret
  20. .ELSEIF uMsg==WM_CLOSE
  21.     invoke EndDialog,hWnd,0
  22.  

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

Код (Text):
  1. .ELSEIF uMsg==WM_COMMAND
  2.     mov eax,wParam
  3.     mov edx,wParam
  4.     shr edx,16
  5.     .if dx==BN_CLICKED
  6.         .if ax==IDB_ProgID  ; convert CLSID into ProgID
  7.             mov buf,0
  8.             invoke SetWindowText,hEdit2,ADDR buf
  9.             invoke GetWindowText,hEdit1,ADDR buf,255
  10.  

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

Код (Text):
  1. .if eax==0  ; GetWindowText - the string is empty
  2.     invoke MessageBox,0,ADDR ms1,ADDR App,0
  3.     invoke SendMessage,hEdit1,EM_SETSEL,0,-1
  4.     invoke SetFocus,hEdit1
  5. .else       ; we've got the text
  6.  

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.

Код (Text):
  1.     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):

Код (Text):
  1.     invoke CLSIDFromString,ADDR wbuf,ADDR cls
  2.     .if eax==NOERROR
  3.         invoke ProgIDFromCLSID,ADDR cls,ADDR bufaddr
  4.         .if eax==S_OK
  5.  

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.

Код (Text):
  1.         invoke WideCharToMultiByte,CP_ACP,0,bufaddr,-1,ADDR buf,255,0,0
  2.         invoke SetWindowText,hEdit2,ADDR buf
  3.     .else   ; couldn't convert  CLSID to ProgID -
  4.         ; issue message
  5.         invoke SetWindowText,hEdit2,ADDR ms3
  6.     .endif
  7.  

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

Код (Text):
  1.     .else   ; error of CLSIDFromString
  2.         invoke MessageBox,0,ADDR Err1,ADDR App,MB_OK OR MB_ICONERROR
  3.         invoke SendMessage,hEdit1,EM_SETSEL,0,-1
  4.         invoke SetFocus,hEdit1
  5.     .endif  ;NOERROR - CLSIDFromString
  6. .endif  ;GetWindowText
  7.  

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

Код (Text):
  1. .elseif ax==IDB_CLSID   ;convert ProgID in CLSID string
  2.     mov buf,0
  3.     invoke SetWindowText,hEdit1,ADDR buf
  4.     invoke GetWindowText,hEdit2,ADDR buf,255
  5.     .if eax==0          ; no text in control - error message
  6.         invoke MessageBox,0,ADDR ms2,ADDR App,0
  7.         invoke SendMessage,hEdit2,EM_SETSEL,0,-1
  8.         invoke SetFocus,hEdit2
  9.     .else               ; got ProgID text - convert to Unicode
  10.         invoke MultiByteToWideChar,CP_ACP,0,ADDR buf,-1,ADDR wbuf,510
  11.                     ; convert ProgID into numeric CLSID
  12.         invoke CLSIDFromProgID,ADDR wbuf,ADDR cls
  13.         .if eax==S_OK   ; got CLSID,
  14.                     ; convert into stirng form
  15.             invoke StringFromCLSID,ADDR cls,ADDR bufaddr
  16.                     ; convert Unicode string into ANSI
  17.             invoke WideCharToMultiByte,CP_ACP,0,bufaddr,-1,
  18. ADDR  buf,255,0,0
  19.             invoke CoTaskMemFree,bufaddr    ; free memory,
  20.                     ; allocated in call to StringFromCLSID
  21.             invoke SetWindowText,hEdit1,ADDR buf
  22.         .else           ; failed to get CLSID from ProgID
  23.             invoke SetWindowText,hEdit1,ADDR ms4
  24.         .endif      ;S_OK (CLSIDFromProgID)
  25.     .endif          ;GetWindowText
  26. .endif              ;ax==IDB_ProgID
  27.  

After that the processing of dialog box messages is over:

Код (Text):
  1.         .endif  ;BN_CLICKED
  2.     .ELSE
  3.         mov eax,FALSE
  4.         ret
  5.     .ENDIF  ;uMsg
  6.        mov eax,TRUE
  7.        ret
  8. DlgProc endp
  9.  
  10. end start
  11.  

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:

Код (Text):
  1. GUID    STRUCT
  2.     Data1   dd ?
  3.     Data2   dw ?
  4.     Data3   dw ?
  5.     Data4   db 8 dup(?)
  6. GUID ENDS
  7.  

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

Код (Text):
  1. {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):

Код (Text):
  1. #include "\masm32\include\resource.h"
  2.  
  3. #define IDC_EDIT1       1001
  4. #define IDC_EDIT2       1002
  5. #define IDC_CHECK1  1003
  6. #define IDC_CHECK2  1004
  7. #define IDC_CHECK3  1005
  8. #define IDC_CHECK4  1006
  9. #define IDC_BUTTON1 1007
  10. #define IDC_STATIC  -1
  11.  
  12. MyDialog DIALOGEX 0, 0, 214, 90
  13. STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU
  14. CAPTION "Simple COM client"
  15. FONT 8, "MS Sans Serif"
  16. BEGIN
  17.     LTEXT           "CLSID:",IDC_STATIC,7,10,26,8,0,WS_EX_RIGHT
  18.     LTEXT           "IID:",IDC_STATIC,7,28,26,8,0,WS_EX_RIGHT
  19.     GROUPBOX        "Server type:",IDC_STATIC,7,45,142,38
  20.     EDITTEXT        IDC_EDIT1,40,7,167,14,ES_AUTOHSCROLL
  21.     EDITTEXT        IDC_EDIT2,40,26,167,14,ES_AUTOHSCROLL
  22.     CONTROL         "InProc server",IDC_CHECK1,"Button",BS_AUTOCHECKBOX |
  23.                     WS_TABSTOP,15,55,58,10
  24.     CONTROL         "InProc handler",IDC_CHECK2,"Button",BS_AUTOCHECKBOX |
  25.                     WS_TABSTOP,15,68,62,10
  26.     CONTROL         "Local server",IDC_CHECK3,"Button",BS_AUTOCHECKBOX |
  27.                     WS_TABSTOP,80,55,55,10
  28.     CONTROL         "Remoute server",IDC_CHECK4,"Button",BS_AUTOCHECKBOX |
  29.                     WS_TABSTOP,80,68,66,10
  30.     PUSHBUTTON      "IUnknown",IDC_BUTTON1,157,49,50,14
  31.     DEFPUSHBUTTON   "Connect",IDOK,157,67,50,14
  32. END
  33.  

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:

Код (Text):
  1. .386
  2. .model flat,stdcall
  3. option casemap:none
  4.  
  5. DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD
  6.  
  7. include \masm32\include\windows.inc
  8. include \masm32\include\user32.inc
  9. include \masm32\include\kernel32.inc
  10. include \masm32\include\ole32.inc
  11. includelib \masm32\lib\user32.lib
  12. includelib \masm32\lib\kernel32.lib
  13. includelib \masm32\lib\ole32.lib
  14.  
  15. .data
  16.  
  17. DlgName db "MyDialog",0
  18. app     db "SimpleClient",0
  19. ms1     db "Enter CLSID here",0
  20. ms2     db "Enter IID here",0
  21. szUnk       db "{00000000-0000-0000-C000-000000000046}",0
  22. mscheck db "The server type must be indicated",0
  23. msclsid db "Enter CLSID here",0
  24. msiid       db "Enter IID here",0
  25. msok    db "Got interface pointer to:",13,10,"CLSID:",9,"%s",13,10,"IID:",9,"%s",0
  26. mserr       db "CoCreateInstance failed:",13,10,"HRESULT==%Xh",0
  27. err1        db "The wrong CLSID",0
  28. err2        db "The wrong IID",0
  29.  
  30. .data?
  31.  
  32. hInstance   HINSTANCE ?
  33. hEdit1  DWORD ?     ; "CLSID"
  34. hEdit2  DWORD ?     ; "IID"
  35. hCheck1 DWORD ?     ; "Inproc server"
  36. hCheck2 DWORD ?     ; "Inproc handler"
  37. hCheck3 DWORD ?     ; "Local server"
  38. hCheck4 DWORD ?     ; "Remoute server"
  39. ctx     DWORD ?     ; server type
  40. szclsid db 64 dup(?)    ; buffer for CLSID string
  41. sziid       db 64 dup(?)    ; buffer for IID string
  42. buf     db 256 dup(?)   ; for ANSI strings
  43. wbuf        db 512 dup(?)   ; for Unicode strings
  44. cls     db 16 dup(?)    ; CLSID of component
  45. iid     db 16 dup(?)    ; IID of called interface
  46. pUnk        DWORD ?     ; ptr to IUnknown interface
  47.  
  48. .const
  49.  
  50. IDC_EDIT1   equ 1001    ; "CLSID"
  51. IDC_EDIT2   equ 1002    ; "IID"
  52. IDC_CHECK1  equ 1003    ; "Inproc server"
  53. IDC_CHECK2  equ 1004    ; "Inproc handler"
  54. IDC_CHECK3  equ 1005    ; "Local server"
  55. IDC_CHECK4  equ 1006    ; "Remoute server"
  56. IDC_BUTTON1 equ 1007    ; "IUnknown"
  57.  
  58. .code
  59.  
  60. start:
  61.     invoke GetModuleHandle, NULL
  62.     mov    hInstance,eax
  63.     invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL
  64.     invoke ExitProcess,eax
  65.  
  66. DlgProc proc USES esi edi ebx,hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
  67. .IF uMsg==WM_INITDIALOG
  68.     ; store handles of controls
  69.     invoke GetDlgItem,hWnd,IDC_EDIT1
  70.     mov hEdit1,eax
  71.     invoke GetDlgItem,hWnd,IDC_EDIT2
  72.     mov hEdit2,eax
  73.     invoke GetDlgItem,hWnd,IDC_CHECK1
  74.     mov hCheck1,eax
  75.     invoke GetDlgItem,hWnd,IDC_CHECK2
  76.     mov hCheck2,eax
  77.     invoke GetDlgItem,hWnd,IDC_CHECK3
  78.     mov hCheck3,eax
  79.     invoke GetDlgItem,hWnd,IDC_CHECK4
  80.     mov hCheck4,eax
  81.     invoke SetFocus,hEdit1
  82.  

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.

Код (Text):
  1.     invoke CoInitialize,0
  2.     mov eax,FALSE
  3.     ret
  4. .ELSEIF uMsg==WM_CLOSE
  5.     invoke CoUninitialize
  6.     invoke EndDialog,hWnd,0
  7.  

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:

Код (Text):
  1. .ELSEIF uMsg==WM_COMMAND
  2.     mov eax,wParam
  3.     mov edx,wParam
  4.     shr edx,16
  5.     .if dx==BN_CLICKED
  6.         .if ax==IDC_BUTTON1     ; button 'IUnknown'
  7. invoke SetWindowText,hEdit2,offset szUnk
  8.         .elseif ax==IDOK        ; button 'Connect'
  9.                         ; get CLSID
  10. invoke GetWindowText,hEdit1,offset szclsid,64
  11. .if eax==0                  ; no text - display message
  12.     invoke MessageBox,0,offset msclsid,offset app,MB_ICONEXCLAMATION
  13.     invoke SendMessage,hEdit1,EM_SETSEL,0,-1
  14.     invoke SetFocus,hEdit1
  15.     mov eax,TRUE
  16.     ret
  17. .else                       ; got text - translate into ANSI and
  18.                         ; convert to numeric form, storing in cls
  19. invoke MultiByteToWideChar,CP_ACP,0,offset szclsid,-1,offset wbuf,510
  20.     invoke CLSIDFromString,offset wbuf,offset cls
  21.     .if eax!=NOERROR            ; error of CLSIDFromString is considered
  22.                         ; as wrong format of entered CLSID string
  23.         invoke MessageBox,0,offset err1,offset app,MB_ICONERROR
  24.         invoke SendMessage,hEdit1,EM_SETSEL,0,-1
  25.         invoke SetFocus,hEdit1
  26.         mov eax,TRUE
  27.         ret
  28.     .endif              ; NOERROR - CLSIDFromString
  29. .endif                  ; GetWindowText (hEdit1)
  30.  
  31.                         ; get IID
  32. invoke GetWindowText,hEdit2,offset sziid,64
  33. .if eax==0  ; no text - issue message
  34.     invoke MessageBox,0,offset msiid,offset app,MB_ICONEXCLAMATION
  35.     invoke SendMessage,hEdit2,EM_SETSEL,0,-1
  36.     invoke SetFocus,hEdit2
  37.     mov eax,TRUE
  38.     ret
  39. .else                       ; got IID text, translate into ANSI,
  40.                         ; then convert to numeric form, storing in iid
  41.     invoke MultiByteToWideChar,CP_ACP,0,offset sziid,-1,offset wbuf,510
  42.     invoke IIDFromString,offset wbuf,offset iid
  43.     .if eax!=NOERROR            ; error of IIDFromString is considered as
  44.                         ; wrong format of entered IID string
  45.         invoke MessageBox,0,offset err2,offset app,MB_ICONERROR
  46.         invoke SendMessage,hEdit2,EM_SETSEL,0,-1
  47.         invoke SetFocus,hEdit2
  48.         mov eax,TRUE
  49.         ret
  50.     .endif              ; NOERROR - IIDFromString
  51. .endif                  ; GetWindowText (hEdit2)
  52.  
  53.         ; chech the state of checkboxes and determine the type of server,
  54.         ; storing the calculated value in ctx
  55. mov ctx,0
  56. invoke IsDlgButtonChecked,hWnd,IDC_CHECK1
  57. .if eax==BST_CHECKED
  58.     or ctx,CLSCTX_INPROC_SERVER
  59. .endif
  60. invoke IsDlgButtonChecked,hWnd,IDC_CHECK2
  61. .if eax==BST_CHECKED
  62.     or ctx,CLSCTX_INPROC_HANDLER
  63. .endif
  64. invoke IsDlgButtonChecked,hWnd,IDC_CHECK3
  65. .if eax==BST_CHECKED
  66.     or ctx,CLSCTX_LOCAL_SERVER
  67. .endif
  68. invoke IsDlgButtonChecked,hWnd,IDC_CHECK4
  69. .if eax==BST_CHECKED
  70.     or ctx,CLSCTX_REMOTE_SERVER
  71. .endif
  72. .if ctx==0              ; no server type is selected - display message
  73.     invoke MessageBox,0,offset mscheck,offset app,MB_ICONEXCLAMATION
  74.     invoke SetFocus,hCheck1
  75.     mov eax,TRUE
  76.     ret
  77. .endif
  78.  

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.

Код (Text):
  1. invoke CoCreateInstance,offset cls,0,ctx,offset iid,offset pUnk
  2. mov ecx,eax
  3. .if eax==S_OK   ; The object is successively created and a pointer to the
  4.         ; requested interface is returned in  pUnk. Issue message
  5.         ; with CLSID of the object and IID of the interface
  6.     invoke wsprintf,offset buf,offset msok,offset szclsid,offset sziid
  7.     invoke MessageBox,0,offset buf,offset app,MB_ICONINFORMATION
  8.  

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:

Код (Text):
  1.     mov eax,pUnk
  2.     push eax        ; ptr to object instance ("this")
  3.     mov eax,[eax]       ; get ptr to virtual table,
  4.     call dword ptr [eax+8]  ; then ptr to the third function
  5.                 ; in this table (Release) and call it
  6.  
  7. .else           ; error of CoCreateInstance
  8.     invoke wsprintf,offset buf,offset mserr,ecx
  9.     invoke MessageBox,0,offset buf,offset app,MB_ICONERROR
  10. .endif          ; eax==S_OK (CoCreateInstance)
  11.  

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

Код (Text):
  1.             .endif  ;ax==IDOK
  2.  
  3.         .endif  ;BN_CLICKED
  4.  
  5.     .ELSE   ; uMsg is not handled
  6.         mov eax,FALSE
  7.         ret
  8.     .ENDIF  ; uMsg
  9.        mov eax,TRUE
  10.        ret
  11. DlgProc endp
  12.  
  13. end start
  14.  

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.

Код (Text):
  1. invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,ADDR SubKey1,0,KEY_ALL_ACCESS,ADDR HKey
  2. .if eax==ERROR_SUCCESS
  3.  

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.

Код (Text):
  1. EnumLoop:
  2.     mov sbksz,255
  3.     invoke RegEnumKeyEx,HKey,idx,ADDR SubKeyBuf,ADDR sbksz,0,0,0,0
  4.     .if eax==ERROR_NO_MORE_ITEMS
  5.         jmp OutLoop
  6.     .elseif eax!=ERROR_SUCCESS
  7.         invoke MessageBox,0,ADDR Err2,ADDR App,MB_OK OR MB_ICONERROR
  8.         jmp OutLoop
  9.     .endif
  10.  

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.

Код (Text):
  1.     invoke lstrcpy,ADDR SubKey2,ADDR SubKey1
  2.     invoke lstrcpy,ADDR SubKey3,ADDR SubKey1
  3.     invoke lstrcat,ADDR SubKey2,ADDR s
  4.     invoke lstrcat,ADDR SubKey3,ADDR s
  5.     invoke lstrcat,ADDR SubKey2,ADDR SubKeyBuf
  6.     invoke lstrcat,ADDR SubKey3,ADDR SubKeyBuf
  7.     invoke lstrcat,ADDR SubKey3,ADDR s
  8.     invoke lstrcat,ADDR SubKey3,ADDR findbuf
  9.     invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,ADDR SubKey3,0,
  10. KEY_ALL_ACCESS,ADDR HKey2
  11. .if eax==ERROR_SUCCESS
  12.  

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:

Код (Text):
  1.     invoke RegCloseKey,HKey2
  2.     invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,ADDR SubKey2,0,
  3. KEY_ALL_ACCESS,ADDR HKey3
  4.     invoke RegQueryValueEx,HKey3,0,0,0,ADDR namebuf,ADDR bsz
  5.     invoke RegCloseKey,HKey3
  6.     invoke SendMessage,hLst,LB_ADDSTRING,0,ADDR namebuf
  7. .endif  ;RegOpenKeyEx (2)
  8.     inc idx
  9.     jmp EnumLoop
  10. OutLoop:
  11.     invoke RegCloseKey,HKey
  12.  

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:

Код (Text):
  1.     invoke GetWindowText,hEd,ADDR buffer,255
  2.     invoke MultiByteToWideChar,CP_ACP,0,ADDR buffer,511,ADDR wbuf,255
  3.     invoke CLSIDFromString,ADDR wbuf,ADDR cls
  4.  

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:

Код (Text):
  1.     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:

Код (Text):
  1.     invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,ADDR SubKey1,0,
  2. KEY_ALL_ACCESS,ADDR HKey
  3.     .if eax==ERROR_SUCCESS
  4. Enum1:
  5.     mov sbksz,255
  6.     invoke RegEnumKeyEx,HKey,idx,ADDR SubKeyBuf,ADDR sbksz,0,0,0,0
  7.     .if eax==ERROR_NO_MORE_ITEMS
  8.         jmp Ex_2
  9.     .elseif eax!=ERROR_SUCCESS
  10.         invoke MessageBox,0,ADDR Err2,ADDR App,MB_OK OR MB_ICONERROR
  11.         jmp Ex_2
  12.  

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).

Код (Text):
  1.     .else
  2.         invoke MultiByteToWideChar,CP_ACP,0,ADDR SubKeyBuf,255,ADDR wbuf,255
  3.         invoke CLSIDFromString,ADDR wbuf,ADDR cls
  4.         .if eax!=NOERROR
  5.             jmp Cont1
  6.         .endif
  7.  

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):

Код (Text):
  1.     lea eax,pItfc   ; pItfc receives ptr to new interface
  2.     push eax
  3.     lea eax,cls     ; requested IID is in cls
  4.     push eax
  5.     mov eax,pUnk        ; ptr to IUnknown interface
  6.                 ; of our object ("this")
  7.     push eax   
  8.     mov eax,[eax]       ; get address of the virtual table
  9.     call dword ptr [eax]    ; call the first function
  10.                 ; from the virtual table (QueryInterface)
  11.  

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:

Код (Text):
  1. .if eax==S_OK
  2.     mov eax,pItfc       ; ptr to requested interface
  3.     push eax        ; put as the first (and the only) parameter;
  4.     mov eax,[eax]       ; address of virtual table
  5.     call dword ptr [eax+8]  ; call the 3rd method from the table (Release)
  6.  

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.

Код (Text):
  1.     invoke lstrcpy,ADDR SubKey2,ADDR SubKey1
  2.     invoke lstrcat,ADDR SubKey2,ADDR s
  3.     invoke lstrcat,ADDR SubKey2,ADDR SubKeyBuf
  4.     invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,ADDR SubKey2,0,
  5. KEY_ALL_ACCESS,ADDR HKey2
  6.     .if eax==ERROR_SUCCESS
  7.         mov bsz,255
  8.         invoke RegQueryValueEx,HKey2,0,0,0,ADDR namebuf,ADDR bsz
  9.         .if eax!=ERROR_SUCCESS
  10.             invoke MessageBox,0,ADDR Err5,ADDR App,MB_OK OR MB_ICONERROR
  11.         .endif
  12.         invoke RegCloseKey,HKey2
  13.         invoke SendMessage,hLst,LB_ADDSTRING,0,ADDR namebuf
  14.     .endif  ;RegOpenKeyEx (2)
  15. .endif  ;S_OK (QueryIntervace)
  16.  

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

Код (Text):
  1. Cont1:
  2.     inc idx
  3.     jmp Enum1
  4.     .endif  ;RegEnumKeyEx
  5. Ex_2:
  6.     invoke RegCloseKey,HKey
  7.  

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:

Код (Text):
  1. invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,ADDR SubKey1,0,KEY_ALL_ACCESS,ADDR HKey
  2.     .if eax==ERROR_SUCCESS
  3. EnumLoop:
  4.         lea esi,buf
  5.         mov sbksz,255
  6.         invoke RegEnumKeyEx,HKey,idx,ADDR SubKeyBuf,ADDR sbksz,0,0,0,0
  7.         .if eax==ERROR_NO_MORE_ITEMS
  8.             jmp OutLoop
  9.         .elseif eax!=ERROR_SUCCESS
  10.             invoke MessageBox,0,ADDR Err2,ADDR App,MB_OK OR MB_ICONERROR
  11.             jmp OutLoop
  12.         .endif
  13.  

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.

Код (Text):
  1.     invoke lstrcpy,ADDR SubKey2,ADDR SubKey1
  2.     invoke lstrcpy,ADDR SubKey3,ADDR SubKey1
  3.     invoke lstrcat,ADDR SubKey2,ADDR s
  4.     invoke lstrcat,ADDR SubKey3,ADDR s
  5.     invoke lstrcat,ADDR SubKey2,ADDR SubKeyBuf
  6.     invoke lstrcat,ADDR SubKey3,ADDR SubKeyBuf
  7.     invoke lstrcat,ADDR SubKey3,ADDR s
  8.     invoke lstrcat,ADDR SubKey3,ADDR findbuf
  9.     invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,ADDR SubKey3,0,
  10. KEY_ALL_ACCESS,ADDR HKey3
  11.  

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:

Код (Text):
  1. .if eax==ERROR_SUCCESS
  2. invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,
  3. ADDR SubKey2,0,KEY_ALL_ACCESS,ADDR HKey2
  4.     .if eax==ERROR_SUCCESS
  5. mov bsz,255
  6. invoke RegQueryValueEx,HKey2,0,0,0,ADDR buf,ADDR bsz
  7.         mov eax,cnt
  8. mov item.iItem,eax
  9. mov item.iSubItem,0
  10. invoke SendMessage,hList,LVM_INSERTITEM,0,ADDR item
  11.     mov bsz,255
  12.     invoke RegQueryValueEx,HKey3,0,0,0,ADDR buf,ADDR bsz
  13.         .if eax==ERROR_SUCCESS
  14.             mov item.iSubItem,1
  15.             invoke SendMessage,hList,LVM_SETITEM,0,ADDR item
  16.         .endif
  17.         invoke RegCloseKey,HKey2
  18.         inc cnt
  19.     .endif  ;RegOpenKeyEx (SubKey2)
  20.     invoke RegCloseKey,HKey3
  21. .endif  ;RegOpenKeyEx (SubKey3)
  22. inc idx
  23. jmp EnumLoop
  24. OutLoop:
  25.     invoke RegCloseKey,HKey
  26.  

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:

Код (Text):
  1. .ELSEIF uMsg==WM_NOTIFY
  2.     mov ebx,lParam
  3.     assume ebx:ptr NMHDR
  4.     .if [ebx].code==NM_DBLCLK
  5.         call FindHelp
  6.     .endif
  7.     assume ebx:nothing
  8.  

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

Код (Text):
  1. FindHelp proc
  2.     assume ebx:ptr NMLISTVIEW
  3.     mov eax,[ebx].iItem
  4.     mov item.iItem,eax
  5.     .if eax==-1
  6.         ret
  7.     .endif
  8.     assume ebx:nothing
  9.  

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).

Код (Text):
  1.     mov item.imask,LVIF_TEXT
  2.     mov item.iSubItem,1
  3.     mov item.pszText,OFFSET buf
  4.     mov item.cchTextMax,255
  5.     invoke SendMessage,hList,LVM_GETITEM,0,ADDR item
  6.  

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

Код (Text):
  1.     invoke lstrcpy,ADDR SubKey2,ADDR cl2
  2.     invoke lstrcat,ADDR SubKey2,ADDR s
  3.     invoke lstrcat,ADDR SubKey2,ADDR buf
  4.     invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,ADDR SubKey2,
  5. 0,KEY_ALL_ACCESS,ADDR HKey
  6.  

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.

Код (Text):
  1.     .if eax==ERROR_SUCCESS
  2.         mov idx,0
  3.         mov mjver,0 ; for max value of majversion
  4. TlibLoop:
  5.         mov sbksz,255
  6.         ; enumerate subkeys under 'HKCR\TypeLib\{GUID}'
  7.         ; (as majversion.minversion)
  8.         invoke RegEnumKeyEx,HKey,idx,ADDR SubKeyBuf,ADDR sbksz,0,0,0,0
  9.         .if eax==ERROR_SUCCESS
  10.             xor eax,eax
  11.             mov al,SubKeyBuf    ; the first digit of subkey (i.e. majversion)
  12.             sub eax,30h     ; convert the symbol into value
  13.             cmp mjver,eax
  14.             jge Tlib1       ; choose the greater value for version
  15.             mov mjver,eax
  16. Tlib1:
  17.             inc idx
  18.             jmp TlibLoop
  19.         .endif  ;RegEnumKeyEx
  20.         invoke RegCloseKey,HKey
  21.  

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.

Код (Text):
  1.     invoke MultiByteToWideChar,CP_ACP,0,ADDR buf,-1,ADDR wbuf,510
  2.     invoke CLSIDFromString,ADDR wbuf,ADDR TlibGuid
  3.     .if eax==NOERROR
  4.         invoke LoadRegTypeLib,ADDR TlibGuid,mjver,0,0,ADDR pTlib
  5.         .if eax==S_OK  
  6.  

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.

Код (Text):
  1.     push OFFSET HelpFile
  2.     push 0              ; don't need context, pBstrHelpCtx=NULL
  3.     push OFFSET DocString
  4.     push OFFSET CompName
  5.     push -1             ; index (-1='the type library itself')
  6.     mov eax,pTlib
  7.     push eax            ; ‘this’ ptr
  8.     mov eax,[eax]           ; virtual table
  9.     call dword ptr [eax+24h]    ; GetDocumentation</pre></code>
  10. <p>0 in HelpFile on return means there is no help file. In this case a simple message box with the received data is displayed:
  11. <p><pre><code>.if HelpFile==0  
  12.     invoke WideCharToMultiByte,CP_ACP,0,CompName,-1,ADDR SubKeyBuf,255,0,0
  13.     invoke lstrcpy,ADDR wbuf,ADDR SubKeyBuf
  14.     invoke lstrcat,ADDR wbuf,ADDR CRLF
  15.     invoke WideCharToMultiByte,CP_ACP,0,DocString,-1,ADDR SubKeyBuf,255,0,0
  16.     invoke lstrcat,ADDR wbuf,ADDR SubKeyBuf
  17.     invoke lstrcat,ADDR wbuf,ADDR CRLF
  18.     invoke lstrcat,ADDR wbuf,ADDR ms1
  19.     invoke MessageBox,0,ADDR wbuf,ADDR App,MB_OK or MB_ICONINFORMATION</pre></code>
  20. <p>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):
  21. <p><pre><code>.else         ; load and show Help file
  22.     invoke WideCharToMultiByte,CP_ACP,0,HelpFile,-1,ADDR buf,255,0,0
  23.     invoke ShellExecute,0,ADDR cmd,ADDR buf,0,0,SW_SHOW
  24.     .if eax<33          ; error of ShellExecute
  25.         invoke wsprintf,ADDR wbuf,ADDR fmt,ADDR buf
  26.         invoke MessageBox,0,ADDR wbuf,ADDR App,MB_OK or MB_ICONERROR
  27.     .endif
  28. .endif      ;HelpFile==0</pre></code>
  29. <p>BSTR strings must be freed aftewords to avoid memory leaks:
  30. <p><pre><code>  invoke SysFreeString,HelpFile  
  31.     invoke SysFreeString,CompName
  32.     invoke SysFreeString,DocString</pre></code>
  33. <p>On error of GetDocumentation method the proper message is displayed. In any case the ITypeLib interface pointer (pTlib) must be freed after that:
  34. <p><pre><code>  .else               ; eroor invoking GetDocumentation
  35.         invoke MessageBox,0,ADDr Err6,ADDR App,MB_OK or MB_ICONERROR
  36.     .endif                  ; GetDocumentation
  37.     mov eax,pTlib
  38.     push eax                ; ‘this’ ptr
  39.     mov eax,[eax]               ; virtual table
  40.     call dword ptr [eax+8]          ; function #3 (Release)

The function is concluded with handling of other errors:

Код (Text):
  1.         .else       ; error of LoadRegTypeLib
  2.             invoke MessageBox,0,ADDR Err5,ADDR App,MB_OK or MB_ICONERROR
  3.         .endif          ; LoadRegTypeLib==S_OK
  4.     .else               ; error of CLSIDFromString
  5.         invoke MessageBox,0,ADDR Err4,ADDR App,MB_OK or MB_ICONERROR
  6.     .endif              ; CLSIDFromString==NOERROR
  7. .else                   ; error of RegOpenKeyEx
  8.     invoke MessageBox,0,ADDR Err7,ADDR App,MB_OK or MB_ICONERROR
  9.     ret
  10. .endif          ; RegOpenKeyEx==ERROR_SUCCESS
  11. ret
  12. 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. © Roustem


0 1.741
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532