The Makings Of An OCX Container

Putting the OCX in it's place

With OLE 1.0, only embedded objects were supported. This meant that all objects opened as separate windows. But it  would be preferable to have the objects appear as a part of our application. The user should not be aware of the fact that he is dealing with two or more separate applications. This need  was satisfied  when in-place activation was introduced in OLE 2.0.
With in-place activation, the server appears within the container window. This means that the server window is a child within  the container window. Read my lips "A child within the container window".
As explained earlier, DoVerb() is responsible for the OCX display. The first parameter to DoVerb() decides the nature of display. Hence, in the function DoVerb() in Progarm 1, replace the first parameter by OLEIVERB_SHOW. This indicates that the OCX should appear as an in-place active object. Sadly this doesn't happen.
Earlier we had said that the OLE interface has no code. We have to add code to implement any and every feature that we require. We have provided no support for in-place activation, so how can we expect the OCX to be displayed in-place?
Add code from Progarm 2 to Program 1. This provides the  function implementations required for an OCX to appear in-place.

Program 2

 void MFrameWnd::OnPaint()
 {
   CFrameWnd::OnPaint();
 }
 
 void MFrameWnd::ObjCreate(CLSID z_TempClsid)
 {
   IClassFactory *z_pClassFactory;
 
   z_pOleClientSite = new MOleClientSite;
   z_pInPlaceFrame = new MOleInPlaceFrame;
   z_pOleInPlaceSite = new MOleInPlaceSite;
   CoGetClassObject(z_TempClsid,1,0,IID_IClassFactory,(void**)& z_pClassFactory);
   z_pClassFactory-> CreateInstance(0,IID_IOleObject,(void**)& z_pOleObject);
   z_pOleObject-> SetClientSite((IOleClientSite*)z_pOleClientSite);
   z_pOleObject-> DoVerb(OLEIVERB_SHOW,0,(IOleClientSite*)z_pOleClientSite,-1,m_hWnd,& z_Rect);
 }  
 void* _export _cdecl MOleInPlaceSite::GetWindow (HWND * lphwnd)
 {
  *lphwnd = z_hFWnd;
   return 0;
 }
 
 void*  _export _cdecl  MOleInPlaceSite::GetWindowContext(IOleInPlaceFrame  **lplpFrame, IOleInPlaceUIWindow **,  RECT  *lprcPosRect,RECT *,OLEINPLACEFRAMEINFO *lpFrameInfo)
 {
   _fmemset(lpFrameInfo,0,sizeof(OLEINPLACEFRAMEINFO));
   *lplpFrame = (IOleInPlaceFrame*)z_pInPlaceFrame;
   ::CopyRect(lprcPosRect,& z_Rect);
   lpFrameInfo-> hwndFrame = z_hFWnd;
   return 0;
 }
 
 void* _export _cdecl MOleClientSite::QueryInterface (REFIID riid , void **ppvObj )
 {
   if (riid == IID_IOleInPlaceSite)
   {
    *ppvObj = z_pOleInPlaceSite;
     return 0;
   }
   return ResultFromScode(E_NOTIMPL);
 }
