WindowsDevCenter.com
oreilly.comSafari Books Online.Conferences.

advertisement


AddThis Social Bookmark Button

Reading and Writing Registry Values with Visual Basic
Pages: 1, 2

Retrieving Unknown Registry Values

Very frequently, we're even less sure of the basic items of information that we need to access Registry values. Possibly we don't know the precise number and names of the values we want, or we don't know their precise data type, or we have no real idea of how long they are. This is frequently the case, for instance, when working with most recently used (MRU) lists, which often are stored in the Registry. Although there are a number of implementations for MRUs, most commonly the name of a file is assigned a simple, one-letter value name (a, b, c, and the like), and an MRUList named value keeps track of the entries by simply listing their names in order. For instance, the string stored to MRUList might be dcabe, indicating that the value stored to d was most recently used, followed by the value stored to c, and so on. In our case, we'll build a routine that reads the MRU list for the Windows Run dialog, which is accessed by selecting the Run option from the Start menu.



When our uncertainly about Registry values extends to most of the values of a key, as it does in this case, we can begin by calling RegQueryInfoKey to collect three useful items of information about the key's values. Its syntax is:

Public Declare Function RegQueryInfoKey Lib "advapi32.dll" Alias "RegQueryInfoKeyA" ( _
   ByVal hKey As Long, _
   ByVal lpClass As String, _
   lpcbClass As Long, _
   ByVal lpReserved As Long, _
   lpcSubKeys As Long, _
   lpcbMaxSubKeyLen As Long, _
   lpcbMaxClassLen As Long, _
   lpcValues As Long, _                  ' Number of values
   lpcbMaxValueNameLen As Long, _        ' Length of longest value name
   lpcbMaxValueLen As Long, _            ' Length of longest value data 
   lpcbSecurityDescriptor As Long, _
   lpftLastWriteTime As FILETIME _
) As Long

We can substitute a null or a null string (depending on the data type) for arguments in which we're not interested. The function tells us the following:

  • How many values the key contains, including the unnamed value
  • The number of characters in the longest named value, not including a terminating null character
  • The number of bytes of data, not including a possible terminating null character

In the case of the MRU list, calling RegQueryInfoKey once can save us from having to call RegQueryValueEx twice for each value, since we don't know the amount of data that each value contains.

In building our MRU list routine, once we know the longest value name and the size of the buffer we need to create, we can begin to read values in either of two ways. One way (which we won't use here) is to retrieve all of the values in the key by calling RegEnumValue. Its syntax is:

Public Declare Function RegEnumValue Lib "advapi32.dll" Alias "RegEnumValueA" ( _
   ByVal hKey As Long, _			' Handle to open key	
   ByVal dwIndex As Long, _              ' Index (0 to total - 1 
   ByVal lpValueName As String, _        ' name of value
   lpcbValueName As Long, _              ' kength of value name 
   ByVal lpReserved As Long, _           ' reserved, must be 0
   lpType As Long, _                     ' data type  
   lpData As Byte, _                     ' value data 
   lpcbData As Long _			 ' length of value data	
) As Long

The major disadvantage of RegEnumValue is that it takes a byte array as the lpdata argument, and so requires that we reassemble our data from the byte array. While this is easy to do, it offers horrendous performance for keys with large numbers of values or for values with large amounts of data. A better alternative is to call RegEnumValue to get the value's name and, if necessary, its data type, and then to use this information to call RegQueryValueEx. The following code fragment illustrates this:

' Open registry key
RegOpenKeyEx HKEY_CURRENT_USER, sPath, 0, KEY_READ, hKey

' Collect information on key's values
RegQueryInfoKey hKey, "", 0, 0, 0, 0, 0, nValues, lValueName, lValueData, 0, ft

' Enumerate values
For nCtr = 0 To nValues - 1
   ' Reinitialize buffer on each iteration
   lName = lValueName + 1
   sName = Space(lName)
   
   ' Get enumerated value
   RegEnumValue hKey, nCtr, sName, lName, 0, lType, ByVal 0, 0
   
   ' Trim value name
   sName = Left(sName, lName)
   
   ' If necessary, handle data types
   ldata = lValueData + 1
   sData = Space(ldata)
   
   ' Get data
   RegQueryValueEx hKey, sName, 0, 0, ByVal sData, ldata
      
   ' Trim string data
   sData = Left(sData, ldata)
   
   ' Process value name and data
   
   nCtr = nCtr + 1
Next

RegCloseKey hKey

However, let's take a look at what we know about the values we want to retrieve. We know that they are all strings (of type REG_SZ). Once we call RegQueryInfoKey, we can determine how large a buffer must be allocated to accommodate the data. And we know the name of one value, MRUList, whose data is an ordered list of the names of the values that we want to retrieve. This means that we can omit enumerating the values and implement our routine by calling RegQueryValueEx for each key that we want to retrieve. The code appears as follows:

Public Sub ShowRunMRU()

   Dim iCtr As Integer
   Dim hKey As Long
   Dim nValues As Long, lValueData As Long
   Dim lData As Long
   Dim sPath As String
   Dim sList As String
   Dim sMsg As String
   Dim sData As String
   Dim sName As String
   Dim ft As FILETIME


   sPath = "Software\Microsoft\Windows\CurrentVersion\Explorer\RunMRU"

   ' Open registry key
   RegOpenKeyEx HKEY_CURRENT_USER, sPath, 0, KEY_READ, hKey

   ' Collect information on key's values
   If RegQueryInfoKey(hKey, "", 0, 0, 0, 0, 0, nValues, 0, lValueData, _
                      0, ft) <> ERROR_SUCCESS Then
      sMsg = "Run MRU list not found"
   Else
      ' initialize data buffer
      lData = lValueData + 1
      sData = Space(lData)
      ' Retrieve MRUList Value
      If RegQueryValueEx(hKey, "MRUList", 0, 0, ByVal sData, lData) <> ERROR_SUCCESS Then
         sMsg = "Run MRU list not found"
      Else
         ' Trim MRU list data
         sList = Left(sData, lData - 1)
      
         For iCtr = 1 To Len(sList)
            ' Get value name
            sName = Mid(sList, iCtr, 1)
            ' Reinitialize buffer on each iteration
            lData = lValueData + 1
            sData = Space(lData)
   
            ' Get data
            RegQueryValueEx hKey, sName, 0, 0, ByVal sData, lData
      
            ' Trim string data
            sData = Left(sData, lData - 1)
      
            sMsg = sMsg & sData & vbCrLf
         Next
      End If

      RegCloseKey hKey

   End If
   
   ' Display MRU list
   MsgBox sMsg, vbOKOnly, "Run MRU List"
   
End Sub

Setting a Registry Value

To write a value to the Registry, you call the RegSetValueEx function. Its syntax is:

Public Declare Function RegSetValueEx Lib "advapi32.dll" Alias "RegSetValueExA" ( _
   ByVal hKey As Long,  _	        	' Handle to registry key
   ByVal lpValueName As String, _        ' Name of value
   ByVal Reserved As Long, _             ' Reserved, must be 0 
   ByVal dwType As Long, _               ' Registry data type constant
   lpData As Any, _                      ' Value's data 
   ByVal cbData As Long _                ' Length in bytes of data
) As Long

The function is fairly straightforward, as far as Registry functions go. A few comments are in order, though:

  • If you are writing an unnamed value to the Registry, lpValueName should be a null string. You also have to be careful about platform: On Windows 95/98/ME, the unnamed value must be of type REG_SZ. On Windows NT/2000/XP, it can be of any valid type.

  • Strings (REG_SZ and REG_EXPAND_SZ) must be null-terminated. In string arrays (REG_MULTI_SZ), each array element must be null-terminated, and the array as a whole must be terminated with two nulls (one for the last element of the array, and one for the array itself). Visual Basic, however, takes care of adding the final terminating null when we pass the string by value.

  • cbData, the count of bytes in the data, must include the terminating null character or characters for Registry string data.

  • The function will create a new value if one does not already exist. If it does exist, it will overwrite the old value with the new one. Because of the possibility of accidentally overwriting data, in some cases you may want to call RegQueryValueEx to check whether a Registry value exists.

The following code illustrates the use of the major Registry value types used by developers.

' Defined to save as REG_BINARY data
Public Type AppFontInfo
   Name As String * 15
   Size As Integer
   Bold As Boolean
   Italic As Boolean
End Type


Public Sub WriteRegistryValues()

   Dim hKey As Long
   Dim lDisp As Long
   Dim sPath As String
   Dim sa As SECURITY_ATTRIBUTES

   Dim sValueName As String

   sPath = "Software\MyCompany\MyApp\Settings"

   If RegCreateKeyEx(HKEY_LOCAL_MACHINE, sPath, 0, vbNullString, REG_OPTION_NON_VOLATILE, _
                     KEY_ALL_ACCESS, sa, hKey, lDisp) <> ERROR_SUCCESS Then
      MsgBox "Unable to create registry key."
      Exit Sub
   End If

   ' Create a REG_DWORD value
   Dim dword As Long

   sValueName = "MRUItems"
   dword = 5
   
   RegSetValueEx hKey, sValueName, 0, REG_DWORD, dword, Len(dword)
   
   ' Create a REG_BINARY value
   Dim fi As AppFontInfo
   fi.Name = "Arial"
   fi.Size = 12
   fi.Bold = False
   fi.Italic = False
   
   sValueName = "DefaultFont"
   
   RegSetValueEx hKey, sValueName, 0, REG_BINARY, fi, Len(fi)
   
   ' Create a REG_SZ value
   Dim sValue As String
   
   sValue = "GraphicsViewer"
   
   sValueName = "AddIn"
   
   RegSetValueEx hKey, sValueName, 0, REG_SZ, ByVal sValue, Len(sValue) + 1
   
   ' Create a REG_EXPAND_SZ value
   Dim sLibPath As String
   
   sLibPath = "%windir%\twain_32.dll"
   
   sValueName = "Library"
   
   RegSetValueEx hKey, sValueName, 0, REG_EXPAND_SZ, ByVal sLibPath, Len(sLibPath) + 1
   
   'Create a REG_MULTI_SZ value
   Dim ColorString As String
   Dim Color As Variant, Colors As Variant
   
   Colors = Array("Yellow", "Black", "Purple")
   
   ' Form array string
   For Each Color In Colors
      ColorString = ColorString & Color & Chr(0)
   Next
   
   sValueName = "Colors"
   
   RegSetValueEx hKey, sValueName, 0, REG_MULTI_SZ, ByVal ColorString, Len(ColorString) + 1
   
   ' Close registry key
   RegCloseKey hKey

End Sub

Click for larger view
Figure 2. Registry after running WriteRegistryValues procedure (You can click on the screen shot to open a full-size view.)

Ron Petrusha is the author and coauthor of many books, including "VBScript in a Nutshell."


Return to WindowsDevCenter.com.