Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Design

Tech Tips


Mar04: Tech Tips

George is a software engineer in the System Design and Verification group at Cadence Design Systems. He can be reached at [email protected].


Cleaning up Component Categories

by Gregory Peet and Matthew Wilson

[email protected]

[email protected]

While developing a sample program for the STLSoft libraries, we came across a rather nasty bug in Microsoft's support for Component Categories. The program is a Component Categories Viewer (available from STLSoft 1.6.5 onwards), and uses the standard component category manager component, created through CLSID_StdComponentCategoriesMgr.

The problem is that some components that have been designed to be marked as "Safe For Scripting and Safe For Initialization" enter these CATIDs into the HKEY_CLASSES_ROOT\Component Categories key. Unfortunately, the CATIDs registered are incorrect. Both component categories are registered missing the closing curly brace of the GUID, as follows:

Safe for scripting

{7DD95801-9882-11CF-9FA9-00AA006C42C4

Safe for initialization

{7DD95802-9882-11CF-9FA9-00AA006C42C4

It's a reasonable guess that the author was using ATL registry scripts, and copied in the erroneous strings as the key names. Further, since those categories would already have been (correctly) registered on the development systems, the authors of the controls would never have noticed the error. These errors are found in the registries of all the systems to which we have access, including NT 4.0, 2000, and XP, so it is probably a widely used component that is causing the problem. Apart from reinstalling a system we cannot know its source. It may even be a component that is installed with the operating system, although we must stress that we have no evidence to its source.

Whatever the source, the result of these bad keys is that a call to ICatInformation::EnumCategories on the standard component category manager balks at the first broken key, returning the misleading error of E_OUTOFMEMORY. This is a truly nasty problem. Of course, you wonder why the component categories manager was not written more robustly; the registry is alterable by any process, so such an erroneous registration is hardly surprising. But the cat's (pardon the pun) out of the bag. Even if Microsoft enhanced the robustness of the component categories manager in subsequent releases, your code that uses the component may run on a system where it is broken.

So what's the solution? Well, since you cannot be sure that the component category manager can work with a corrupt registry, and you cannot be sure of an uncorrupted registry, the only pragmatic answer is to clean the registry before you use it. In this tip we present an API that provides functions for detecting and/or cleansing the HKEY_CLASSES_ROOT\ Component Categories key, which you can (and probably should) call before every use of the component categories manager.

The API consists of the following two typedefs and three functions:

typedef BOOL (WINAPI PfnHandleBadKeyA) (LPCSTR, DWORD);

typedef BOOL (WINAPI PfnHandleBadKeyW) (LPCWSTR, DWORD);

ULONG WINAPI CompCat_FindBadA

(PfnHandleBadKeyA pfn, DWORD param);

ULONG WINAPI CompCat_FindBadW

(PfnHandleBadKeyW pfn, DWORD param);

ULONG WINAPI CompCat_DeleteBad(void);

The CompCat_FindBadA/W() functions take a callback function, and a caller-supplied parameter, and inspect every subkey of the HKEY_CLASSES_ROOT\Component Categories key. For each key found, it is tested (see Listing One) for validity, using the CLSIDFromString() COM API function, and if this test fails, the callback function is called, passing the name of the key, and the callback parameter.

The functions enumerate through the component category keys using the WinSTL (a subproject of STLSoft; http:// winstl.org/) component reg_key_sequence. The value type of the sequence is the reg_key class, on which is called name() to get the subkey name to test with CLSIDFromString() and to pass to the callback function. If the callback function returns FALSE, then the enumeration is terminated, otherwise it goes to completion. In either case, CompCat_FindBadA/W() returns the number of invalid keys found.

The other API function, CompCat_DeleteBad(), is implemented in terms of CompCat_FindBadA(), and an (internal) callback function CompCat_DeleteBadKeyA(). Listing Two shows the implementation of both of these functions. CompCat_DeleteBadKeyA() is itself implemented as a call to Reg_DeleteKey() (described in the next tech tip). The third parameter to Reg_DeleteKey() indicates whether to delete all subkeys. In this case, since we know that the component category key is badly formed, it is not useful to any client process, so it's a pretty safe bet that we can delete it. If you're not comfortable with that, you can stick with CompCat_FindBadA/W() and your own callback method that prompts a user for confirmation.

The API is included in STLSoft 1.7.1 onwards (in the extras directory), and available electronically (see "Resource Center," page 5). Also included is a command-line program, ccc, which can optionally report and/or delete bad keys. Project files for C++ Builder, Digital Mars, Metrowerks CodeWarrior, and Visual C++ are provided.

An Even Better RegDeleteKey

by Matthew Wilson

[email protected]

The May 1999 issue of Windows Developer's Journal included a "Tech Tip" from Fran Heeran, "A Better RegDeleteKey()," that highlighted the inconsistent implementation of RegDeleteKey() between the Windows 9x and NT platforms, and describing a corrective solution, the function wdjRegDeleteKey().

The tip highlighted the fact that on Windows 9x platforms, a call to RegDeleteKey() deletes all subkeys, whereas on Windows NT platforms, the function fails if the specified key has any subkeys. Quite an inconsistency!

wdjRegDeleteKey() handled this issue by recursively deleting subkeys, if any, irrespective of platform so that the call is the semantic equivalent to that implemented on the Windows 9x platforms. A useful tip.

However, there are times when the rejection of a key deletion due to the presence of child keys is desirable, in which case, Fran's powerful function may actually be too powerful, and a function that mimics the NT semantics may be desired.

In addition, there was a slight logic problem in the implementation of wdjRegDeleteKey() in so far as the error code returned to the caller may not actually be the error code that causes the recursive deletion to terminate. Furthermore, it is conceivable, though quite unlikely, that this could lead to an implementation difference on 9x and NT platforms: This is because, at an arbitrary level in the recursion, the function attempts to delete the given key irrespective of whether all its subkeys are successfully deleted. This could still result on the platform difference due to the 9x platform situation of successfully deleting all subkeys when they shouldn't be.

What I describe here is a simple function, Reg_DeleteKey(), that builds on Fran's function to address the issue of high- or low-power setting, and also corrects the slight problem in the initial implementation; see Listing Three.

As with RegDeleteKey() and wdjRegDeleteKey(), Reg_DeleteKey() takes as arguments a root HKEY and the name of a subkey to delete. In addition, it takes a BOOL parameter bRecurse. If bRecurse is True, then it recursively deletes the subkey and all its child subkeys in the same manner as wdjRegDeleteKey(). If bRecurse is False, then it checks that there are no child subkeys of the given subkey, returning the ERROR_KEY_HAS_CHILDREN error code.

The implementation of Reg_DeleteKey() follows a similar format to that of wdjRegDeleteKey(): Open the named key, enumerate and delete its children, delete the key itself. However, there are two key differences.

First, the return type of the RegEnumKeyEx() call is preserved, in case it is required to be returned to the caller in an erroneous condition.

Second, if bRecurse is False the ERROR_KEY_HAS_CHILDREN error code is returned. If bRecurse is True, however, then Reg_DeleteKey() is called for each enumerated child key name.

DDJ

Listing One

/////////////////////////////////////////////////////////////////////////////
// Extract from compcatutil.cpp
// License:     (Licensed under the Synesis Software Standard Source License)
//              http://synesis.com.au/licenses/ssssl.html
//              Copyright (C) 2003, Gregory Peet & Matthew Wilson.
//              All rights reserved.
/////////////////////////////////////////////////////////////////////////////

extern "C"
ULONG WINAPI CompCat_FindBadA(PfnHandleBadKeyA pfn, DWORD param)

{
  using winstl::a2w;
  using winstl::reg_key_a;
  typedef winstl::reg_key_sequence_a  sequence_t;

  ULONG       cInvalid = 0;
  sequence_t  cc_keys(HKEY_CLASSES_ROOT, "Component Categories");
  sequence_t::const_iterator  begin = cc_keys.begin();
  sequence_t::const_iterator  end   = cc_keys.end();

  for(; begin != end; ++begin)
  {
    CLSID           clsid;
    reg_key_a const key(*begin);
    if(CLSIDFromString(a2w(key.name().c_str()), &clsid) != NOERROR)
    {
      if((*pfn)(key.name().c_str(), param))
      {
        ++cInvalid;
      }
      else
      {
        break;
      }
    }
  }
  return cInvalid;
}

Back to Article

Listing Two

/////////////////////////////////////////////////////////////////////////////
// Extract from compcatutil.cpp
// License:     (Licensed under the Synesis Software Standard Source License)
//              http://synesis.com.au/licenses/ssssl.html
//              Copyright (C) 2003, Gregory Peet & Matthew Wilson.
//              All rights reserved.
////////////////////////////////////////////////////////////////////////////

namespace 
{
  BOOL WINAPI CompCat_DeleteBadKeyA(char const *key_name, DWORD param)
  {
    return ERROR_SUCCESS == Reg_DeleteKey((HKEY)param, key_name, true);
  }
}
extern "C"
ULONG WINAPI CompCat_DeleteBad()
{
  winstl::reg_key_a;
  reg_key_a key_root(HKEY_CLASSES_ROOT, "Component Categories");
  return CompCat_FindBadA(CompCat_DeleteBadKeyA, (DWORD)key_root.get_key_handle());
}

Back to Article

Listing Three

LONG Reg_DeleteKey(HKEY hkey, LPCTSTR lpszSubKey, BOOL bRecurse)
{
    /* Open the sub-key. */
    HKEY    hkeySub;
    LONG    lRet = RegOpenKeyEx(hkey, lpszSubKey, 0, 
                                          KEY_READ | KEY_WRITE, &hkeySub);
    if(lRet == ERROR_SUCCESS)
    {
        /* Enumerate all of the decendents of this child. */
        for(; lRet == ERROR_SUCCESS; )
        {
            FILETIME    time;
            TCHAR       szChildKey[MAX_PATH + 1];
            DWORD       dwSize = sizeof(szChildKey) / sizeof(szChildKey[0]);
            lRet = RegEnumKeyEx(hkeySub, 0, szChildKey, &dwSize, 
                                                  NULL, NULL, NULL, &time);
            if(lRet != ERROR_SUCCESS)
            {
                if(lRet == ERROR_NO_MORE_ITEMS)
                {
                    /* The enumeration is complete, so break main 
                     * loop, but indicate success.
                     */
                    lRet = ERROR_SUCCESS;
                    break;
                }
            }
            else
            {
                if(!bRecurse)
                {
                  /* Can't delete keys with children, unless requested to. */
                    lRet = ERROR_KEY_HAS_CHILDREN;
                }
                else
                {
                    /* Recursively delete this child. */
                    lRet = Reg_DeleteKey(hkeySub, szChildKey, bRecurse);
                }
            }
        }
        /* Close the sub-key. */
        RegCloseKey(hkeySub);
        /* Delete the sub-key. */
        if(lRet == ERROR_SUCCESS)
        {
            lRet = RegDeleteKey(hkey, lpszSubKey);
       }
    }
    return lRet;
}

Back to Article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.