Once again use Insert Control... sub-option place the OCX Button as an object within our container. This time the OCX will appear to be a part and  parcel of our window. The OCX is now said to appear as an in-place active object.
ObjCreate() has been modified to facilitate in-place activation. The classes MOleClientSite, MOleInPlaceFrame and MOleInPlaceSite are our stand-ins for the interfaces IOleClientSite, IOleInPlaceFrame and IOleInPlaceSite respectively. We have created an  instance of each in ObjCreate().
So far we have called functions in the OCX. We have asked  the OCX for information. In a similar fashion, the OCX also wants to know more about the container. To do so, it requires a pointer from the container.
The container has to use the IOleObject function SetClientSite() to pass it's IOleClientSite pointer to the OCX. Thus, we pass the MOleClientSite pointer to SetClientSite() after suitable casting. The OCX on receiving the pointer through SetClientSite() will use it to determine if the container supports in-place editing by asking for an IOleInPlaceSite pointer. This it does by calling QueryInterface() of MOleClientSite with IID_IOleInPlaceSite.The OCX uses the MOleInPlaceSite pointer passed  to it to call two functions from this interface: GetWindow() and GetWindowContext(). If the OCX is to appear in-place within the container, then it needs to know the handle  to the parent window. In the function GetWindow(), we pass  a pointer  to  the  handle of the container's window  back  to  the OCX.
It  is the function GetWindowContext() that makes crucial information regarding the container available to the OCX. It has five parameters. The container is expected to pass the address of it's IOleInPlaceFrame object to the OCX using the first parameter.
The third is a pointer to a RECT structure. This RECT structure holds the area in which the OCX should appear when in-place active. We use the global CRect object z_Rect that holds the initial  dimensions of the OCX to initialize this parameter.
The last and the most important parameter is a pointer to an OLEINPLACEFRAMEINFO structure. This structure holds details such as whether the container is an MDI application, pointer to the frame window etc. We initialize just one member of  the structure to hold the handle to the container window. This is the only  member of the structure that has to be initialized. This is why we use the _fmemset() function to set all other members of the structure to 0 at the start of GetWindowContext().
The interface IOleInPlaceFrame is required only if we are going to merge the OCX menus with the container menu; bring in the OCX's toolbar and for other such actions. As of now, we do not use any function of this interface, but cannot do without it either. If we do not pass the address of an IOleInPlaceFrame instance to the OCX in the GetWindowContext() function then the OCX will appear embedded and not in-place.
The first parameter to DoVerb() is OLEIVERB_SHOW to indicate that the OCX should appear in-place. It is passed the pointer to the interface MOleClientSite as the  third parameter. In addition, the address of z_Rect is passed as the  last parameter to DoVerb(). This indicates  the  size and position that the OCX is to occupy in the  container's window.
In the above explanation, you might have noticed that every thing seemed cut and dry. Use SetClientSite() before DoVerb()  to pass the IOleClientSite() pointer  to the OCX. The OCX will then use this pointer to call QueryInterface() with  IID_IOleInPlaceSite. Use GetWindow() and GetWindowContext()  of  IOleInPlaceSite to pass all relevant information to the OCX etc.
But things are not always as self-evident as they seem. Nowhere in the OLE documentation are the steps to follow in  writing a container and/or a server stated explicitly.
This sequence was something that we have figured out using our own devices. Before, we tackled the OCX container  we had worked extensively on the OLE containers and both  DLL and EXE servers that were written in C/C++ and studied the interaction between them in detail. We read between the lines and used native  wisdom to piece together the complete  picture. (Small is Beautiful).
In the above example, the OCX expects the IOleClientSite function GetWindow() to return the window  handle to it. Similarly, GetWindowContext() is expected to  provide the OCX with other information necessary for in-place activation. If we do not provide the code in either GetWindow() or GetWindowContext()  the OCX will not appear in-place. Consider another scenario. What if  the OCX does not return an IOleObject pointer in CreateInstance()? The entire process will come to a grinding halt.
While all through the OLE documentation alludes to what has to be done in the various functions of the interfaces, never ever does it state when the functions are to be used. Thus, OLE will work if and only if both participants follow certain rules. As long as everyone tows the line, the OLE apparatus works just fine. But one step out of line and the whole system collapses; faster than a house of cards.
Unfortunately, the rules of OLE are unstated. Rules everyone  know exist but cannot pin-point. As of now, trial and error is the only way that one learns about the rules of OLE. To say that the path to OLE is strewn with obstacles is an understatement of understatements.

Some meat on the bones

Next we will add a few features to the container. For example, if an user clicks within the container window but outside  of  the OCX area when the OCX is in-place active, then  the OCX will get hidden. And what's more! An image of it will be  displayed on screen. There are several other such additions that have been made.

Program 3

 MFrameWnd::MFrameWnd()
 {
   Create(0,"OCX Container");
   z_pMenu = new CMenu;
   z_pMenu-> LoadMenu(IDR_MENU1);
   SetMenu(z_pMenu);             
   z_pTracker  = new CRectTracker(& z_Rect,CRectTracker::solidLine);
 }
 
 void MFrameWnd::ObjCreate(CLSID z_TempClsid)
 {
  IClassFactory *z_pClassFactory;
   char *z_sName;
   
   z_pOleClientSite = new MOleClientSite;
   z_pInPlaceFrame = new MOleInPlaceFrame;
   z_pOleInPlaceSite = new MOleInPlaceSite;
   CoGetClassObject(z_TempClsid,1,0,IID_IClassFactory,(void**)& z_pClassFactory);
   z_pClassFactory-> CreateInstance(0,IID_IOleObject,(void**)& z_pOleObject);
   z_pOleObject-> SetClientSite((IOleClientSite*)z_pOleClientSite);
   z_pOleObject-> DoVerb(OLEIVERB_SHOW,0,(IOleClientSite*)z_pOleClientSite,-1,m_hWnd,& z_Rect);
   z_nActive = TRUE;
 }
 
 void MFrameWnd::OnPaint()
 {
   if(z_pOleObject)
   {                           
     SIZEL z_sizel;
     CPaintDC z_DC(this);
 
     if(!z_nActive)
     {
       z_pTracker-> m_nStyle =  CRectTracker::resizeInside|CRectTracker::solidLine;
       OleDraw(z_pOleObject,DVASPECT_CONTENT,z_DC.m_hDC,& z_Rect);
     }                                                                         
     else
       z_pTracker-> m_nStyle = CRectTracker::solidLine;
       z_pTracker-> Draw(& z_DC);     
       z_sizel.cx  = XformWidthInPixelsToHimetric(z_DC.m_hDC,(z_pTracker- > m_rect.right -
        z_pTracker-> m_rect.left));
       z_sizel.cy  = XformWidthInPixelsToHimetric(z_DC.m_hDC,
        (z_pTracker- > m_rect.bottom - z_pTracker-> m_rect.top));
       z_pOleObject-> SetExtent(DVASPECT_CONTENT,& z_sizel);
       ReleaseDC(& z_DC);
   }
   CFrameWnd::OnPaint();
 }
 
 void MFrameWnd::OnLButtonDown(UINT,CPoint)
 { 
   z_nActive = FALSE;
   z_pOleObject-> DoVerb(OLEIVERB_HIDE,0,(IOleClientSite*)z_pOleClientSite,
     -1,m_hWnd,& z_Rect);
 }                                                                                                            
   
 void MFrameWnd::OnLButtonDblClk(UINT,CPoint z_Point)
 {
     if(!z_nActive & &  z_Rect.PtInRect(z_Point)) 
     {
         z_nActive = TRUE;
         z_pOleObject-> DoVerb(OLEIVERB_SHOW,0,(IOleClientSite*)z_pOleClientSite,
           -1,m_hWnd,& z_Rect);
     }
 } 
 
 void MFrameWnd::OnMouseMove(UINT z_nFlags,CPoint z_Point)
 {                               
   int z_nDraw = 0;
   if(!z_nActive & &  (z_nFlags &  MK_LBUTTON) & &  z_Rect.PtInRect(z_Point))                                           
   {
       z_nDraw = 1;
       z_pTracker-> Track(this,z_Point);
   }                                                      
   if(z_nDraw)      
     {
       z_Rect = z_pTracker-> m_rect;
       Invalidate();
     } 
 }    
