Last week, we looked at the basic
Registry functions provided by VB. I also suggested a simple model by which we could think
of the Registry. I strongly recommend you read the first
part of this series before this article, as many of the principles involved in this
article are fairly complex.
The Registry originally appeared in Windows 3.x, as a store of OLE types. However, all
application and Windows’ settings were kept in private initialisation files, spread all
over the hard disk. When Windows 95 was introduced, the people at Microsoft had had a good
think about this, and decided to put all these settings in one central file, so if the
computer crashed, you would lose all of your settings, not just one application’s worth.
Well, this wasn’t quite what they thought! They really wanted to have all
applications using the Registry to store settings instead of INI files, for reasons of
support and adapting to multi-user environments.
Because of this, we recommend that you back up the Registry BEFORE you start playing,
rather than after. The files that you need are system.dat and user.dat which are both in
the Windows directory. That way, if everything goes wrong, you can replace the damaged
ones.
This idea of one central database to store settings has been implemented in all the
newest versions of Windows (i.e. Windows 98, Windows NT4, and Windows NT5/2000), and is
definitely here to stay.
You can edit the Registry using a program called Regedit.exe. It is found in the
Windows directory. It allows you to browse through the Registry, and make changes to it.
This provides a very easy way to check that your app is doing what it should be doing in
the correct places.
The Registry database model introduced last week
was very much simplified. It suggested that the AppName parameter was specifying a ‘drive’
in the Registry. However, this area that VB writes to is in fact just another key. The
real ‘drives’ are the entries that you see when you first start up Regedit. The two that
you are most likely to use are HKEY_CURRENT_USER and HKEY_LOCAL_MACHINE. In fact
HKEY_CLASSES_ROOT is equal to HKEY_LOCAL_MACHINESOFTWARECLASSES, and HKEY_CURRENT_USER
is the same as one of the entries in HKEY_USERS. VB in fact uses
HKEY_CURRENT_USERSOFTWAREVB AND VBA PROGRAM SETTINGS to store the data.
The HKEY_CLASSES_ROOT key stores everything about documents, document readers, and how
they relate to each other. It stores the information about extensions, and which
applications are associated with the extension. It also stores information about DLLs and
OCXs. See the article about the Registry and DLLs for more information. HKEY_LOCAL_MACHINE
is where you will store most of your application’s settings. The standard is:
HKEY_LOCAL_MACHINESoftwareCompanyNameAppName.
If you have user specific settings, you can store it in similar sub keys in
HKEY_CURRENT_USER. You should not really need any of the other keys, although in
HKEY_DYN_DATA you can find some information about windows, similar to the things found in
the System Monitor (sysmon.exe) utility. You cannot write to this key.
One thing that you must be careful of, if your users could be running NT, is that you
have the necessary privileges to read and write to keys in the Registry.
So, as a proficient Windows programmer, you must know how to access and change all
parts, not just the parts that VB provides you with. To do this, we must use the API.
First, let’s have some declarations:
Declarations
Copy the following code into the declarations section of a module.
Public Const HKEY_CLASSES_ROOT = &H80000000 Public Const HKEY_CURRENT_USER = &H80000001 Public Const HKEY_LOCAL_MACHINE = &H80000002 Public Const HKEY_USERS = &H80000003 Public Const HKEY_CURRENT_CONFIG = &H80000005 Public Const HKEY_DYN_DATA = &H80000006 Public Const REG_SZ = 1 ' Unicode nul terminated string Public Const REG_BINARY = 3 ' Free form binary Public Const REG_DWORD = 4 ' 32-bit number Public Const ERROR_SUCCESS = 0& Public Declare Function RegCloseKey Lib "advapi32.dll" _ (ByVal hKey As Long) As Long Public Declare Function RegCreateKey Lib "advapi32.dll" _ Alias "RegCreateKeyA" (ByVal hKey As Long, ByVal lpSubKey _ As String, phkResult As Long) As Long Public Declare Function RegDeleteKey Lib "advapi32.dll" _ Alias "RegDeleteKeyA" (ByVal hKey As Long, ByVal lpSubKey _ As String) As Long Public Declare Function RegDeleteValue Lib "advapi32.dll" _ Alias "RegDeleteValueA" (ByVal hKey As Long, ByVal _ lpValueName As String) As Long Public Declare Function RegOpenKey Lib "advapi32.dll" _ Alias "RegOpenKeyA" (ByVal hKey As Long, ByVal lpSubKey _ As String, phkResult As Long) As Long Public Declare Function RegQueryValueEx Lib "advapi32.dll" _ Alias "RegQueryValueExA" (ByVal hKey As Long, ByVal lpValueName _ As String, ByVal lpReserved As Long, lpType As Long, lpData _ As Any, lpcbData As Long) As Long Public Declare Function RegSetValueEx Lib "advapi32.dll" _ Alias "RegSetValueExA" (ByVal hKey As Long, ByVal _ lpValueName As String, ByVal Reserved As Long, ByVal _ dwType As Long, lpData As Any, ByVal cbData As Long) As Long
The main idea for reading and writing to the Registry is to find and select a key,
read/write a value, and then close the key. This is all based around handles to keys.
Handles to keys (a similar idea to window handles and device context handles) are numbers,
which Windows uses to reference a file or part of memory; in this case, it is a key in the
Registry.
One of the limitations of using the built in functions of VB is that you can only write
strings to the Registry. However, with the API, you can write Long type numbers and Byte
arrays, as well as several other types of string. We will now take you through how to read
and write to anywhere in the Registry.
Copy the following procedure into your project.
Public Sub DeleteValue( _ ByVal hKey As Long, _ ByVal strPath As String, _ ByVal strValue As String) Dim hCurKey As Long Dim lRegResult As Long lRegResult = RegOpenKey(hKey, _ strPath, hCurKey) lRegResult = RegDeleteValue(hCurKey, _ strValue) lRegResult = RegCloseKey(hCurKey) End Sub
The RegOpenKey API function to create takes a handle to a key, and the path that you
want to select, and returns a handle to the new key. As was mentioned earlier, a handle to
a key is a reference to a key. You must have a key to start with, so the six constants for
the six root keys are fixed, and are provided as constants. The handles to these keys are
the same on every Win32 system in every session. The other handles that you receive for
the sub-keys will be variable, and will change each time you select the key. However, the
handle will remain valid until you use the RegCloseKey function. If you don’t close the
handle to the key when you are finished with it, it will continue using up resources, so
you must remember to close it when you are done.
Unusually, the Registry API functions will return 0 on success, and another number for
failure. There is the constant ERROR_SUCCESS, which is defined to make code more readable
when debugging.
This wrapper function takes a handle to a key, a path to a key, and the name of the value
to delete. The handle to the key for most purposes will be one of the constant handles to
a root key, and the path will be the path to the key. The path, as with the intrinsic VB
functions can include the "" sign to signify sub keys. If you want to delete a
value in the root of the key specified pass strPath as vbNullString. If you want to erase
the default value, pass strValue as vbNullString.
Deleting Keys
Public Sub DeleteKey(ByVal hKey As Long, ByVal strPath As String) Dim lRegResult As Long lRegResult = RegDeleteKey(hKey, strPath) End Sub
This function, as above, takes a handle to a key and a path to a key. It will find the
key specified, and will delete all sub-keys and values, and using the ‘drive’ model would
be equivalent to the ‘deltree’ DOS command – only without confirmation. Use this
carefully, as it can have disastrous effects!
Creating Keys
Public Sub CreateKey(hKey As Long, strPath As String) Dim hCurKey As Long Dim lRegResult As Long lRegResult = RegCreateKey(hKey, strPath, hCurKey) If lRegResult <> ERROR_SUCCESS Then ' there is a problem End If lRegResult = RegCloseKey(hCurKey) End Sub
Now that we are getting going, this does not need too much explaining. Pass a handle to
a key and a path, and it will create all necessary keys to create the specified key. If
you want to write an error handler into these functions, the function provides a space for
checking whether the function has been a success.
Public Function GetSettingString( _ hKey As Long, _ strPath As String, _ strValue As String, _ Optional _ Default As String) As String Dim hCurKey As Long Dim lResult As Long Dim lValueType As Long Dim strBuffer As String Dim lDataBufferSize As Long Dim intZeroPos As Integer Dim lRegResult As Long ' Set up default value If Not IsEmpty(Default) Then GetSettingString = Default Else GetSettingString = "" End If lRegResult = RegOpenKey(hKey, _ strPath, hCurKey) lRegResult = RegQueryValueEx(hCurKey, _ strValue, 0&, _ lValueType, ByVal 0&, _ lDataBufferSize) If lRegResult = ERROR_SUCCESS Then If lValueType = REG_SZ Then strBuffer = String(lDataBufferSize, " ") lResult = RegQueryValueEx(hCurKey, _ strValue, 0&, 0&, _ ByVal strBuffer, _ lDataBufferSize) intZeroPos = InStr(strBuffer, Chr$(0)) If intZeroPos > 0 Then GetSettingString = Left$(strBuffer, _ intZeroPos - 1) Else GetSettingString = strBuffer End If End If Else ' there is a problem End If lRegResult = RegCloseKey(hCurKey) End Function Public Sub SaveSettingString(hKey As Long, strPath _ As String, strValue As String, strData As String) Dim hCurKey As Long Dim lRegResult As Long lRegResult = RegCreateKey(hKey, strPath, hCurKey) lRegResult = RegSetValueEx(hCurKey, strValue, 0, REG_SZ, _ ByVal strData, Len(strData)) If lRegResult <> ERROR_SUCCESS Then 'there is a problem End If lRegResult = RegCloseKey(hCurKey) End Sub
These Registry functions for strings are quite complicated because of the need to
initialise buffers. The function to retrieve strings first sets up the default value. This
will be used if the specified key is not valid. The first call to RegQueryValueEx
determines the data type in the key and the length if it is a string. The second call
retrieves the string. Then the string is processed, removing any trailing nulls.
The function to save the string is much simpler. The RegSetValueEx function is called,
passing the type as a string, the string data, and the length of the string.
Public Function GetSettingLong( _ ByVal hKey As Long, _ ByVal strPath As String, _ ByVal strValue As String, _ Optional Default As Long) As Long Dim lRegResult As Long Dim lValueType As Long Dim lBuffer As Long Dim lDataBufferSize As Long Dim hCurKey As Long ' Set up default value If Not IsEmpty(Default) Then GetSettingLong = Default Else GetSettingLong = 0 End If lRegResult = RegOpenKey(hKey, _ strPath, hCurKey) lDataBufferSize = 4 ' 4 bytes = 32 bits = long lRegResult = RegQueryValueEx(hCurKey, _ strValue, 0&, _ lValueType, lBuffer, _ lDataBufferSize) If lRegResult = ERROR_SUCCESS Then If lValueType = REG_DWORD Then GetSettingLong = lBuffer End If Else 'there is a problem End If lRegResult = RegCloseKey(hCurKey) End Function Public Sub SaveSettingLong(ByVal hKey As Long, ByVal _ strPath As String, ByVal strValue As String, ByVal _ lData As Long) Dim hCurKey As Long Dim lRegResult As Long lRegResult = RegCreateKey(hKey, strPath, hCurKey) lRegResult = RegSetValueEx(hCurKey, strValue, 0&, _ REG_DWORD, lData, 4) If lRegResult <> ERROR_SUCCESS Then 'there is a problem End If lRegResult = RegCloseKey(hCurKey) End Sub
These two functions are probably the simplest of the three data types. We know the
length of data that we want to retrieve so we don’t need any extra calls.
Public Function GetSettingLong(ByVal hKey As Long, _ ByVal strPath As String, ByVal strValue As String, _ Optional Default As Long) As Long Dim lRegResult As Long Dim lValueType As Long Dim lBuffer As Long Dim lDataBufferSize As Long Dim hCurKey As Long ' Set up default value If Not IsEmpty(Default) Then GetSettingLong = Default Else GetSettingLong = 0 End If lRegResult = RegOpenKey(hKey, strPath, hCurKey) lDataBufferSize = 4 ' 4 bytes = 32 bits = long lRegResult = RegQueryValueEx(hCurKey, strValue, 0&, _ lValueType, lBuffer, lDataBufferSize) If lRegResult = ERROR_SUCCESS Then If lValueType = REG_DWORD Then GetSettingLong = lBuffer End If Else 'there is a problem End If lRegResult = RegCloseKey(hCurKey) End Function Public Sub SaveSettingLong(ByVal hKey As Long, ByVal _ strPath As String, ByVal strValue As String, ByVal lData As Long) Dim hCurKey As Long Dim lRegResult As Long lRegResult = RegCreateKey(hKey, strPath, hCurKey) lRegResult = RegSetValueEx(hCurKey, strValue, 0&, _ REG_DWORD, lData, 4) If lRegResult <> ERROR_SUCCESS Then 'there is a problem End If lRegResult = RegCloseKey(hCurKey) End Sub
Public Function GetSettingByte( _ ByVal hKey As Long, _ ByVal strPath As String, _ ByVal strValueName As String, _ Optional Default As Variant) As Variant Dim lValueType As Long Dim byBuffer() As Byte Dim lDataBufferSize As Long Dim lRegResult As Long Dim hCurKey As Long If Not IsEmpty(Default) Then If VarType(Default) = vbArray + vbByte Then GetSettingByte = Default Else GetSettingByte = 0 End If Else GetSettingByte = 0 End If lRegResult = RegOpenKey(hKey, strPath, hCurKey) lRegResult = RegQueryValueEx(hCurKey, strValueName, 0&, _ lValueType, ByVal 0&, lDataBufferSize) If lRegResult = ERROR_SUCCESS Then If lValueType = REG_BINARY Then ReDim byBuffer(lDataBufferSize - 1) As Byte lRegResult = RegQueryValueEx(hCurKey, strValueName, 0&, _ lValueType, byBuffer(0), lDataBufferSize) GetSettingByte = byBuffer End If Else 'there is a problem End If lRegResult = RegCloseKey(hCurKey) End Function Public Sub SaveSettingByte(ByVal hKey As Long, ByVal _ strPath As String, ByVal strValueName As String, byData() As Byte) Dim lRegResult As Long Dim hCurKey As Long lRegResult = RegCreateKey(hKey, strPath, hCurKey) lRegResult = RegSetValueEx(hCurKey, strValueName, _ 0&, REG_BINARY, byData(0), UBound(byData()) + 1) lRegResult = RegCloseKey(hCurKey) End Sub
These are probably the hardest of the functions. This is because you cannot pass arrays
to API calls, so you must pass the first array element, and the number of elements. The
retrieve function, initialises the default value, and then makes a call to the
RegQueryValueEx to determine the length of the array. It then initialises an array of
bytes as a buffer to accept the data. The RegQueryValueEx is then called again to collect
the data. The save function is easier, as we just pass the first array element, and
the number in the array.
This function returns a variant containing a byte array. To use it:
Dim mybytes As Variant Dim loopy As Integer mybytes = GetSettingByte(HKEY_CURRENT_USER, _ "CompanyMyApp", "MyBinaryData") If VarType(mybytes) = vbArray + vbByte Then For loopy = 0 To Ubound(mybytes) Debug.Print mybytes(loopy) Next End If
This will print all of the array onto the Debug window.
Conclusion
Well that’s it… a whirlwind tour of the Windows Registry API functions. Hopefully you
followed that, but if not you can contact me,
and there is always the Q and A
forum for more general questions. Next week, we will look at how we can control
certain aspects of Windows from the Registry, and what the Registry can be used for in
your project. We will also look at creating a GetAllSettings function to mimic the VB one.
That’s all for this week…