Download Source Code: RegistryBrowser.zip - 25.33KB
| Create step-by-step a Registry Browser application, similar to RegEdit/RegEdt32. RegKey and RegValue wrapper classes, that may reflect better the natural low-level structure of the Registry. | ||
Overview
We'll implement a very simple Registry Browser, similar to Microsoft's RegEdit or RegEdt32 applications, that come with Windows. As limitations, we'll not allow modifying operations and we'll expose only the local registry.
The project is just a demo, but it will be used as basic framework for extensibility interfaces and more friendly views, that we will introduce in future articles. For now, just a little practice on .NET's registry classes, exposed by the Microsoft.Win32 namespace.
The Registry is the main system database of Windows' operating system. Its structure is hierarchical and similar to the file system. Nodes are known as keys, simply identified by a label, the key name. Hive keys are top-level predefined keys, which serve as entry points in the Registry. A key can expand into subkeys. Each key is also associated to a dictionary of registry values, as name-value pairs, where the value is limited to some specific data types. Default value is the registry value with no name.
The Good and The Ugly

An object model must be natural and intuitive. It has to represent as close as possible associations between classes. Take a look at the drill-down simplified object model of the File System classes from System.IO namespaces.
Once you call the static method DriveInfo.GetDrives(), you get the collection of all A:, B:, C:, D: etc drives from your computer, that allows easy drill-down navigation to any other file system entity. Each drive has a RootDirectory, then each DirectoryInfo can have subdirectories and files.
The way these classes have been built offer such an easy and intuitive hierarchical navigation both ways. It is so natural for a DirectoryInfo object to return a collection of DirectoryInfo instances for its subfolders. Not names of these folders, from which you can eventually later instantiate DirectoryInfo objects.

Take a look now at the way the Registry object model has been built, in Microsoft.Win32 namespace. You might expect to find a simple hierarchy of registry keys and value objects, because this is what the registry is all about.
You have a Registry class with some static methods and fields. Top level keys are returned by not less then seven individual fields. And a RegistryKey object will not return its subkeys as RegistryKey instances, but names, from which you can eventually "open" one by one RegistryKey objects. While we fully agree that this Open method may lead to some performance issues, this is not a reason to not provide simple properties returning collections of objects, the way they are for the File System model.
And there is no class for a registry value! Yes, they would need one, because each registry value has a name, a specific data type and an atomic or composite value, depending on its type. There are, of course, methods to extract and deal with registry values, in both Registry and RegistryKey classes, but designed in such a bad object-oriented manner. It's like the Microsoft guys who wrapped the Win32 functions for the registry, were not so comfortable with basic object-oriented concepts.
Wrapper Registry Classes
We will create our own wrapper registry classes, which at least will expose a more natural and easy to navigate object model.
As expected, there will be two classes, RegKey and RegValue, one for all registry keys, including the hive keys and the top entry point in the registry itself, the other for registry values.
Just to avoid a separate class for the entry point in the Registry itself, we'll simply use the default public constructor of a RegKey. This fake key, which is not associated with a RegistryKey, will transparently create and return, as subkey, the hive keys. A third, private constructor, will be used for all other regular keys:
// ctor for the registry hive
public RegKey() { _name = "My Computer"; }
// ctor for hive subkeys
private RegKey(RegKey parent, RegistryKey key)
{
Debug.Assert(parent != null && key != null);
_parent = parent;
_name = key.ToString();
_key = key;
}
// ctor for subkeys
private RegKey(RegKey parent, string name)
{
Debug.Assert(parent != null && name != null && name.Length > 0);
_parent = parent;
_name = name;
}We now have a natural hierarchy of subkeys, it doesn't matter if it's about top level hive keys (HKEY_...) or lower level keys. Each key may have KeyValue objects. It's simple and intuitive:
// Collection of all registry subkeys
public RegKey[] Keys
{
get
{
RegKey[] keys = null;
if (_parent == null)
{
keys = new RegKey[5];
keys[0] = new RegKey(this, Registry.ClassesRoot);
keys[1] = new RegKey(this, Registry.CurrentConfig);
keys[2] = new RegKey(this, Registry.CurrentUser);
keys[3] = new RegKey(this, Registry.LocalMachine);
keys[4] = new RegKey(this, Registry.Users);
//keys[5] = new RegKey(this, Registry.DynData);
//keys[6] = new RegKey(this, Registry.PerformanceData);
}
else if (this.RegistryKey != null)
{
string[] names = _key.GetSubKeyNames();
Array.Sort(names);
keys = new RegKey[names.Length]; //_key.SubKeyCount
for (int i = 0; i < names.Length; i++)
keys[i] = new RegKey(this, names[i]);
}
return keys;
}
}
// collection of all registry values
public RegValue[] Values
{
get
{
RegValue[] values = null;
if (_parent != null && this.RegistryKey != null)
{
string[] names = _key.GetValueNames();
values = new RegValue[names.Length];
for (int i = 0; i < names.Length; i++)
values[i] = new RegValue(names[i],
_key.GetValue(names[i]),
_key.GetValueKind(names[i]));
}
return values;
}
}Why registry values deserved a separate class, RegValue, it can be seen in the implementation of the ToString method override. Registry values are formatted based on their data type, and the display value returned by this method is similar to the one used in RegEdit/RegEdt32 views.
// formatted text of the atomic value
public override string ToString()
{
switch (_kind)
{
case RegistryValueKind.String:
return (_value == null ? "(value not set)"
: _value.ToString());
case RegistryValueKind.Binary:
if (_value == null || (_value as byte[]).Length == 0)
return "(zero-length binary value)";
StringBuilder sb = new StringBuilder();
foreach (byte value in (_value as byte[]))
sb.AppendFormat("{0:x2} ", value);
return sb.ToString();
case RegistryValueKind.DWord:
return string.Format("0x{0:x8} ({1})",
Convert.ToInt32(_value), Convert.ToInt32(_value));
case RegistryValueKind.MultiString:
return (_value == null ? "" : _value.ToString());
case RegistryValueKind.ExpandString:
return (_value == null ? "" : _value.ToString());
}
return _value.ToString();
}
To see how easy it is now to drill-down and navigate through object instances in this hierarchy, load the compiled assembly into the generic Instance Browser, an application created and discussed recently in another article of our magazine.
From XtractPro.Databases namespace, instantiate a RegKey object. This gives you access to the collection of hive keys, through the Keys array.
From these new RegKey instances, drill down on both Keys and Values, from any other RegKey object. Simple and easy, something that was not possible with the way registry classes have been implemented in Microsoft.Win32 namespace.
In the next chapter, we provide a step-by-step tutorial about implementing the UI. It's trivial and, if you're already familiar with Windows.Forms, you can skip it.