The  OCX will appear in-place. Notice that the OCX is now given a thin black line as a border. Click within the container window but not in the area occupied by the OCX. This will cause the actual OCX to be hidden from  view, that is it is de-activated, and an image of it will be displayed instead. Note a change in the border. The image has a black solid-line border with the dots along the inside of the line.
Click on the OCX image and drag the mouse around. The OCX image will move to the new location. Click on a dot along the border and drag the mouse. This will allow us to re-size the OCX. To re- activate the OCX, double click on the image. The OCX will appear in-place active at  it's new position and in the new size.
In ObjCreate(), we set the boolean variable z_nActive to TRUE. This indicates that the OCX is in-place active. z_nActive is used to hold the active status of the OCX. Every time the OCX is hidden it will be set to FALSE.
When  the OCX appeared as an embedded object in our container,  you must have noticed that it opened as a separate  window. When we bring in an OCX as an in-place active object, all  that happens is that the window of the container is set as the parent  to the window of the OCX. The window of  the OCX, thus, appears as a child within the container window. It has just a border and no caption or menu. As long as the OCX is active and we are within the bounds of it's  window all the messages are passed to it's call-back function else  they are sent to the call-back function of the container.
A user may want to change the size and location of an in-place active OCX interactively. Since, the OCX window  is a child within the container window with just a border, there  is no way that we can directly change it's dimensions and position. So we resort to a bit of subterfuge. What if we had a rectangle around the OCX? The dimensions and position of which we could change with a mouse. We could then manipulate the  rectangle and pass it's new size and/or position to the OCX. The OCX can use this information to redraw itself so as to occupy the new size and/or position. Such a rectangle can be created using the MFC and is called a tracker.
The class CRectTracker of the MFC is used to implement a tracker in our application. In the constructor of the class MFrameWnd, we create a CRectTracker  object. This object  is initially set to have the same dimensions  and position as the OCX. We have stipulated that the tracker is to initially appear as a solid line coinciding with the  borders of the OCX. The advantage of using this class is that it provides built-in support that allows us to re-size and  move the tracker with the aid of a mouse. The style of the tracker  is changed  so that the user is provided with a visual  feedback  on the status of the OCX.
Every time we want to change the size and/or  position  of  the OCX,  we first click within the container window but outside of the area of the OCX to de-activate it. The message WM_LBUTTONDOWN will be sent to the container  window's call-back function. OnLButtonDown() will get executed. In this  function, we use DoVerb() with OLEIVERB_HIDE as the first parameter. This will hide the OCX window from view. What is now displayed is an image of  the OCX. z_nActive is set to FALSE to indicate that the OCX is no longer active.
Then, we click anywhere within the area bounded by the tracker rectangle and drag it around. The CRectTracker function Track() used in OnMouseMove() will be executed. It will update the CRectTracker variable m_rect to hold the new size and/or location of the tracker. We update our global variable z_Rect to hold these new dimensions and then repaint the container window so that the OCX image appears at the new location.
In OnPaint() we initially check the status of z_nActive to determine if the OCX is active or hidden  and appropriately change the style of the tracker.
When the OCX is not in-place active, the image of  the OCX has to be drawn and this drawing is handled by the OLE API OleDraw(). To OleDraw(), we pass the IOleObject pointer of  the  OCX. DVASPECT_CONTENT specifies that the image of  the OCX is to be rendered. We also pass OleDraw() a device context to the container window and the area where the image is to be drawn. OleDraw() uses all this data to draw a spitting image of the actual OCX.
The OCX also has to be informed of it's  new  dimensions. OLE sets standards for any all information exchanges  that are to take place. One such standard states that whenever dimensions are to be passed between applications, they must always be in HIMETRIC units. We convert the width and height  which are in pixels to HIMETRIC using XformWidthInPixelsToHimetric() and XformHeightInPixelsToHimetric() respectively and store them in the SIZEL structure. Both of these are OLE UI functions. We pass the SIZEL structure which holds the new dimensions to the OCX through the function SetExtent() of  the  interface IOleObject.
Experience  shows  that these dimensions in HIMETRIC are converted back to pixel units by the OCX. Then why pass dimensions in HIMETRIC? Because OLE says so. If you write your own container and OCX then you can pass dimensions back and forth in pixels. But then nobody else who follows the OLE set standards will be able to communicate  with you.
Double click on the image to re-activate the OCX. Once again, a DoVerb() with OLEIVERB_SHOW as the first parameter is executed. In this case, in OnPaint(), the style of the tracker will be changed to indicate that the OCX is active and it will be displayed at the new position and size set using the tracker.

The OCX in action

We had mentioned before that verbs provoke action in the OCX. However, the OCX has to have support for  the action. What do we mean by support? Consider OLEIVERB_OPEN. When this verb is invoked on the OCX, there should be code provided in the  OCX  so that it opens as an embedded object.
Consequently, this means that the OCX should have code for all the different verbs that it can be invoked with. However, it is not mandatory that all the OCX's or indeed all OLE servers, should support all the verbs. It is upto the programmer writing  the OCX to decide the verbs that it will support.
In the next program, we have demonstrated the effect of a few  of the verbs. We have assumed that all the OCXes brought into this container support these verbs.

Program 4

 void MFrameWnd::ObjCreate(CLSID z_TempClsid)
 {
   IClassFactory *z_pClassFactory;
   char *z_sName;
   CMenu *z_pAddMenu,*z_pPopMenu,*z_pSubMenu;
   
   z_pOleClientSite = new MOleClientSite;
   z_pInPlaceFrame = new MOleInPlaceFrame;
   z_pOleInPlaceSite = new MOleInPlaceSite;
   CoGetClassObject(z_TempClsid,1,0,IID_IClassFactory,(void**)& z_pClassFactory);
   z_pClassFactory-> CreateInstance(0,IID_IOleObject,(void**)& z_pOleObject);
   z_pOleObject-> SetClientSite((IOleClientSite*)z_pOleClientSite);
   z_pOleObject-> DoVerb(OLEIVERB_SHOW,0,(IOleClientSite*)z_pOleClientSite,-1,m_hWnd,& z_Rect);
   z_nActive = TRUE;
   z_pSubMenu = z_pMenu-> GetSubMenu(1);
   z_pAddMenu = new CMenu;
   z_pAddMenu-> LoadMenu(IDR_MENU2);
   z_pPopMenu = z_pAddMenu-> GetSubMenu(0);
   z_sName = (char*) malloc(10);
   z_pOleObject-> GetUserType(0,& z_sName);
   z_pSubMenu-> AppendMenu(MF_ENABLED|MF_STRING|MF_POPUP,(UINT)z_pPopMenu-> m_hMenu,z_sName);
   free(z_sName);
 }
 
 void MFrameWnd::PrimaryVerb()
 {
   z_pOleObject-> DoVerb(OLEIVERB_PRIMARY,0,(IOleClientSite*)z_pOleClientSite,-1,m_hWnd,& z_Rect);
 }
 
 void MFrameWnd::ShowVerb()
 {
   z_pOleObject-> DoVerb(OLEIVERB_SHOW,0,(IOleClientSite*)z_pOleClientSite,-1,m_hWnd,& z_Rect);
 }
 
 void MFrameWnd::OpenVerb()
 {
   z_nActive = FALSE;
   z_pOleObject-> DoVerb(OLEIVERB_OPEN,0,(IOleClientSite*)z_pOleClientSite,-1,m_hWnd,& z_Rect);
 }
 
 void MFrameWnd::HideVerb()
 {
   z_nActive = FALSE;
   z_pOleObject-> DoVerb(OLEIVERB_HIDE,0,(IOleClientSite*)z_pOleClientSite,-1,m_hWnd,& z_Rect);
 }
 
 void MFrameWnd::UIActivateVerb()
 {
   z_nActive = TRUE;
   z_pOleObject-> DoVerb(OLEIVERB_UIACTIVATE,0,(IOleClientSite*)z_pOleClientSite,-1,m_hWnd,& z_Rect);
 }
 
 void MFrameWnd::PropertiesVerb()
 {
   z_pOleObject-> DoVerb(OLEIVERB_PROPERTIES,0,(IOleClientSite*)z_pOleClientSite,-1,m_hWnd,& z_Rect);
 }      
