The ATL 2.x Registrar provides optimized access to the system registry through a custom interface. The Registrar is free-threaded and allows static linking of code for C++ clients.
Note The ATL 2.x Registrar, provided in atl.dll, does not support Automation or the 1.1 methods that operated on a single key.
This article covers the following topics related to the Registrar:
Note All of the source code for the 2.x Registrar ships with ATL. You can find the source code in atl\src\atliface.h.
A registrar script provides data-driven, rather than API-driven, access to the system registry. Data-driven access is typically more efficient since it takes only one or two lines in a script to add a key to the registry.
The ATL Object Wizard automatically generates a registrar script for your COM server. You can find this script in the .rgs file associated with your object.
The ATL Registrars Script Engine processes your registrar script at run time. ATL automatically invokes the Script Engine during server setup.
The scripts used by the ATL Registrar follow BNF syntax and use the notation shown in the next table.
Convention/Symbol | What It Means |
::= | Equivalent |
| | OR |
X+ | One or more Xs. |
[X] | X is optional. Optional delimiters are denoted by []. |
Any bold text | A string literal. |
Any italicized text | How to construct the string literal. |
As indicated in the preceding table, registrar scripts use string literals. These values are actual text that must appear in your script. The following table describes the string literals used in an ATL Registrar script.
String Literal | Description |
ForceRemove | Completely remove the following key (if it exists) and then recreate it. |
NoRemove | Do not remove the following key during Unregister. |
val | The following <Key Name> is actually a named value. |
Delete | Delete the following key during Register. |
s | The following value is a string. |
d | The following value is a DWORD. |
Here are a few syntax examples to help you understand how the notation and string literals work in an ATL Registrar script.
<registry expression> ::= <Add Key>
specifies that registry expression is equivalent to Add Key.
<registry expression> ::= <Add Key> | <Delete Key>
specifies that registry expression is equivalent to either Add Key or Delete Key.
<Key Name> ::= '<AlphaNumeric>+'
specifies that Key Name is equivalent to one or more AlphaNumerics.
<Add Key> ::= [ForceRemove | NoRemove | val]<Key Name>
specifies that Add Key is equivalent to Key Name, and that the string literals, ForceRemove, NoRemove, and val, are optional.
<AlphaNumeric> ::= any character not NULL, i.e. ASCII 0
specifies that AlphaNumeric is equivalent to any non-NULL character.
Using BNF syntax, you define one or more parse trees in your script.
Each parse tree has the form:
<root key>{<registry expression>}+
where:
<root key> ::= HKEY_CLASSES_ROOT | HKEY_CURRENT_USER | HKEY_LOCAL_MACHINE | HKEY_USERS | HKEY_PERFORMANCE_DATA | HKEY_DYN_DATA | HKEY_CURRENT_CONFIG | HKCR | HKCU | HKLM | HKU | HKPD | HKDD | HKCC <registry expression> ::= <Add Key> | <Delete Key> <Add Key> ::= [ForceRemove | NoRemove | val]<Key Name> [<Key Value>][{< Add Key>}] <Delete Key> ::= Delete<Key Name> <Key Name> ::= '<AlphaNumeric>+' <AlphaNumeric> ::= any character not NULL, i.e. ASCII 0 <Key Value> ::== <Key Type><Key Name> <Key Type> ::= s | d <Key Value> ::= '<AlphaNumeric>'
Note HKEY_CLASSES_ROOT and HKCR are equivalent; HKEY_CURRENT_USER and HKCU are equivalent; and so on.
A parse tree can add multiple keys and subkeys to the <root key>. In doing so, it keeps a subkey's handle open until the parser has completed parsing all its subkeys. This approach is more efficient than operating on a single key at a time, as seen in the following parse tree example:
HKEY_CLASSES_ROOT { 'MyVeryOwnKey' { 'HasASubKey' { 'PrettyCool?' } } }
Here, the Registrar initially opens (creates) HKEY_CLASSES_ROOT\MyVeryOwnKey. It then sees that MyVeryOwnKey has a subkey. Rather than close the key to MyVeryOwnKey, the Registrar retains the handle and opens (creates) HasASubKey using this parent handle. (The system registry can be slower when no parent handle is open.) Thus, opening HKEY_CLASSES_ROOT\MyVeryOwnKey and then opening HasASubKey with MyVeryOwnKey as the parent is faster than opening MyVeryOwnKey, closing MyVeryOwnKey, and then opening MyVeryOwnKey\HasASubKey.
The scripting examples in this article demonstrate how to add a key to the system registry; register the Registrar COM server; and specify multiple parse trees.
The following parse tree illustrates a simple script that adds a single key to the system registry. In particular, the script adds the key, MyVeryOwnKey, to HKEY_CURRENT_USER. It also assigns the default string value of HowGoesIt? to the new key:
HKEY_CURRENT_USER { 'MyVeryOwnKey' = s 'HowGoesIt?' }
This script can easily be extended to define multiple subkeys as follows:
HKCU { 'MyVeryOwnKey' = s 'HowGoesIt?' { 'HasASubkey' { 'PrettyCool?' = d '55' val 'ANameValue' = s 'WithANamedValue' } } }
Now, the script adds a subkey, HasASubkey, to MyVeryOwnKey. To this subkey, it adds both the PrettyCool? subkey (with a default DWORD value of 55) and the ANameValue named value (with a string value of WithANamedValue).
The following script registers the Registrar COM server itself.
HKCR { ATL.Registrar = s 'ATL 2.0 Registrar Class' { CLSID = s '{44EC053A-400F-11D0-9DCD-00A0C90391D3}' } NoRemove CLSID { ForceRemove {44EC053A-400F-11D0-9DCD-00A0C90391D3} = s 'ATL 2.0 Registrar Class' { ProgID = s 'ATL.Registrar' InprocServer32 = s '%MODULE%' { val ThreadingModel = s 'Apartment' } } } }
At run time, this parse tree adds the ATL 2.0 Registrar Class key to HKEY_CLASSES_ROOT. To this new key, it then:
Since CLSID is shared, it should not be removed in Unregister mode. The statement, NoRemove CLSID, does this by indicating that CLSID should be opened in Register mode and ignored in Unregister mode.
The ForceRemove statement provides a housekeeping function by removing a key and all its subkeys before recreating the key. This can be useful if the names of the subkeys have changed. In this scripting example, ForceRemove checks to see if {44EC053A-400F-11D0-9DCD-00A0C90391D3} already exists. If it does, ForceRemove:
The parse tree now adds two new subkeys to {44EC053A-400F-11D0-9DCD-00A0C90391D3}. The first key, ProgID, gets a default string value that is the ProgID. The second key, InprocServer32, gets a default string value, %MODULE%, that is a preprocessor value explained in the section, Using Replaceable Parameters (The Registrar's Preprocessor), of this article. InprocServer32 also gets a named value, ThreadingModel, with a string value of Apartment.
In order to specify more than one parse pree in a script, simply place one tree at the end of another. For example, the following script adds the key, MyVeryOwnKey, to the parse trees for both HKEY_CLASSES_ROOT and HKEY_CURRENT_USER:
HKCR { 'MyVeryOwnKey' = s 'HowGoesIt?' }
HKEY_CURRENT_USER { 'MyVeryOwnKey' = s 'HowGoesIt?' }
Note In a Registrar script, 4K is the maximum token size. (A token is any recognizable element in the syntax). In the previous scripting example, HKCR, HKEY_CURRENT_USER, 'MyVeryOwnKey', and 'HowGoesIt?' are all tokens.
Replaceable parameters allow a Registrar's client to specify run-time data. To do this, the Registrar maintains a replacement map into which it enters the values associated with the replaceable parameters in your script. The Registrar makes these entries at run time. The following section, Using %MODULE%, demonstrates these steps.
To specify run-time data using replaceable parameters:
Besides adding entries to the map, you may also want to remove all entries from it. This is useful if more than one object wishes to use the same instance of the Registrar.
To remove all entries from the replacement map:
The ATL Object Wizard automatically generates a script that uses the %MODULE% replaceable parameter. ATL uses %MODULE% for the actual location of your server's DLL or EXE.
Besides adding %MODULE% to the script, the following line is added to the object's class declaration:
DECLARE_REGISTRY_RESOURCEID(IDR_MYCOMAPP)
This macro expands to:
static HRESULT WINAPI UpdateRegistry(BOOL bRegister) { return _Module.UpdateRegistryFromResource(IDR_MYCOMAPP, bRegister); }
where _Module refers to the global CComModule, which has the following method and #define statement:
UpdateRegistryFromResource(UINT nResID, BOOL bRegister, struct _ATL_REGMAP_ENTRY* pMapEntries = NULL);
#define UpdateRegistryFromResource UpdateRegistryFromResourceD
This method calls AtlModuleUpdateRegistryFromResourceD, which contains the following code:
ATLAPI AtlModuleUpdateRegistryFromResourceD(_ATL_MODULE*pM, LPCOLESTR lpszRes, BOOL bRegister, struct _ATL_REGMAP_ENTRY* pMapEntries, IRegistrar* pReg) { USES_CONVERSION; ... CComPtr<IRegistrar> p; ... TCHAR szModule[_MAX_PATH]; GetModuleFileName(pM->m_hInst, szModule, _MAX_PATH); p->AddReplacement(OLESTR("Module"), T2OLE(szModule)); ... }
Note You can find this code in atl\include\atlimpl.cpp.
CoCreateInstance acquires the pointer p, which points to the Registrar. Then, AddReplacement receives an LPCOLESTR containing the string "Module", as well as an LPCOLESTR containing the string acquired from the Win32 API function, GetModuleFileName. This code has adds a replacement map entry for the Module variable that has a value associated with the result of GetModuleFileName. Now, when the preprocessor sees the %MODULE% in the script, it will replace it with the value from GetModuleFileName.
Another use of the preprocessor is to concatenate run-time data with script data. For example, suppose we need an entry that contains a full path to a module with the string ", 1" appended at the end. First, define the following expansion:
'MyGoofyKey' = s '%MODULE%, 1'
Then, before calling one of the script processing methods, add a replacement to the map:
TCHAR szModule[_MAX_PATH] GetModuleFileName(pM->m_hInst, szModule, _MAX_PATH); p->AddReplacement(OLESTR("Module"), T2OLE(szModule)), CComBSTR(szModule));
During the parsing of the script, the Registrar expands '%MODULE%, 1' to c:\mycode\mydll.dll, 1.
Note In a Registrar script, 4K is the maximum token size. (A token is any recognizable element in the syntax.) This includes tokens that were created or expanded by the preprocessor.
Note To substitute replacement values at run time, do not specify the DECLARE_REGISTRY_RESOURCE or DECLARE_REGISTRY_RESOURCEID macro. Instead, create an array of _ATL_REGMAP_ENTRIES structures, where each entry contains a variable placeholder paired with a value to replace the placeholder at run time. Then call CComModule::UpdateRegistryFromResourceD, passing it the array. This method adds all the replacement values in the _ATL_REGMAP_ENTRIES structure to the Registrars replacement map.
The previous section, Using Replaceable Parameters (The Registrar's Preprocessor), discussed replacement maps and introduced two of the Registrar's methods, AddReplacement and ClearReplacements. The Registrar has eight other methods specific to scripting. All eight of these methods are described in the following table and invoke the Registrar on a particular script.
Method | Syntax/Description |
ResourceRegister | HRESULT ResourceRegister( LPCOLESTR resFileName, UINT nID, LPCOLESTR szType ); Registers the script contained in a module's resource. resFileName indicates the UNC path to the module itself. nID and szType contain the resource's ID and type, respectively. |
ResourceUnregister | HRESULT ResourceUnregister( LPCOLESTR resFileName, UINT nID, LPCOLESTR szType );( BSTR fileName, VARIANT
ID, VARIANT type ); Unregisters the script contained in a module's resource. resFileName indicates the UNC path to the module itself. nID and szType contain the resource's ID and type, respectively. |
ResourceRegisterSz | HRESULT ResourceRegisterSz( LPCOLESTR resFileName, LPCOLESTR szID, LPCOLESTR szType ); Registers the script contained in a module's resource. resFileName indicates the UNC path to the module itself. szID and szType contain the resource's string identifier and type, respectively. |
ResourceUnregisterSz | HRESULT ResourceUnregisterSz( LPCOLESTR resFileName, LPCOLESTR szID, LPCOLESTR szType ); Unregisters the script contained in a module's resource. resFileName indicates the UNC path to the module itself. szID and szType contain the resource's string identifier and type, respectively. |
FileRegister | HRESULT FileRegister( LPCOLESTR fileName ); Registers the script in a file. fileName is a UNC path to a file that contains (or is) a resource script |
FileUnregister | HRESULT FileUnregister( LPCOLESTR fileName ); Unregisters the script in a file. fileName is a UNC path to a file that contains (or is) a resource script |
StringRegister | HRESULT StringRegister( LPCOLESTR data ); Registers the script in a string. data contains the script itself. |
StringUnregister | HRESULT StringUnregister( LPCOLESTR data ); Unregisters the script in a string. data contains the script itself. |
ATL uses the first two methods shown in the table (ResourceRegister and ResourceUnregister) in atlimpl.cpp:
LPCOLESTR szType = OLESTR("REGISTRY"); GetModuleFileName(pM->m_hInstResource, szModule, _MAX_PATH); LPOLESTR pszModule = T2OLE(szModule); if (HIWORD(lpszRes)==0) { if (bRegister) hRes = p->ResourceRegister(pszModule, ((UINT)LOWORD((DWORD)lpszRes)), szType); else hRes = p->ResourceUnregister(pszModule, ((UINT)LOWORD((DWORD)lpszRes)), szType); } else { if (bRegister) hRes = p->ResourceRegisterSz(pszModule, lpszRes, szType); else hRes = p->ResourceUnregisterSz(pszModule, lpszRes, szType); }
Note that szModule contains the value acquired from GetModuleFileName.
The next two methods shown in the table, ResourceRegisterSz and ResourceUnregisterSz, are similar to ResourceRegister and ResourceUnregister, but allow you to specify a string identifier.
The methods FileRegister and FileUnregister are useful if you do not want the script in a resource or if you want the script in its own file. The methods StringRegister and StringUnregister allow the .rgs file to be stored in a dynamically-allocated string.
C++ clients can create a static link to the Registrar's code. Static linking of the Registrar's parser adds approximately 5K to a release build.
The simplest way to set up static linking assumes you have specified DECLARE_REGISTRY_RESOURCEID in your object's declaration. (This is the default specification used by the ATL.)
To create a static link using DECLARE_REGISTRY_RESOURCEID: