Click here to Skip to main content
15,867,453 members
Articles / Desktop Programming / MFC
Article

Registry API Wrapper

Rate me:
Please Sign up or sign in to vote.
4.92/5 (9 votes)
24 Feb 2000CPOL 216.9K   3.8K   57   72
The Win32 Registry API is far too complex for simple tasks, and all the error checking gets in the way of the real work...
  • Download source files - 29 Kb
  • Download sample application - 113 Kb
  • The problem

    I personally find the Win32 Registry API to be overly complex for performing simple operations. The Win32 Registry API is very "rich". You can do a lot with it, but most of the time you just want to read or write a value. The error checking involved in doing even simple registry operations is enough to hide the meaning of the code. Most of the time you either want the operation to succeed or fail as a whole, you're not interested in handling errors at each stage and performing recovery operations...

    A simple Registry API wrapper class

    I've seen many Win32 Registry API wrapper classes but all of them failed to actually make it easier to use the registry than the API. Most have been simple wrappers which add no value (apart from saving you the bother or having to pass the HKEY to each function...).

    CRegistryKey is essentially just a very simple wrapper around a HKEY. It adds value by converting errors to exceptions, defaulting most of the rarely used parameters with sensible values and providing STL style iterators for sub keys and values. All of the Win32 Registry API is present as member functions, but if you want to check for errors without using exceptions there's a conversion operator that will allow access to the underlying HKEY.

    CRegistryKey was designed to make code like this as easy as possible...

    try 
    	{ 
    	   CRegistryKey key = CRegistryKey(HKEY_LOCAL_MACHINE, _T("SOFTWARE")); 
    	    
    	   // for each sub key of this key... 
    	    
    	   for (CRegistryKey::SubkeyIterator it = key.BeginSubkeyIteraton(); 
    	        it != key.EndSubkeyIteraton(); 
    	        ++it) 
    	   { 
    	      // Dump out the key's name 
    	 
    	      tcout << it.GetName() << endl; 
    	 
    	      // Then display each value  
    	    
    	      CRegistryKey subKey = it.OpenKey(); 
    	    
    	      for (CRegistryKey::ValueIterator val = subKey.BeginValueIteration(); 
    	           val != subKey.EndValueIteration();   
    	 
    	           ++val) 
    	      { 
    	         tcout << " " 
    	<< val.name() << T(" ") << val.valueAsString() 
    	<< endl; 
    	      } 
    	   } 
    	} 
    	catch (CRegistryKey::Exception &e) 
    	{ 
    	   e.MessageBox(); 
    	}

    Remove a few inconsistencies and tidy things up

    RegDeleteKey behaves differently on Win95 and NT. CRegistryKey::DeleteKey( ) acts consistently on both platforms, never removing sub keys, whilst CRegistryKey::DeleteKeyAndSubkeys( ) always removes sub keys.

    In addition, CRegistryKey::OpenKey( ) only opens a key and does not create one, use CRegistryKey::CreateKey( ) to create one and CRegistryKey::CreateOrOpenKey( ) if you don't care.

    Why complicate matters with reference counting?

    CRegistryKey is slightly more complex than just a wrapper. It's actually implemented using the "handle-body idiom", the CRegistryKey object is a handle only the underlying HKEY which is reference counted. This is so that it's easy to pass CRegistryKey 's by value. Since CRegistryKey owns an open HKEY, and since that key is closed when the CRegistryKey object goes out of scope we have to be careful when copying them around. It's not enough to allow the default copy constructor and assignment operators to be used. Allowing that would mean that the underlying HKEY could be closed more than once, as in the example below...

    void DoStuffWithKey(CRegistryKey key1) 
    	{ 
    	   // blah blah blah, do clever stuff with a key...  
    	 
    	   // key1 is closed here when it goes out of scope! 
    	} 
    	 
    	try 
    	{ 
    	   CRegistryKey key1 = CRegistryKey(HKEY_LOCAL_MACHINE, _T("SOFTWARE")); 
    	    
    	   // call a function and pass the key by value... 
    	 
    	   DoStuffWithKey(key1); 
    	 
    	   // key1 is closed here - for the second time!!! 
    	} 
    	catch (CRegistryKey::Exception &e) 
    	{ 
    	   e.MessageBox(); 
    	}

    An alternative could be to duplicate the HKEY using DuplicateHandle( ) in the copy constructor and assignment operator, but that's messy, and unnecessary, and besides it will fail if the HKEY is a key on a remote machine. A better, if slightly more complex, solution is to hold a single representation of the underlying HKEY and only close it when the last CRegistryKey that references it is destroyed.

    To achieve this, we store a counter with the HKEY and increment the counter every time we copy a CRegistryKey that refers to the HKEY , and decrement the counter when we destroy such a CRegistryKey . With this in place there is only ever one copy of the open HKEY no matter how many additional CRegistryKey objects are created from the initial one. What's more, as a user of CRegistryKey we never see any of this complexity - it just works.

    Iterators and templates

    The code that makes it easy to navigate sub keys or values associated with a CRegistryKey is structured in a way that is similar to STL iterators. This makes it easy and intuitive to use, just ++ your way along the list of keys or values. When I was writing these iterators, I realised how much code was similar between the sub key iterator and the value iterator. At first I tried to factor this common code into a common base class. This didn't really work, a lot of the common stuff was in assignment or equality operations which didn't work well as base class members. Next I tried a template base class which took the derived class with the specialist knowledge as its template parameter. Whilst this was better (the assignment and equality ops could be based on the template parameter rather than the base class) it still wasn't ideal as constructor of the iterator has to "prime the pump" by advancing to the first available item. This involved calling the Advance() function which in turn needed derived class functionality in the form of a virtual function call to GetItem() . Of course, a virtual function call from a base class constructor is not going to work in this instance.

    The end result was an iterator template which takes a template parameter to its base class. The base class implements the specialist knowledge and is available at any point in the life of the template derived class. To make life easier for myself I write a very simple abstract interface class which represented the interface required by the template class for it to work. This wasn't required, the template would have worked with any class that implemented the required interface, not just those derived from the abstract interface class, but it made the requirements easier to see. Base classes are quite at liberty to add any additional functionality that they require of the resulting iterator - for example, they might add access functions for parts of the item being iterated over. While I was implementing the iterator requirements base class I realised that all of the handling of the underlying registry key, which was common between both implementations, would need to go here, rather than in the template class. Only if the code were in a base class of the implementation could the implementation use it...

    The resulting classes involved look something like this...

    key-iterator.gif (5392 bytes)

    At the end of the day, for the sake of removing some duplicate code, the iterators are more complex. This complexity is purely an implementation issue, it doesn't leak through into the iterator interface and affect users. Even so, I'd probably do them differently next time...

    Putting it all together

    Now that we have examined all of the pieces our simple Win32 Registry API wrapper class has turned out to be a collaboration of quite a few classes, as can be seen from the diagram below:

    RegistryKey.gif (6528 bytes)

    Only CRegistryKey itself is declared at the namespace level, all other classes are nested within CRegistryKey . This allows for class names to be less specific as they are already scoped within CRegistryKey - for example we can safely call the value class "Value" as the only way to access it is as " CRegistryKey::Value ".

    Known limitations

    At present CRegistryKey doesn't provide wrappers for the following Win32 Registry API calls, this shouldn't cause a problem since you can always access the underlying HKEY to make these calls.

    • RegQueryMultipleValues( )
    • RegQueryValueEx( ) and RegSetValueEx( ) for REG_LINK , REG_MULTI_SZ and REG_RESOURCE_LIST value types.
    • RegQueryInfoKey( ) - Most of the information is not required due to other wrappers.

    Since a CRegistryKey represents an open registry key there's no way to close the key whilst the key is in scope. This hasn't proved to be a problem for me, but if it does you can always assign one of the standard key handle values to your CRegistryKey which will cause the open key to be closed and the standard key to be open - since the standard key handle values appear to be treated differently to normal keys (you don't need to open or close them) this has the desired effect.

    try 
    	{ 
    	   CRegistryKey key = CRegistryKey(HKEY_LOCAL_MACHINE, _T("SOFTWARE")); 
    	 
    	   // Do some key stuff... 
    	 
    	   // Force the key closed... 
    	   key = HKEY_LOCAL_MACHINE; 
    	   // Do other stuff in this scope... 
    	} 
    	catch (CRegistryKey::Exception &e) 
    	{ 
    	   e.MessageBox(); 
    	}

    I personally tend to scope the key so that it remains open whilst it is in scope and then is automatically closed when it goes out of scope... Don't pass the CRegistryKey to the RegCloseKey( ) API as this will cause the key to be closed twice, once by the RegCloseKey( ) call and once when the CRegistryKey goes out of scope or has another key assigned to it.

    See the article on Len's homepage for the latest updates.

    License

    This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


    Written By
    Software Developer (Senior) JetByte Limited
    United Kingdom United Kingdom
    Len has been programming for over 30 years, having first started with a Sinclair ZX-80. Now he runs his own consulting company, JetByte Limited and has a technical blog here.

    JetByte provides contract programming and consultancy services. We can provide experience in COM, Corba, C++, Windows NT and UNIX. Our speciality is the design and implementation of systems but we are happy to work with you throughout the entire project life-cycle. We are happy to quote for fixed price work, or, if required, can work for an hourly rate.

    We are based in London, England, but, thanks to the Internet, we can work 'virtually' anywhere...

    Please note that many of the articles here may have updated code available on Len's blog and that the IOCP socket server framework is also available in a licensed, much improved and fully supported version, see here for details.

    Comments and Discussions

     
    GeneralMemory leaks Pin
    andrewtruckle23-May-08 3:18
    andrewtruckle23-May-08 3:18 
    GeneralRe: Memory leaks Pin
    andrewtruckle23-May-08 3:53
    andrewtruckle23-May-08 3:53 
    GeneralRe: Memory leaks Pin
    andrewtruckle23-May-08 4:40
    andrewtruckle23-May-08 4:40 
    GeneralRe: Memory leaks Pin
    Len Holgate23-May-08 8:53
    Len Holgate23-May-08 8:53 
    GeneralRe: Memory leaks Pin
    Len Holgate23-May-08 8:52
    Len Holgate23-May-08 8:52 
    GeneralRe: Memory leaks Pin
    Len Holgate23-May-08 8:41
    Len Holgate23-May-08 8:41 
    GeneralRe: Memory leaks Pin
    Len Holgate23-May-08 8:47
    Len Holgate23-May-08 8:47 
    GeneralRe: Memory leaks Pin
    andrewtruckle23-May-08 9:10
    andrewtruckle23-May-08 9:10 
    GeneralRe: Memory leaks Pin
    Len Holgate23-May-08 9:22
    Len Holgate23-May-08 9:22 
    GeneralRe: Memory leaks Pin
    andrewtruckle23-May-08 9:26
    andrewtruckle23-May-08 9:26 
    GeneralRe: Memory leaks Pin
    Len Holgate28-May-08 6:13
    Len Holgate28-May-08 6:13 
    GeneralRe: Memory leaks Pin
    andrewtruckle28-May-08 6:43
    andrewtruckle28-May-08 6:43 
    I am using VS2005 but in my code I directly open a CRegistryKey item and then iterate the values.

    try
    {
        CRegistryKey    key = CRegistryKey(HKEY_LOCAL_MACHINE,
                                _T("SOFTWARE\\ODBC\\ODBCINST.INI\\ODBC Drivers"),
                                KEY_READ);
        for (CRegistryKey::ValueIterator val = key.BeginValueIteration();
                val != key.EndValueIteration(); ++val)
        {
            if (bCheck)
            {
                strName = val.GetName();
                strValue = val.AsString();
    
                if (strValue.CollateNoCase(_T("Installed")) == 0)
                {
                    strNameLower = strName;
                    strNameLower.MakeLower();
                    if (strNameLower.Find(_T("(*.mdb)")) != -1)
                    {
                        // A potential driver
                        aryStrDrivers.Add(strName);
                        if (strName.CollateNoCase(_T("Microsoft Access Driver (*.mdb)")) == 0)
                        {
                            strDriver = strName;
                            bCheck = FALSE;
            //              break;
                        }
                    }
                }
            }
        }
    


    Ofcourse, I did not have the bCheck bit. I just broke out of the loop. I think it works for you because you have the outer for loop intercepting the break and clearing it up.

    Also, you have to do the AsString call to create that memory buffer.
    GeneralRe: Memory leaks Pin
    Len Holgate28-May-08 6:54
    Len Holgate28-May-08 6:54 
    GeneralRe: Memory leaks Pin
    Len Holgate28-May-08 6:59
    Len Holgate28-May-08 6:59 
    GeneralRe: Memory leaks Pin
    andrewtruckle28-May-08 7:10
    andrewtruckle28-May-08 7:10 
    AnswerRe: Memory leaks Pin
    Len Holgate28-May-08 7:13
    Len Holgate28-May-08 7:13 
    GeneralRe: Memory leaks Pin
    andrewtruckle28-May-08 7:47
    andrewtruckle28-May-08 7:47 
    QuestionVista problems Pin
    andrewtruckle28-May-07 21:34
    andrewtruckle28-May-07 21:34 
    AnswerRe: Vista problems Pin
    Len Holgate28-May-07 23:14
    Len Holgate28-May-07 23:14 
    GeneralRe: Vista problems Pin
    andrewtruckle28-May-07 23:28
    andrewtruckle28-May-07 23:28 
    GeneralRe: Vista problems Pin
    andrewtruckle28-May-07 23:36
    andrewtruckle28-May-07 23:36 
    GeneralRe: Vista problems Pin
    Len Holgate28-May-07 23:42
    Len Holgate28-May-07 23:42 
    GeneralRe: Vista problems Pin
    andrewtruckle30-May-07 10:12
    andrewtruckle30-May-07 10:12 
    GeneralBug: Iterating through empty value list Pin
    rh_7-Feb-07 1:25
    rh_7-Feb-07 1:25 
    AnswerRe: Bug: Iterating through empty value list Pin
    Len Holgate7-Feb-07 1:44
    Len Holgate7-Feb-07 1:44 

    General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

    Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.