View the pop-up under the main menu-option Edit.... There will be a pop-up Button Control added to the end. This pop-up menu will hold six verbs that can be invoked on the OCX. See the effect of the different verbs on the OCX.
In ObjCreate(), we retrieve a handle to the pop-up associated with the main menu option Edit.... To the end of this pop-up we add a pop-up of the verbs. This pop-up is a part of  a menu resource; IDR_MENU2; in the RC file of  the container. A handle to this pop-up menu is obtained and is used in AppendMenu().
We wanted the text for this pop-up to indicate the name of the OCX used. This name is obtained from the OCX with the help of the IOleObject function GetUserType(). When we call the function, the OCX fills up a previously allocated string  with it's name. We append this string as the pop-up to the existing menu. We use the message map  entries  to trap messages from this pop-up and associate functions with them. All that these functions do is they call DoVerb() with the appropriate verb.
It is interesting to see the effect of the OLEIVERB_PROPERTIES verb on the OCX. Selecting the sub-option Properties from the pop-up will call the function PropertiesVerb(). This will bring up a dialog box  titled `Button Control Properties'. What you will see is a sneak preview of a significant feature of Windows '95 in action!
The dialog box that you see is the properties dialog box. Associated with each OCX there could be several properties,  for example, color. To provide the user with an easy and standard method of changing the properties of an object, Microsoft has come up with the concept of Property Pages.
A property page could be seen as a dialog box in which all the required information about one or more properties is available. For example, a property page dealing with fonts has information about the current font, point size, nature of font (bold,  italics, etc.) and so on displayed in it. Naturally, this information can be modified.
Some commonly used properties such as color, font etc. have been provided with standard property pages by Microsoft. For other properties unique property pages can be designed and implemented. A collection of all the property pages associated with a particular OCX is the properties dialog box for that OCX.
In the `Button Control Properties' dialog box, there are two rectangles at the top titled `General' and `Fonts' respectively. These are called tabs. Clicking on any one of them brings up the property page associated with the title. By default, in this case, the dialog box displays the property page associated with `General'. This property page holds a single edit control titled `Caption'. Type in a string into this edit control. Click on the `Apply  Now' button  to see the changes made being applied to the OCX. In this way, the `Caption' property can be changed from the property page entitled `General'.
Next click on the tab `Fonts'. This brings up the property page associated with fonts. This is a standard property page provided by Microsoft.
The properties dialog box will also be displayed when we  select the Primary... sub-option. Thus, a  DoVerb() with OLEIVERB_PRIMARY as it's first parameter will produce the same results as a DoVerb() with OLEIVERB_PROPERTIES. Property pages are explained in greater detail later when we deal with properties of  the OCX. We reiterate that the verbs are to be implemented by the OCX. The container has absolutely nothing to do with  the verbs.

A choice in the matter

In the above programs, we had hard-coded the name of the OCX to be activated. Ideally, the user should be able to select an OCX from those available at run-time.
All the OLE servers are registered with `reg.dat', the registration database. This is true of the OCXes  too. So,  we can read `reg.dat' to populate a listbox with  the names of  the available controls. This will allow the user to select the OCX that is to be used.
Registering objects with the registration database calls for a bit of history. In the case of OLE servers, all you had to do was have an ASCII file with all details of the OLE server. This `.reg' file contained information like the CLSID of the server, the name of the server, the name of the `.exe' or `.dll' file, the verbs and so on. The `.reg' file used a program called REGEDIT to register the OLE server  with the registration database, `reg.dat'.
REGEDIT does not resort to any mysterious mantra. It merely uses standard Windows API like `RegOpenKey()', `RegSetKey()' and so on to write to the registration database.
Displaying all the available OLE servers in a container program was child's play with the OLE UI function OleUIInsertObject(). On executing this function the OLE servers are displayed in a standard dialog box titled `Insert Object'.
With the advent of OCXes, this standard `Insert  Object' dialog has undergone a sea-change as seen in  Microsoft Access 2.0. In the new `Insert Object' dialog box an additional radio-button is provided for OCXes. Selecting  this radio-button enables us to exclusively see the registered OCXes.
However, there is no documentation on how this new `Insert Object' dialog box can be obtained and made a part of the OCX container. There must be something in the OCX registration code that differentiates it from an conventional OLE server.
We once again had to turn our attention to the OCX samples to understand the registration code. But in this particular  case the OCX samples too were no big help. All OCX samples provided with Visual C++ use the MFC. They have been generated using the Control Wizard. These applications  have  OCX registration code built into them and hence do not require a separate `.reg' file.
Well, our never-say-die spirit carried us through  this. After sifting through the code in the OCX samples we isolated the code used by an OCX to register itself. A single helper function; AfxOleRegisterControlClass(). We converted this function to the basic API registration functions only to realise that the only difference between the information in the registration code for an OCX and that in a `.reg' file for an OLE server is the presence of the keyword `Control'.
Thus, we can easily have a `.reg' file to register an OCX which has the line HKEY_CLASSES_ROOT\CLSID\TheActualCLSIDOfTheControl\Control in addition to all the details that are normally present in  the `.reg' file for an OLE server.
In the following program we have created our own `Insert Control' dialog box which displays the names of only the OCXes from the registration database.

Program 5

 void MFrameWnd::InsertControl()
 {
   MDialog z_Dialog(IDD_DIALOG1);
 
   z_Dialog.DoModal();
 }
 
 MDialog::MDialog(int ID):CDialog(ID)
 {
   z_nID = ID;
 }
 
 BOOL MDialog::OnInitDialog()
 {
   CDialog::OnInitDialog();  
   if(z_nID==IDD_DIALOG1)
   {
     long z_lCount = 0;
     CListBox *z_pListBox = (CListBox*)GetDlgItem(IDC_LIST1);
       
     while(1)
     {
       HKEY z_Root,z_Root1,z_Root2;
       char *z_sName,*z_sName1,*z_sName2;
       long z_lCopied;
       
       z_sName = (char*) malloc(100);
       z_sName1 = (char*) malloc(100);
       z_sName2 = (char*) malloc(100);
       z_lCopied = 100;
       RegOpenKey(HKEY_CLASSES_ROOT,"",& z_Root);
       if((RegEnumKey(z_Root,z_lCount,z_sName,100)))
         break;
       z_lCount++;
       strcpy(z_sName2,z_sName);                
       strcpy(z_sName1,z_sName);
       strcat(z_sName1,"\\CLSID");
       RegOpenKey(HKEY_CLASSES_ROOT,z_sName1,& z_Root1);
       RegQueryValue(z_Root1,0,z_sName,& z_lCopied);
       z_lCopied = 100;
       strcpy(z_sName1,"CLSID\\");
       strcat(z_sName1,z_sName);
       strcat(z_sName1,"\\Control");
       if(!RegOpenKey(HKEY_CLASSES_ROOT,z_sName1,& z_Root2))
       {
         RegQueryValue(z_Root,z_sName2,z_sName,& z_lCopied); 
         z_sPrgID.SetAt(z_sName,z_sName2);     
         z_pListBox-> AddString(z_sName);
       }  
       free(z_sName);
       free(z_sName1);
       free(z_sName2);
       RegCloseKey(z_Root);
       RegCloseKey(z_Root1);
       RegCloseKey(z_Root2);
     }           
     z_pListBox-> SetCurSel(0);
     return TRUE;                        
   }                      
   return TRUE;
 }
 
 void MDialog::OnOK()
 {
   if(z_nID == IDD_DIALOG1)
   {
     char *z_sPID;
     CListBox *z_pListBox;
     int z_nIndex;
     CString z_sTemp,z_sString;
     
     z_pListBox = (CListBox*)GetDlgItem(IDC_LIST1);
     z_nIndex = z_pListBox-> GetCurSel();  
     z_pListBox-> GetText(z_nIndex,z_sString);
     z_sPrgID.Lookup(z_sString,z_sTemp);
     z_sPID = z_sTemp.AllocSysString();    
     EndDialog(0);
     CLSIDFromString(z_sPID,& z_Clsid);
     z_pFrameWnd-> ObjCreate(z_Clsid);            
     ::SysFreeString(z_sPID);
   }       
   else
     EndDialog(0);
 }
When we select the menu-option Insert  Control..., we create an MDialog object and pass it the ID of the dialog resource to be associated with it. The ID is stored in the global variable z_nID. Next, the CDialog function DoModal() is used to display the dialog box.
In OnInitDialog(), we use the registration database functions of the Windows API to read `reg.dat'. RegOpenKey() is used to obtain a handle to required keys. Then, we use RegEnumKey() to access each individual sub-key.
The while loop reads individual entries in `reg.dat' and uses suitable string concatenation to read the CLSID. Using the CLSID, we determine whether the entry is an OCX by searching for the keyword Control.
If it is then the CMapStringToString object, z_sPrgID, is used to store both the human readable name of the  OCX and the ProgID of  the OCX as strings. CMapStringToString one of  the MFC collection classes. The name acts as the key in the map which stores the ProgID as the value. In addition, the name of the OCX is added as an element in the listbox of the dialog. Thus, the listbox is populated with the names of all  registered OCXes.
When  the user selects an OCX and clicks on the  OK button, OnOK() gets executed. We determine the OCX selected by the user in the list box and search z_sPrgID for the allied ProgID. CLSIDFromString() will search `reg.dat' for the CLSID associated with this ProgID. The CLSID is then passed to ObjCreate() where it is used by CoGetClassObject() to activate the OCX.

Continue