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):
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 = -4100If you have installed Word XP you can launch another .vbs file:
Код (Text):
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 WithIf the control "Calendar" was installed when installing MS Office you can create and examine in your browser the following html-file:
Код (Text):
<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.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):
Set x = CreateObject("MyName") x.Visible = TrueNaturally 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 utilityThe user interface design of the utility is shown in fig.2. It's implemented in the following resource file (COM_1.rc):
Код (Text):
#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 ENDThe 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):
.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" buttonThe utility is implemented as a modal dialog box created from resource template:
Код (Text):
.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,0The algorithm is like that. On pressing "Get ProgID" button edit control for ProgID is cleared and the CLSID control text is read.
Код (Text):
.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,255If the string is empty a message suggesting to enter the text is displayed:
Код (Text):
.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 textA 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):
invoke MultiByteToWideChar,CP_ACP,0,ADDR buf,-1,ADDR wbuf,510One 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):
invoke CLSIDFromString,ADDR wbuf,ADDR cls .if eax==NOERROR invoke ProgIDFromCLSID,ADDR cls,ADDR bufaddr .if eax==S_OKIn 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):
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 .endifFurther the block for handling errors of function CLSIDFromString goes which are considered as a wrong format of an entered CLSID string:
Код (Text):
.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 ;GetWindowTextThe block handling pressing "Get ProgID" button is complete. Pressing "Get CLSID" button is handled in a similar way.
Код (Text):
.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_ProgIDAfter that the processing of dialog box messages is over:
Код (Text):
.endif ;BN_CLICKED .ELSE mov eax,FALSE ret .ENDIF ;uMsg mov eax,TRUE ret DlgProc endp end startThe 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):
GUID STRUCT Data1 dd ? Data2 dw ? Data3 dw ? Data4 db 8 dup(?) GUID ENDSBut in the registry a string of the following format is used for representation of GUID:
Код (Text):
{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_2Here's the corresponding resource file (COM_2.rc):
Код (Text):
#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 ENDSo 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):
.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,hEdit1Since 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):
invoke CoInitialize,0 mov eax,FALSE ret .ELSEIF uMsg==WM_CLOSE invoke CoUninitialize invoke EndDialog,hWnd,0The 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):
.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 .endifNow 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):
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_ICONINFORMATIONAt 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):
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.
Код (Text):
.endif ;ax==IDOK .endif ;BN_CLICKED .ELSE ; uMsg is not handled mov eax,FALSE ret .ENDIF ; uMsg mov eax,TRUE ret DlgProc endp end startSubkeys 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 utilityTo 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):
invoke RegOpenKeyEx,HKEY_CLASSES_ROOT,ADDR SubKey1,0,KEY_ALL_ACCESS,ADDR HKey .if eax==ERROR_SUCCESSFirst 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):
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 .endifOn 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):
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_SUCCESSThen 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):
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,HKeyInterface 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 utilityHere 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):
invoke GetWindowText,hEd,ADDR buffer,255 invoke MultiByteToWideChar,CP_ACP,0,ADDR buffer,511,ADDR wbuf,255 invoke CLSIDFromString,ADDR wbuf,ADDR clsThen 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):
invoke CoCreateInstance,ADDR cls,0, CLSCTX_SERVER,ADDR IUnk,ADDR pUnkIf 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):
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_2The 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):
.else invoke MultiByteToWideChar,CP_ACP,0,ADDR SubKeyBuf,255,ADDR wbuf,255 invoke CLSIDFromString,ADDR wbuf,ADDR cls .if eax!=NOERROR jmp Cont1 .endifNow 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):
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:
Код (Text):
.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.
Код (Text):
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.
Код (Text):
Cont1: inc idx jmp Enum1 .endif ;RegEnumKeyEx Ex_2: invoke RegCloseKey,HKeyAfter 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 utilityThe 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):
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 .endifAs 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):
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 HKey3The 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):
.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,HKeyA 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):
.ELSEIF uMsg==WM_NOTIFY mov ebx,lParam assume ebx:ptr NMHDR .if [ebx].code==NM_DBLCLK call FindHelp .endif assume ebx:nothingFindHelp procedure begins with checking whether double click is acually made on a listview element:
Код (Text):
FindHelp proc assume ebx:ptr NMLISTVIEW mov eax,[ebx].iItem mov item.iItem,eax .if eax==-1 ret .endif assume ebx:nothingNow 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):
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 itemThen using the given value the name of the registry key "TypeLib\{GUID of type library}" is created and the key is opened.
Код (Text):
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 HKeyIt'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):
.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,HKeyThe 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):
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_OKThe 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):
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</pre></code> <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: <p><pre><code>.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</pre></code> <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): <p><pre><code>.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</pre></code> <p>BSTR strings must be freed aftewords to avoid memory leaks: <p><pre><code> invoke SysFreeString,HelpFile invoke SysFreeString,CompName invoke SysFreeString,DocString</pre></code> <p>On error of GetDocumentation method the proper message is displayed. In any case the ITypeLib interface pointer (pTlib) must be freed after that: <p><pre><code> .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:
Код (Text):
.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 endpYou 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
3 whales of COM. The first whale: registry
Дата публикации 26 дек 2006