OLE Controls: Registration

Dale Rogerson
Microsoft Developer Network Technology Group

Created: October 6, 1994

Click to open or copy the files in the CTLREG sample application for this technical article.

Abstract

This article explains how an application or setup program can register and unregister OLE Controls using OLE's self-registration facility. Registering and unregistering an OLE Control requires examining the control's version information and its registry entry.

Introduction

Before you can use an OLE Control, you must register it with the system. Registering an OLE Control places information about the control in the system registry. Once the control has been registered, applications and development environments can search the registry to determine which controls have been installed. Unlike a Visual Basic® custom control (VBX), an OLE Control does not need to be registered by each application that uses it. Once the control has been registered with the system, any application can find it.

Note   In this article, I use the term "registry" to refer to two implementations of the configuration utility: the registration database in Microsoft® Windows® 3.1 and the Registry in Windows NT™.

The OLE Control Developer's Kit (CDK) includes a tool called REGSVR.EXE (or REGSVR32.EXE for 32-bit systems) that can register and unregister controls. When you install the CDK, it adds two commands—Register Control and Unregister Control—to the Visual Workbench Tools menu (in Visual C++™ version 1.51 or 2.0). These commands call REGSVR.EXE to register or unregister the control built by the current project. If the current project does not build a control, the attempt to register or unregister it fails.

Many control containers or installation programs will prefer to handle control registration themselves instead of spawning REGSVR.EXE to do the job. These applications can adopt the method that REGSVR.EXE uses to register controls, and provide additional functionality.

The OLE Controls specification (OLE Control Developer's Kit, User's Guide and Reference, Part Four: Appendixes, Appendix D: "OLE Controls Architecture") dictates that OLE Controls must be self-registering, that is, an OLE Control must be able to register and unregister itself. REGSVR.EXE takes advantage of this requirement and gets the control to do the registration work.

Luckily, ControlWizard, a tool included with the CDK, creates controls that support self-registration. In this article, we will discuss how to programmatically register and unregister controls that support self-registration.

Self-registration simplifies the process of registering a control, because the control does the actual work of adding the necessary information to, or removing it from, the registry. The container need only call the appropriate function exported from the .OCX file to get the control to register itself.

However, our container will still need to do some work. Before we can tell the control to register or unregister itself, we have to make sure that it supports self-registration. In addition, a container may want to display all registered controls and let the user select the control to unregister. To provide this functionality, we need to search the registry for all registered controls. Some containers may also want to display the toolbox bitmap associated with a control. The CTLREG sample application provides this functionality. It displays the names of registered controls and their bitmaps in an owner-drawn list box. (The Test Container included in the CDK lists registered control names but does not display their bitmaps.)

We will discuss these issues in detail in this technical article. This article expands on the information about installation and registration in the OLE Control Developer's Kit User's Guide and Reference, Part Four: Appendixes, Appendix D: "OLE Controls Architecture".

CTLREG Sample Application

The CTLREG sample application included with this article registers and unregisters OLE Controls. To register a control, choose the Register command from the Controls menu. This command brings up a dialog box that shows all of the files in the current directory that have the .OCX extension. You can also choose to view files with a .DLL extension.

To unregister a control, choose the Unregister command from the Controls menu. This command displays an owner-drawn list box with a list of the currently installed controls and their toolbox bitmaps. (Figure 1 shows the controls installed on my system.) It is possible to delete an OLE Control's .OCX file without removing its corresponding registry information. The "Exist" keyword in the dialog box identifies controls whose .OCX files exist. Controls that can be inserted in a standard OLE container are marked with the word "Insert." As you can see in Figure 1, I modified the Spindial control so that it could be inserted in an OLE container. The 16-bit version of CTLREG displays 16-bit controls only, and the 32-bit version of CTLREG displays 32-bit controls only.

Figure 1. The Unregister dialog box in CTLREG32.EXE

Clicking the Cleanup button removes registry entries for controls whose .OCX files have been deleted. More on this in the section on "Cleaning Up the Registry" at the end of this article.

Note   The code fragments in this article reflect the state of the CTLREG sample code when the article was written. These code fragments may differ slightly from the code in the CTLREG sample files provided with this article.

Self-Registration and Version Information

All OLE servers have to be registered before they will work. OLE servers such as Microsoft Excel and Word register themselves when they run stand-alone. The same is true of servers built with AppWizard. Self-registration makes sense for a server such as Microsoft Word that is also a stand-alone application. However, almost all OLE Controls are implemented as DLLs, and you can't "run" a DLL. So, a different method of self-registration had to be devised. ControlWizard builds controls that support self-registration by default.

What Is Self-Registration?

An OLE Control that supports self-registration exports the following two functions:

STDAPI DllRegisterServer(void) ;
STDAPI DllUnregisterServer(void) ;

DllRegisterServer adds or updates registry information for all the classes implemented by the DLL. DllUnregisterServer removes information from the registry. Once you know the name of the file, calling these functions is pretty trivial.

Here is the C++ code for registering a control (without error correction):

#include <stdole.h> 
.
.
.

typedef HRESULT (STDAPICALLTYPE *CTLREGPROC)() ; // Requires stdole.h

.
.
.

// Path to OLE Control in m_strPathName
HMODULE hModule = ::LoadLibrary(m_strPathName) ;
CTLREGPROC DLLRegisterServer =
      (CTLREGPROC)::GetProcAddress(hModule,"DllRegisterServer" ) ;
DLLRegisterServer() ;
::FreeLibrary(hModule) ;

The code that I used in CTLREG to register a control is shown below. In CTLREG, the CCtlReg class, which is in the CCLTREG.CPP file (notice the initial "C" in the filename), performs registration and unregistration tasks. When an instance of CCtlReg is created, it is given the path to the OLE control's .OCX file in the constructor.

BOOL CCtlReg::Register()
{
   BOOL bResult = FALSE ;
   if (SupportsSelfRegister())
   {
      // Path to OLE Control in m_strPathName
      HMODULE hModule = ::LoadLibrary(m_strPathName) ;
      if (LOADLIBRARY_FAILED(hModule)) return FALSE ;

      CTLREGPROC DLLRegisterServer =
            (CTLREGPROC)::GetProcAddress(hModule,"DllRegisterServer" ) ;
      if (DLLRegisterServer != NULL)
      {
         HRESULT regResult = DLLRegisterServer() ;
         bResult = (regResult == NOERROR) ; 
      }
      ::FreeLibrary(hModule) ;
   }
   return bResult ;
}

LOADLIBRARY_FAILED is a macro defined as follows:

#ifdef _WIN32
#define LOADLIBRARY_FAILED(x) (x == 0)
#else
#define LOADLIBRARY_FAILED(x) (x <= HINSTANCE_ERROR)
#endif

Don't I Have to Initialize OLE?

Yes, you do. The DLLRegisterServer and DLLUnregisterServer calls require OLE, which must be initialized. If your application is already a container or server, it initializes OLE. However, if your application (like most installation programs) does not use OLE, it will need to initialize OLE.

If you build an application with AppWizard and do not choose any OLE options, you will need to do the following before you can register and unregister controls:

  1. Include AFXOLE.H in STDAFX.H.

  2. Link to OLE2.LIB.

  3. Add the following lines to CTLREG.CPP:
    BOOL CCtlregApp::InitInstance()
    {              
       // Initialize OLE libraries
       if (FAILED(OleInitialize(NULL)))
       {
          AfxMessageBox("OLE initialization failed. 
                         Make sure that the OLE libraries are the
                         correct version.") ;
          return FALSE ;
       }
       // Standard initialization
       ...
    }
    

Does a Control Support Self-Registration?

Before calling DllRegisterServer or DllUnregisterServer, we verify that the control supports self-registration. CCtlReg::Register verifies this by calling the CCtlReg::SupportsSelfRegister function, which I wrote for this purpose.

Ideally, we should be able to determine whether a control supports self-registration without actually loading the control. Loading the control will execute LibMain, which may result in undesirable side-effects. We use the version information for the control to determine whether the control supports self-registration without loading the control and executing LibMain.

The Microsoft Windows version 3.1 operating system includes a set of functions that standardize version information for .DLLs and .EXEs. These functions simplify the difficulties installation programs face in determining what is currently installed and what should be replaced. The version information is included in .EXEs and .DLLs as a special resource type. The functions that manipulate the version information are exported from VERS.DLL. Applications that call these functions need to link with VERSION.LIB. (If you are still developing for a 16-bit system, you should link with VER.LIB instead.)

The version information for the CDK Spindial sample control (from the SPINDIAL.RC2 file) is shown below.

#ifdef _WIN32
#include "winver.h"
#else
#include "ver.h"
#endif

VS_VERSION_INFO     VERSIONINFO
  FILEVERSION       1,0,0,1
  PRODUCTVERSION    1,0,0,1
  FILEFLAGSMASK     VS_FFI_FILEFLAGSMASK
#ifdef _DEBUG
  FILEFLAGS         VS_FF_DEBUG|VS_FF_PRIVATEBUILD|VS_FF_PRERELEASE
#else
  FILEFLAGS         0 // final version
#endif
#ifdef _WIN32
  FILEOS            VOS__WINDOWS32
#else
  FILEOS            VOS__WINDOWS16
#endif
  FILETYPE          VFT_DLL
  FILESUBTYPE       0   // not used
BEGIN
   BLOCK "StringFileInfo"
   BEGIN
#ifdef _WIN32
      BLOCK "040904B0" // Lang=US English, CharSet=Unicode
#else
      BLOCK "040904E4" // Lang=US English, CharSet=Windows Multilingual
#endif
      BEGIN
         VALUE "CompanyName",     "\0"
         VALUE "FileDescription", "SPINDIAL OLE Control DLL\0"
         VALUE "FileVersion",     "1.0.001\0"
         VALUE "InternalName",    "SPINDIAL\0"
         VALUE "LegalCopyright",  "Copyright \251 1994, Microsoft Corporation\0"
         VALUE "LegalTrademarks", "\0"
         VALUE "OriginalFilename","SPINDIAL.DLL\0"
         VALUE "ProductName",     "SPINDIAL\0"
         VALUE "ProductVersion",  "1.0.001\0"

         VALUE "OLESelfRegister", "\0"

      END
   END
   BLOCK "VarFileInfo"
   BEGIN
#ifdef _WIN32
      VALUE "Translation", 0x409, 1200
         // English language (0x409) and the Unicode codepage (1200)
#else
      VALUE "Translation", 0x409, 1252
         // English language (0x409) and the Windows ANSI codepage (1252)
#endif
   END
END

ControlWizard generated this information automatically for us. Notice the OLESelfRegister key. A control that supports self-registration adds this string to the StringFileInfo block of the VERSIONINFO structure.

How Is the Version Information Read?

The following functions manipulate the version information:

GetFileVersionInfoSize returns the size of the version information, so the correct size buffer can be allocated. GetFileVersionInfo is then used to fill the buffer with the version information. VerQueryValue searches the buffer filled by GetFileVersionInfo and returns the requested values.

The process of reading the values is complicated by the translation information needed to get to the string. The version information contains two main blocks: the VarFileInfo block and the StringFileInfo block. The StringFileInfo block contains a sub-block, "040904E4". Figure 2 shows this graphically.

Figure 2. Blocks in the version information

To get to the OLESelfRegister key, we need to specify its "path" to VerQueryValue. In the case illustrated above, that path is "\StringFileInfo\040904E4\OLESelfRegister". However, the path may change depending on the "Translation" entry in the VarFileInfo block. Before we query for "OLESelfRegister", we need to query "\VarFileInfo\Translation" and build the path to OLESelfRegister from the result.

In CTLREG, the CCtlReg::SupportsSelfRegister function handles this process. We have already seen how CCtlReg::Register calls SupportsSelfRegister; you can see how SupportsSelfRegister works below.

BOOL CCtlReg::SupportsSelfRegister()
{
   BOOL bResult = FALSE;
   DWORD  handle;
   UINT  uiInfoSize;
   UINT  uiVerSize ;
   UINT  uiSize ;
   BYTE* pbData = NULL ;
   DWORD* lpBuffer;;
   char szName[512] ;

   // Get the size of the version information.
   uiInfoSize = 
      ::GetFileVersionInfoSize( m_strPathName,
                                &handle);
   if (uiInfoSize == 0) return FALSE ;

   // Allocate a buffer for the version information.
   pbData = new BYTE[uiInfoSize] ;

   // Fill the buffer with the version information.
   bResult = 
      ::GetFileVersionInfo( m_strPathName,
                            handle,
                            uiInfoSize,
                            pbData);
   if (!bResult) goto NastyGoto ;

   // Get the translation information.
   bResult = 
     ::VerQueryValue( pbData,
                      "\\VarFileInfo\\Translation",
                      (void**)&lpBuffer,
                      &uiVerSize);
   if (!bResult) goto NastyGoto ;

   bResult = uiVerSize ;
   if (!bResult) goto NastyGoto ;

   // Build the path to the OLESelfRegister key
   // using the translation information.
   sprintf( szName,
             "\\StringFileInfo\\%04hX%04hX\\OLESelfRegister",
             LOWORD(*lpBuffer),
             HIWORD(*lpBuffer)) ;

   // Search for the key.
   bResult = ::VerQueryValue( pbData, 
                              szName, 
                              (void**)&lpBuffer, 
                              &uiSize);

NastyGoto:
   delete [] pbData ;
   return bResult ;
}

That's All There Is to Registration

That's all there is to OLE Control registration. I placed the registration code in the CCtlReg class, so it is easy to call from the view code in an application. In CTLREG, I invoke a CFileDialog object to get a filename, then I attempt to register it. The code is shown below:

void CCtlRegView::OnControlsRegister() 
{

   static char BASED_CODE szFilter[] = 
      "OLE controls (*.ocx) | *.ocx | DLL's (*.dll) | *.dll |
       OCX and DLL (*.ocx; *.dll) | *.ocx;*.dll |
       All Files (*.*) | *.* ||" ;

   CFileDialog aDlg( TRUE,
                     "OCX",
                     "*.ocx",
                     OFN_PATHMUSTEXIST |OFN_FILEMUSTEXIST,
                     szFilter,
                     this ) ;

   if (aDlg.DoModal() == IDOK)
   {
      CCtlReg aCtlReg(aDlg.GetPathName()) ;
      if (aCtlReg.Register())
      {
          MessageBox( "Successful control registration.",
                      "Control Registration.",
                       MB_OK | MB_ICONINFORMATION) ;
      }
      else
      {
         MessageBox( "Control registration failed.",
                     NULL,
                     MB_OK | MB_ICONEXCLAMATION) ;
      }
   }
}

Unregistering a Control

The unregistration procedure is the same as the registration procedure, except that you call the DllUnregisterServer entry point (instead of DllRegisterServer) in your control's .OCX file. The CCtlReg::Unregister code, shown below, is identical to the CCtlReg::Register code except that "DLLUnregisterServer" replaces "DLLRegisterServer" in the ::GetProcAddress call.

BOOL CCtlReg::Unregister()
{
   BOOL bResult = FALSE ;
   if (SupportsSelfRegister())
   {
      // Path to OLE control in m_strPathName
      HMODULE hModule = ::LoadLibrary(m_strPathName) ;
      if (LOADLIBRARY_FAILED(hModule)) return FALSE ;

      CTLREGPROC DLLUnregisterServer =
            (CTLREGPROC)::GetProcAddress( hModule,
                                          "DllUnregisterServer" ) ;
      if (DLLUnregisterServer != NULL)
      {
         HRESULT regResult = DLLUnregisterServer() ;
         bResult = (regResult == NOERROR) ; 
      }
      ::FreeLibrary(hModule) ;
   }
   return result ;
}

Looking in the Registry

When you register the Spindial sample control that comes with the CDK, what does it add to the registry? The next section answers this question for 32-bit applications; the section after that ("The Little 16-Bit Popguns") answers the question for 16-bit applications. Depending on your development environment (16-bit, 32-bit, or both) read one or both of these sections. (My technical reviewers did not seem to grasp that I was presenting the same information twice—once for 16-bit and once for 32-bit—that's why I'm stating what appears to be obvious.)

The Big 32-Bit Guns

If you are running Windows NT version 3.5, use REGEDT32.EXE to look at the registry. Windows NT does not automatically give you an icon for REGEDT32.EXE, but the file should be in the WINNT\SYSTEM32 directory.

Take a look at the HKEY_CLASSES_ROOT tree and the entry labeled something like "SPINDIAL.spindialCtrl.1", as shown in Figure 3.

Figure 3. SPINDIAL.spindial.1 entry under HKEY_CLASSES_ROOT, as displayed by REGEDT32.EXE

This is not the entry we are looking for—there's no information in here that tells us whether this is a control or a server. If you keep looking through the entries under HKEY_CLASSES_ROOT, you will find one for CLSID (Figure 4).

Figure 4. The CLSID section of HKEY_CLASSES_ROOT

In this list, you will find a CLSID that matches the one under SPINDIAL.spindialCtrl.1. Figure 5 shows what I found on my system. This entry provides the information we need to determine which controls are installed.

Figure 5. Registry entry for Spindial control

The next section explains how to find this information on a 16-bit system.

The Little 16-Bit Popguns

Things aren't as pretty in 16-bit land. Instead of running REGEDT32.EXE, you run REGEDT (the Registration Info Editor) with the /V parameter to put it in super-whiz-bang-cool-developer-only mode. REGEDT works, but its interface is not as nice as the REGEDT32.EXE interface; REGEDT always shows the registry fully expanded and does not let you hide child levels.

Somewhere in this text you will find an entry labeled "SPINDIAL.spindialCtrl.1" or something similar. Luckily, this entry was listed first on my system (Figure 6).

Figure 6. Registration Info Editor screen

Unfortunately, this entry does not tell us whether this is a control or a server. Search for the key "\CLSID". Figure 7 below shows the results of this search on my system.

Figure 7. Search results for "\CLSID"

In this list, you will find a CLSID that matches the one under SPINDIAL.spindialCtrl.1 (Figure 8). This is the information we need to determine which controls are installed.

Figure 8. Registration database entry for the Spindial control

What's in the Registry?

Now that we know how to use REGEDT32.EXE and REGEDT.EXE to examine the control information in the registry on 32-bit and 16-bit systems, we can look at the information a control places in the registry. Figure 9 shows the typical information a control might place in the registry.

Figure 9. Registry information

Notice that two entries, InprocServer32 and ToolboxBitmap32, have the suffix "32", which indicates that this information applies to the 32-bit version of the control. It is possible to have both 16-bit and 32-bit versions of the same control installed.

We might want to display a list box that will allow the user to select a registered control to insert into the document. The OLE Test Container tool displays such a list box. After the user has inserted the control, the Test Container also places a button representing the control on the toolbar. The CTLREG sample application displays a dialog box showing the registered controls so the user can select a control to unregister.

Figure 10 shows the registry information required to determine whether an entry is a control, to unregister an control, and to get the bitmap for a control. The four keys under the class registry entry are discussed in the sections below.

Figure 10. Registry information for registering and unregistering controls

Control

OLE Controls put the Control key under their CLSID. Search for this key to determine whether a class is an OLE Control. We will discuss this in more detail in the "Finding Installed Controls" section later in this article.

Insertable

OLE objects that can be embedded include the Insertable key in their class registry entry. OLE servers such as Microsoft Excel, Word, and Scribble include this key when they register. The Insert Object command in Word uses this key to fill a list box with the names of objects that you can embed within your document.

OLE Controls may or may not include the Insertable key. The control developer will not define this key if inserting the control (for example, a timer) into a document does not make any sense. If the control developer wants to use the control with OLE containers that do not support OLE Controls directly, the control will have an Insertable key. Including this key indicates that the control is compatible with existing OLE containers.

ControlWizard does not define the Insertable key by default. The code shown below for Spindial from SPINCTL.CPP determines whether the control can be inserted. Change FALSE to TRUE (see the line in bold below) if you would like to insert your control.

BOOL CSpindialCtrl::CSpindialCtrlFactory::UpdateRegistry(BOOL bRegister)
{
   if (bRegister)
      return AfxOleRegisterControlClass(
                                        AfxGetInstanceHandle(),
                                        m_clsid,
                                        m_lpszProgID,
                                        IDS_SPINDIAL,
                                        IDB_SPINDIAL,

                                        FALSE,      //  Not insertable

                                        _dwSpindialOleMisc,
                                        _tlid,
                                        _wVerMajor,
                                        _wVerMinor);
   else
      return AfxOleUnregisterClass(m_clsid, m_lpszProgID);
}

InprocServer

The InprocServer key gives us the path to the 16-bit .OCX file that implements the control. The InprocServer32 key points to the 32-bit .OCX file. This path is used when unregistering a control.

Currently, there is no link between the information in the registry and the .OCX file. If you delete the .OCX file, the registry information is not removed or updated. Therefore, you must check to see if the .OCX file exists before running it. The CTLREG sample application marks controls that have existing .OCX files with the word "Exist".

ToolboxBitmap

The ToolboxBitmap and ToolboxBitmap32 keys point to a file containing a bitmap that can be used in toolbars and toolboxes, such as the Visual Basic toolbox. These buttons are defined to be 16x15 pixels. Figure 11 shows the ToolboxBitmap entry for the Spindial control.

Figure 11. Value for ToolboxBitmap32

As you can see in Figure 11, the ToolboxBitmap key value contains the path, followed by a comma and the resource number. The path is used by the LoadLibrary function, and the resource number is used in the LoadBitmap call.

Finding Installed Controls

Now that we know what to look for and where, we need to understand how to find the installed controls. This involves the following functions:

I used these functions instead of RegOpenKeyEx and other Reg...Ex functions because they are available for Win16 as well as Win32®. These four functions are not wrapped by the Microsoft Foundation Class Library (MFC). To emphasize this, I use the scope resolution operator (::).

Figure 12 shows where these functions are applied.

Figure 12. Functions used for the registry

RegOpenKey and RegCloseKey

First, we must understand how to open and close the CLSID key. To open a key, you use the RegOpenKey function with three parameters: HKEY_CLASSES_ROOT, the name of the key you want to open, and a pointer to the variable that receives the handle to the open key. The code below opens the CLSID key under HKEY_CLASSES_ROOT. We use the hKeyClsid handle to get to the keys under the CLSID key, then we close the CLSID key.

   HKEY hKeyClsid ;
   LONG regResult = ERROR_SUCCESS;
   regResult = ::RegOpenKey(HKEY_CLASSES_ROOT, "CLSID", &hKeyClsid);
   if (regResult != ERROR_SUCCESS) return FALSE ;
   //
   // Use hKeyClsid here.
   //
   ::RegCloseKey(hKeyClsid); 

A word to the weary: ERROR_SUCCESS is currently defined as 0, so you cannot do the following:

if (::RegOpenKey(...))
   // Succeeded.
else
   // Failed.

You could do the following, but it seems counterintuitive to me:

if (!::RegOpenKey(...))
   // Succeeded.
else
   // Failed.

Instead, use:

if (::RegOpenKey(...) == ERROR_SUCCESS)
   // Succeeded.

RegEnumKey

The second step is to enumerate the keys under CLSID using the RegEnumKey function. We pass four parameters to RegEnumKey: the hKeyClsid that we just got from RegOpenKey, the index that tracks the keys we have already enumerated, a pointer to a buffer that will hold the key name, and the size of the buffer. In this case, the key name will be the CLSID of the control, a string in the form {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}. It's easy to fill a list box with the key names; however, we have to open the key to get its handle before we can do any meaningful work.

In the code below, RegEnumKey returns a key name. We open this key to obtain its handle (hKeyX). We then open hKeyX, looking for the Control key. If the key is present, we know we have a control and can add it to our list box. If the Control key is not present, we continue enumerating the keys under CLSID.

   DWORD dwIndex = 0 ;
   char szClsidName[MAX_PATH+1] ;
   HKEY hKeyX ;
   HKEY hKeyControl ;
   .
   .
   .
   while(::RegEnumKey(hKeyClsid, 
                      dwIndex++, 
                      szClsidName, 
                      MAX_PATH+1) == ERROR_SUCCESS)
   {
      // 
      // Open the szClsidName key here.
      //
      regResult = ::RegOpenKey(hKeyClsid,szClsidName,&hKeyX) ;
      if (regResult != ERROR_SUCCESS) continue;
     
      // Look for the "Control" key.
      regResult = ::RegOpenKey(hKeyX, "Control", &hKeyControl) ;
      if (regResult == ERROR_SUCCESS)
      {
         .
         .
         .
         // This is a control.
         .
         .
         .
         ::RegCloseKey(hKeyControl) ;
      }
      ::RegCloseKey(hKeyX) ;
   }

RegQueryValue

Now that we know we have a control, we can examine the other keys handled by hKeyX. To get the value for a key, we use the RegQueryValue function. RegQueryValue accepts four parameters: a handle to an open key, the name of the key (located under the open key) whose value you wish to retrieve, a buffer to hold the key value, and a size for the buffer. Figure 13 illustrates how this works.

Figure 13. RegQueryValue parameters

Here's the code that calls RegQueryValue:

LONG lSize ;
char szBuffer[MAX_PATH*2] ;
// Look for path of InprocServer. 
lSize = sizeof(szBuffer) ;
::RegQueryValue(hKeyX, "InprocServer32", szBuffer, &lSize) ;  

// Store path name.
char* pPath = new char[lSize] ;
memcpy(pEntry->m_pPath, szBuffer, (int)lSize) ;

I originally wrote CTLREG for Win32; however, I wanted to use functions that were also available in Win16, so I used RegQueryValue instead of RegQueryValueEx. Unfortunately, the Win16 and Win32 versions of RegQueryValue are not interchangeable. In the Win16 version, there is no way to get the size of the buffer before you fill it. In the Win32 version, you can get the size of the buffer (in the lSize parameter) if you pass a NULL instead of a pointer to the buffer. The code below shows my original code before I had to port it to Win16.

// Get size
regResult = ::RegQueryValue(hKeyX, "InprocServer32", NULL, &lSize) ;
if (regResult == ERROR_SUCCESS)
{
   // Allocate buffer
   char* pPath = new char[lSize] ;
   // Fill buffer
   ::RegQueryValue(hKeyX, "InprocServer32", pPath, &lSize) ;
}

A word to the wise: You may find a specific function included in both Win32 and Win16, but the two versions of the function may not work the same.

Putting it all together

In the CTLREG application, a CPtrArray (m_PtrArray) in the CUnregisterDlg function is filled with information from the registry, as listed below. m_PtrArray is then used to fill a list box in the dialog box. The code below ties together all the pieces we have talked about. It also shows how to get an hBitmap for the ToolboxBitmap key.

BOOL CUnregisterDlg::GetControlNames()
{
   DWORD dwIndex = 0 ;
   HKEY hKeyClsid ;
   HKEY hKeyX ;
   HKEY hKeyControl ;
   HKEY hKeyInsertable; 
   LONG lSize ;
   LONG regResult = ERROR_SUCCESS;
   char szClsidName[MAX_PATH+1] ;
   char szBuffer[MAX_PATH*2] ;

#ifdef _WIN32
   static char szToolboxBitmap[] = "ToolboxBitmap32" ;
   static char szInprocServer[] = "InprocServer32" ;
#else
   static char szToolboxBitmap[] = "ToolboxBitmap" ;
   static char szInprocServer[] = "InprocServer" ;
#endif


   //HKEY_CLASSES_ROOT
   //   CLSID
   //      {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}
   //         Control
   //         Insertable
   //         ToolbarBitmap

   regResult = ::RegOpenKey(HKEY_CLASSES_ROOT, "CLSID", &hKeyClsid);
   if (regResult != ERROR_SUCCESS) return FALSE ;

   // Enumerate all entries under CLSID.
   while(::RegEnumKey(hKeyClsid, 
                      dwIndex++, 
                      szClsidName, 
                      MAX_PATH+1) == ERROR_SUCCESS)
   {
      // Open the CLSID key.
      regResult = ::RegOpenKey(hKeyClsid,szClsidName,&hKeyX) ;
      if (regResult != ERROR_SUCCESS) continue;

      // Is this a control?
      regResult = ::RegOpenKey(hKeyX, "Control", &hKeyControl) ;
      if (regResult == ERROR_SUCCESS)
      {
         // Yes, we have a control. 

         // Look for path of InprocServer. 
         lSize = sizeof(szBuffer) ;
         regResult = ::RegQueryValue(hKeyX, szInprocServer, szBuffer, &lSize) ;
         if (regResult != ERROR_SUCCESS)
         {// Didn't find it. It might be 16-bit instead of 32-bit or vice versa.
             continue ; 
         }

         // Store path name.
         CRegEntry* pEntry = new CRegEntry ;
         pEntry->m_pPath = new char[lSize] ;
         memcpy(pEntry->m_pPath, szBuffer, (int)lSize) ;
         
         // Check to see if .OCX file actually exists.
         OFSTRUCT OpenBuff ;
         if (::OpenFile(pEntry->m_pPath, &OpenBuff, OF_EXIST) == HFILE_ERROR)
         {
            pEntry->m_bExists = FALSE ;
         }

         // Get control name and put it into array.
         lSize = sizeof(szBuffer); 
         regResult = ::RegQueryValue(hKeyClsid, szClsidName, szBuffer, &lSize) ;
         if (regResult != ERROR_SUCCESS) 
         {   
            delete pEntry ;
            continue ;
         }
         pEntry->m_pName = new char[lSize] ; 
         memcpy(pEntry->m_pName, szBuffer, (int)lSize) ;

         // Is it insertable?
         regResult = ::RegOpenKey(hKeyX,"Insertable",&hKeyInsertable) ;
         pEntry->m_bInsertable = (regResult == ERROR_SUCCESS) ;
         if (pEntry->m_bInsertable) ::RegCloseKey(hKeyInsertable) ;

         // Put bitmap in array.
         lSize = sizeof(szBuffer) ; 
         regResult = ::RegQueryValue(hKeyX, szToolboxBitmap, szBuffer, &lSize) ;

         if (regResult == ERROR_SUCCESS)
         {
            CString strPathAndResNum(szBuffer, (int)lSize) ;
            // Let's pretend we're using Basic.
            int indexComma = strPathAndResNum.Find(',') ; 
            CString strPath = strPathAndResNum.Left(indexComma) ;
            CString strResNum = strPathAndResNum.Mid(indexComma+1) ;
            WORD wResNum = atoi(strResNum) ;

            HINSTANCE hInstCtl  = ::LoadLibrary(strPath) ;
            if (hInstCtl > HINSTANCE_ERROR)
            {
                // The .OCX file might not actually exist.
                pEntry->m_hBitmap = 
                     ::LoadBitmap(hInstCtl, MAKEINTRESOURCE(wResNum)) ;
                :FreeLibrary(hInstCtl) ;
            }
         }
         // Add entry to list.
         m_PtrArray.Add(pEntry); 
         
         // Clean up.
        ::RegCloseKey(hKeyControl) ;
      }
      ::RegCloseKey(hKeyX) ; 
   }
   ::RegCloseKey(hKeyClsid); 

   return TRUE ;
}

Cleaning Up the Registry

You can delete an OLE Control without unregistering it. Therefore, the registry may contain entries for OLE Controls that no longer exist on the system. I decided to add some minimal support to CTLREG for cleaning up entries associated with non-existing controls. I will not include the code here, but you can look in the UNREGDLG.CPP file for the OnCleanup, WipeOut, and GetControlNames functions. The version of GetControlNames is slightly different from the one listed above. Figure 14 provides a graphical representation of the clean-up code.

Figure 14. Cleaning up the registry

In Figure 14, we examine the registry information under \HKEY_CLASSES_ROOT\CLSID and delete the information for the Time control. We read the TypeLib entry to get the ID for the Time control's type library, then delete this entry from \HKEY_CLASSES_ROOT\TypeLib. Next, we read the ProgID entry that returns TIME.TimeCtrl.1. We use this information to delete the \HKEY_CLASSES_ROOT\TIME.TimeCtrl.1 entry. Now we delete the CLSID entry for the Time control itself. The last thing we do is search through \HKEY_CLASSES_ROOT\CLSID, deleting entries that have an InprocServer name matching that of the Time control.

This process leaves the dispatch map and events dispatch map entries for the Time control in \HKEY_CLASSES_ROOT\Interface. There is no easy way to find out which dispatch maps belong to a deleted control.

This clean-up method does work, but it is not 100 percent foolproof, especially on systems that include both 16-bit and 32-bit controls.

Conclusion

OLE Controls support self-registration, so a developer does not have to understand OLE to register or unregister a control. However, the developer will have to learn about version information to determine whether the control supports self-registration. Controls generated by ControlWizard support self-registration. In addition, many applications that use OLE Controls will need to search the registry for information about the control, for example, to find the location of the control's toolbox bitmap.

Bibliography

Microsoft OLE Control Developer's Kit (CDK). User's Guide and Reference.

Microsoft Windows version 3.1 Software Development Kit (SDK). Programmer's Reference, Volume 1: Overview, Chapter 11. (MSDN Library Archive, Product Documentation, SDKs, Windows 3.1 SDK)

Microsoft Win32 Software Development Kit (SDK) for Windows NT. Programmer's API Reference, Volume 2: Systems Services, Multimedia, Extensions, and Application Notes, Part 5, Chapter 80. (MSDN Library Archive, Product Documentation, SDKs, Win32 SDK)