Microsoft & .NETVisual BasicAccessing the Windows Registry with the API

Accessing the Windows Registry with the API

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

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…

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories