Hardcore Clarion - Using The Registry

by Paul Attryde

Published 1999-01-01    Printer-friendly version

Download the source

With the introduction of Windows 95 and Windows NT, Microsoft introduced a new concept for the storing of system and application configuration data - the registry.

The registry was meant to do away with all those little pesky .INI files that were laying around your hard disk. Instead, all of the settings would be conveniently stored in one file - convenient, that is, until it became corrupted and you were unable to boot your computer.

However, that’s another story - I’m sure we’ve all been there, so it needs little explanation from me. This month I’m going to explain how to access the registry from within your Clarion programs

Before I do, let me try to explain a little how the registry is organised. There are plenty of third party books out there which go into great detail, but a quick grounding seems appropriate.

The registry stores data in a hierarchically structured tree. Each node in the tree is called a key. Each key can contain both subkeys and data entries called values.

If you compare this to a typical hard disk, you can see that a key is similar to a directory, and a value is a file name. Keeping with this analogy, the contents of the value are the contents of the data file.

Values can be of basically two different types (strings or numbers), with variations thereof (Unicode, null-terminated, big-endian, little-endian etc). It is recommended that if you have a large amount of data (>2K) then the data should be stored as a file and referred to by using a key in the registry rather than being stored as a value. Executable binary code should never be stored in the registry.

There are a number of root keys in the registry which all keys are subkeys of. These main keys are:

HKEY_CLASSES_ROOT Registry entries subordinate to this key define types (or classes) of documents and the properties associated with those types. Data stored under this key is used by Windows shell applications and by object linking and embedding (OLE) applications.
HKEY_CURRENT_USER Registry entries subordinate to this key define the preferences of the current user. These preferences include the settings of environment variables, data about program groups, colors, printers, network connections, and application preferences.
HKEY_LOCAL_MACHINE Registry entries subordinate to this key define the physical state of the computer, including data about the bus type, system memory, and installed hardware and software.
HKEY_USERS Registry entries subordinate to this key define the default user configuration for new users on the local computer and the user configuration for the current user

You may well find other keys in the root, depending on your OS. Win95/98 and Win2000 both have OS-specific keys which I’ll not bother covering here - I’ll leave that as an exercise for you to complete. Most of the information we’re going to be reading, and all of that which you’ll ever need to write, is in one of the 4 base keys mentioned here.

Writing code . . .

Now that we have a vague understanding of how the registry is organised, let’s examine some data. As is the case with lots of things, reading data is a damn sight easier than writing it, so for this example I’m going to write a small Clarion application that displays a list of all the installed software on a given computer. Obviously we’re going to need to know the name of the key that stores this, and because we’re clever programmers we know it’s at HKLM\Software\Microsoft\Windows\CurrentVersion\Uninstall

Now, this list is also available in the ‘Add-Remove Programs’ control panel, so at first glance this seems a pointless exercise - until I point out that there are normally lots more packages listed, and that the control panel doesn’t display them all, just filters out those that don’t have a DisplayName value.

Our first task is to prototype the registry functions we intend to use. Using our trusty MSDN we know that there are 38 functions concerning the registry - luckily for us, we only need 4 of them:

RegOpenKeyEx(Long,Long,Long,Long,Long),Long,Pascal,Raw,Name('RegOpenKeyExA')

RegCloseKey(Long),Long,Pascal,Raw

RegEnumKey(Long,Long,Long,Long),Long,Pascal,Raw,Name('RegEnumKeyA')

RegEnumValue(Long,Long,Ulong,Ulong,Ulong,Ulong,Ulong,Ulong),Long,Pascal,Raw,Name('RegEnumValueA')

As you can see from the prototypes, I’ve decided to use the ANSI equivalents, but if you really want the extra hassle you’re quite welcome to use the Unicode version instead.

One thing to be aware of is that although the procedures are prototyped exactly the same under Win95/98 and Win2000, they can behave differently, especially when deleting keys. The REGDELETEKEY() function (which we won’t actually be using in this article) automatically deletes all sub-keys under Win95/98 - it doesn’t under Win2000.

The first thing we need to do in our sample application is open the base key that we already know. We do this by using the REGOPENKEYEX() function:

Loc:KeyName = 'Software\Microsoft\Windows\CurrentVersion\UnInstall'
If RegOpenKeyEx(HKEY_LOCAL_MACHINE,|
                    Address(Loc:KeyName),|
                    0,|
                    Key_Query_Value,|
                    Address(Loc:KeyHandle)) = ERROR_SUCCESS
     ! OK
End

When dealing with the registry, get used to passing the base key as the one parameter and the rest of the key as another (it makes it easy to change base keys without any string manipulation). What we’re doing here is opening the key with sufficient access rights to enumerate all sub-keys and values. We pass in the address of the string containing the key to open, and the address of the variable that’s going to hold the handle of the open key. We need this handle when we work our way through all the sub-keys.

Now that we’ve opened the base key, we have to enumerate this key to find all it’s sub-keys (each application that is installed has it’s own sub-key). Once we have that key-name, we have to open the sub-key and enumerate all the values in that key. As you can probably guess, for complex operations you will quickly end up with keys, sub-keys, sub-sub-keys and so on.

To enumerate all the sub-keys we use the RegEnumKey function.

RegEnumKey(Loc:KeyHandle,Loc:SubKeyLoop,Address(Loc:SubKeyName),100)

It takes the handle of the base key we’ve just opened, the number of the key that we want, a string to hold the name of the key, and an integer specifying the length of the string. To find all the sub-keys for a key, simply place a call to this function inside a loop and increase the number of the key until the return value is ERROR_NO_MORE_ITEMS (a constant of 259).

If the call succeeds, it returns 0 and the string contains the name of the sub-key. We have to concatenate this string with the original to find the new fully-qualifed name of the sub-key, which we then pass to another call to REGOPENKEYEX().

Once we’ve opened the sub-key successfully, we need to enumerate all the values within the key. For this we use the REGENUMVALUE() function:

Loc:ValueNameStrSize = 50
Loc:ValueDataStrSize = 500
Loc:ValueDataType = Reg_SZ
x# = RegEnumValue( Loc:SubKeyHandle,|
                             Loc:ValueLoop,|
                             Address(Loc:ValueNameStr),|
                             Address(Loc:ValueNameStrSize),|
                             NULL,|
                             Address(Loc:ValueDataType),|
                             Address(Loc:ValueDataStr),|
                             Address(Loc:ValueDataStrSize))

This is similar to using REGENUMKEY() - it takes the handle to the sub-key we just opened, and uses a numeric counter to work through all the values in the sub-key. Just like REGENUMKEY() it returns ERROR_NO_MORE_ITEMS when we run out of values. It also takes as parameters the address of the string to put the value name in, the length of this string, the address of the variable which holds the data type, and the address of the string containing the data itself, and the length of this second string.

So what do we end up with? Something similar to this:

Loc:KeyName = 'Software\Microsoft\Windows\CurrentVersion\UnInstall'
  If RegOpenKeyEx(HKEY_LOCAL_MACHINE,|
           Address(Loc:KeyName),|
           0,|
           Key_Query_Value,|
           Address(Loc:KeyHandle)) <> ERROR_SUCCESS Then
     Message('Unable to open registry key','User Notification')
   Else
     Loc:SubKeyLoop = 1
     Loop
       Case RegEnumKey(Loc:KeyHandle,Loc:SubKeyLoop,Address(Loc:SubKeyName),100)
       Of Error_Success
         Loc:SubKeyLoop += 1
         Loc:Keyname = 'Software\Microsoft\Windows\CurrentVersion\UnInstall\' & Clip(Loc:SubKeyName)
         Case RegOpenKeyEx(HKEY_LOCAL_MACHINE,|
                    Address(Loc:KeyName),|
                    0,|
                    Key_Query_Value,|
                    Address(Loc:SubKeyHandle))
         Of Error_Success
            Rq:Value=-1;RQ:DataString = Clip(Loc:SubKeyName);Add(RegistryQ)
            Loc:ValueLoop = 0
            Loop
              Loc:ValueNameStrSize = 500
              Loc:ValueDataStrSize = 500
              Loc:ValueDataType = Reg_SZ
              x# = RegEnumValue( Loc:SubKeyHandle,|
                         Loc:ValueLoop,|
                         Address(Loc:ValueNameStr),|
                         Address(Loc:ValueNameStrSize),|
                         NULL,|
                         Address(Loc:ValueDataType),|
                         Address(Loc:ValueDataStr),|
                         Address(Loc:ValueDataStrSize))
              Case x#
              Of Error_Success
                Loc:ValueLoop += 1
                If Clip(Loc:ValueNameStr) = '' Then
                   Loc:ValueNameStr = '{{Default}'
                End
                If Clip(Loc:ValueDataStr) = '' Then
                   Loc:ValueDataStr = '<<NONE>'
                End
                Rq:Value=2;RQ:DataString = Clip(Loc:ValueNameStr);Add(RegistryQ)
                Rq:Value=3;RQ:DataString = Clip(Loc:ValueDataStr);Add(RegistryQ)
              Of Error_No_More_Items
                Break
              Else
                Message('Unable to enumerate value for key "' & Clip(Loc:SubKeyName) |
                                                              & '"','User Notification')
                Break
              End
            End
            If RegCloseKey(Loc:SubKeyHandle) <> ERROR_SUCCESS then
               Message('Unable to close registry key','User Notification')
            End
         Else
            Message('Unable to open sub-key ' & Clip(Loc:SubKeyName),'User Notification')
         End
       Of Error_No_More_Items
         Break
       Else
         Message('Unable to enumerate key','User Notification')
         Break
       End
     End
     If RegCloseKey(Loc:KeyHandle) <> ERROR_SUCCESS then
        Message('Unable to close registry key','User Notification')
      End
    End

This code should read the registry and build a queue of all programs installed on your machine, and all of their respective values. If you compare the list this code builds to that of the control panel mentioned earlier you can see that the registry contains a lot more information than you would normally see.

As I mentioned at the beginning, reading the registry is a lot easier than writing to it, mainly because you have to create the key if doesn’t exist before you can open it. That’s not actually that difficult to do, just another thing to slow you down in coding the boring stuff and taking up valuable development time. So, in addition to this months download of the source code mentioned this month, there’s also an added bonus . . .

Download

This month’s download includes some code I wrote to access the registry. It’s hand-coded source for a DLL called REGLIB that contains 2 registry versions of GetIni and PutIni, called GetRegistry and PutRegistry.

They both take the same number of parameters as their INI file equivalents, but do work slightly differently. GetRegistry takes 4 parameters - 2 strings, an optional string, and a long, and returns a string containing the required values:

String1 The name of the key the value is in.

String2 The name of the value we are looking for.

String3 The default value to return if the required value is not found.

Long1 The base key to use

PutRegistry also accepts 4 paramters - a string, 2 optional strings, and a long:

String1 The name of the key the value is in.

String2 The name of the value we are looking for.

String3 The data to put into the value

Long1 The base key to use

If the 3rd parameter (the data) is omitted or is an empty string, the value will be deleted from the key.

If the 2nd parameter (the value) is omitted or is an empty string, the entire key will be deleted (with the understanding that all sub-keys must have been deleted first when running under Windows NT).

Hopefully these should be of some use in cutting down development time, and should make it easier for Clarion developers to use the registry to store their configuration settings.

Summary

Using the registry needn’t be as daunting as it first appears. With a little background, it’s as easy as using INI files, and just because of the complexity you can be happy in the knowledge that this time 99% of your users won’t be hacking away and changing values they aren’t meant to!

Next Time …

Using Simple MAPI to email-enable your Clarion application.

And Remember …

As always, the best place for information is MSDN. If you don't suscribe, you can still get to the information via the web at http://msdn.microsoft.com/

Printer-friendly version