No announcement yet.

Can PowerBasic 9 Create ActiveX Controls (.OCX)?

  • Filter
  • Time
  • Show
Clear All
new posts

  • Can PowerBasic 9 Create ActiveX Controls (.OCX)?

    I understand that Powerbasic 9 can create ActiveX (COM) .DLLs, but does anybody know if PowerBasic 9 is capable of creating an ActiveX (.OCX) Control? I have seen several posts on how PowerBasic can use ActiveX Controls, but no references to creating them.

    If so, could sombody post sample source code that demonstrates the concept?

    Thanks in advance.

  • #2
    far as I know

    Yes, as far as I know it can. I've been studying up on it for the past six months, and give me another three months or so and I think I'll have it! There are several folks here who know how to do it actually (Jose, Dominic, etc). There are about fifteen or so interfaces involved that most ActiveX controls implement, and you can go to the MS documentation to learn about them (good luck). Perhaps someone more knowlegable than I can correct me on this, but I don't believe the file extension, i.e., ocx, dll, exe, matters. What matters are the implementations of the interfaces.

    In terms of ActiveX exe servers, I believe there is a problem there. You have the situation of having to handle the out of process remote procedure call RPC situation. In C/C++ midl takes care of that, but I don't believe that was implemented in PB9.
    Last edited by Fred Harris; 16 Dec 2008, 11:18 AM.


    • #3
      Apology for wrong Forum

      I apologize in advance for placing this question on the wrong forum. I didn't notice the Programming with Objects forum until after my post...


      • #4
        Perhaps the forum administrator will want to move it there.

        Another thought Wayne. At...

        is a very simple C++ tutorial on creating an ActiveX control with Microsoft's Active Template Library ( ATL ). I built the control myself (its mostly wizards) as it only takes an hour or so to go through it. I was able to very easily get the control working in both PowerBASIC 9 and VB6. My present intention is to reverse engineer the wizard generated code to figure out what is going on. Its a major undertaking for me and will take some time that I imagine very few folks would want to do themselves. But I like figuring things out.


        • #5
          You won't be able to create a visual control (aka OCX) as PB has no native support for property sheets or containers to be used with the target design environment. Whilst it's true ActiveX objects can be under any extension (DLL or OCX is most common), OCX usually denotes the library is an ActiveX control.

          You can still create an ActiveX DLL and code the object calls by hand in VB, VC++, etc.
 | Slam DBMS | PrpT Control | Other Downloads | Contact Me


          • #6
            Hi Kev,

            Nonetheless, I tend to think it can be done. PowerBASIC is certainly internally implementing a number of base COM interfaces such as IUnknown, IClassFactory, etc. Pointers to all these should be obtainable, I would think. The rest of the interfaces usually associated with ActiveX controls would have to be hand wired into the already existing PowerBASIC infrastructure.

            In musing about this, if one were to wish to create a visual control, then the control would need a class registered somewhere, and I would think one of the class factory interfaces would be the place to do this. However, this is internally implemented by PB, so it would have to be handled elsewhere.

            In terms of messages from a window procedure, they would need to be routed through the Connection Point mechanism to be picked up in the client. Lots of issues, to be sure. Not a trivial undertaking. That's why I'm taking the approach I am, that is, start with a C++ created ActiveX control for which I have the code, and reverse engineer the thing so to speak to see what makes it tick. Then implement it in PB.


            • #7
              I was talking about native support - you can't just compile an ActiveX DLL, rename to OCX and add it to VB's control palette. There are rules that need to be followed. My answer to the OP is that PowerBASIC does not currently have native support for property sheets or design-time functions, therefor does not create ActiveX controls. A comparison can be made with PB7, before v7.00 was released you could call ActiveX libraries but it was a lot of work without shortcuts.

              Sure, you might be able to add it using native code, but then again you may not
              Last edited by Kev Peel; 16 Dec 2008, 01:08 PM.
     | Slam DBMS | PrpT Control | Other Downloads | Contact Me


              • #8
                Yes, you are right Kev. I was interpreting Wayne's question in terms of whether its humanly possible to do it - not whether its a practical possibility. And in my mind anyway I don't see any use for creating ActiveX controls unless one would be doing it to market them to other developers using scripting languages for web development. If all you want is a visual control in a dll, then an ordinary custom control seems to be about a thousand times more practical - and generally faster than an IDispatch implementation in an ActiveX control.


                • #9
                  Fred nailed my intent almost correctly when he stated "market them to other developers", but slightly incorrect for the part "using scripting languages for web development". I am creating an SDK for developers to manage a hardware device, in which one implementation was an ActiveX control that adds design persistence, and manages and removes callback complexities across different platforms and languages that support COM.

                  I will probably go ahead and code an ActiveX DLL that accomplishes this purpose, but I was hoping to be able to show up the C++ developer [who is about to get a new assignment!] -- and reuse the COM DLL code if Fred's endeavor to reverse-engineer proves successful!


                  • #10
                    In musing about this, if one were to wish to create a visual control, then the control would
                    need a class registered somewhere, and I would think one of the class factory interfaces would
                    be the place to do this.
                    No, you don't want to put it there, that is not the purpose of a class factory.

                    There are two types of visual ActiveX controls, windowed and windowless. The windowed form is
                    your old fashioned control wrapped in COM.

                    An ActiveX control exports the following functions:

                    The window class for a windowed control is registered in DllGetClassObject.
                    For example,
                    FUNCTION DllGetClassObject ALIAS "DllGetClassObject" _
                      ( _
                      rclsid  AS GUID, _
                      riid    AS GUID, _
                      ppv     AS DWORD _
                      ) EXPORT AS LONG
                      LOCAL hr      AS LONG
                      ppv = %NULL
                      SELECT CASE rclsid
                        CASE $CLSID_TREECONTROL
                          IF g_pClassFactoryTree = %NULL THEN
                            g_pClassFactoryTree = treeIClassFactory_Create()
                          END IF
                          IF g_pClassFactoryTree = %NULL THEN
                            FUNCTION = %E_OUTOFMEMORY
                            EXIT FUNCTION
                          END IF
                          hr = treeIClassFactory_QueryInterface(g_pClassFactoryTree, riid, ppv)
                          treeIClassFactory_Release g_pClassFactoryTree
                          InitTree2D 0
                        CASE ELSE
                          hr = %CLASS_E_CLASSNOTAVAILABLE
                      END SELECT
                      FUNCTION = hr
                    END FUNCTION
                    The InitTree2D in the code above registers the window class.
                    The COM function, CoGetClassObject, calls DllGetClassObject when an ActiveX control is instantiated.

                    In terms of messages from a window procedure, they would need to be routed through the Connection
                    Point mechanism to be picked up in the client. Lots of issues, to be sure. Not a trivial undertaking.
                    Routed through? What do you mean by that? This is trivial to implement because... On second thought,
                    I will let you figure it out.
                    Last edited by Dominic Mitchell; 16 Dec 2008, 11:19 PM.
                    Dominic Mitchell
                    Phoenix Visual Designer


                    • #11
                      The short answer: No

                      The long answer, with some tricks one may create some kind of ActiveX control
                      however one should then be able to access the events fired by the ActiveX which at this point I don't see how that could be accomplished.

                      Now, I may just overlook a lot of stuff and therefore don't take my word for it but I would think if this great compiler would be able to do this, there would be
                      some kind of example code in the samples directory which I didn't find.

                      To be 100% certain, just drop an email at the support department of PowerBasic. They will be able to answer all of your questions regarding this

                      So here we are, this is the end.
                      But all that dies, is born again.
                      - From The Ashes (In This Moment)


                      • #12

                        Routed through? What do you mean by that? This is trivial to implement because... On second thought, I will let you figure it out.
                        Dominic isn't being mean! He knows I like to solve these things like puzzels. Anyway, to answer Dominic's question all I meant was that the purported PB ActiveX control would need to fully and satisfactorily implement ...


                        ...because PowerBASIC fully implements the COM standards which will expect these interfaces to be present and working if PowerBASIC's 'With Events'
                        functionality is to receive events from the control. With my limited knowledge at least I see no way of getting around this. This is the required infrastructure/internal plumbing through which both objects (ActiveX control/client app) exchange pointers to each other's interfaces.

                        Thanks for setting me straight on the RegisterClass() thing Dominic. I hadn't 'mused' hard enough on it. The DllGetClassObject() would be the place to do it since that is (I believe???) only called one time where as the class factories are called for every instantiated object, I suppose.

                        The whole issue of windowed controls vs. windowless controls has me a bit confused. Where I ran into the issue is when I created the simple Atl tutorial project I mentioned above. If anyone is interested in persuing this topic as I am, and you have Visual Studio 6 and the MSDN docs that came with it, that tutorial is called Polygon and is at...

                        MSDN Library Visual Studio 6.0\Visual C++ Documentation\Reference\Microsoft Foundation Class Library And Templates\Active Template Library\Atl Tutorial
                        If you follow that tutorial you can create a functioning visual ActiveX control in about an hour. I wouldn't even say you need to know C++ to do it - lots of screenshots and wizards. The control is a geometric colored shape within a circle. There are properties to change the number of sides of the shape from 3 (a triangle) to a hundred or something. You can also set the color. When you click within the polygon the control fires an event to the client's sink. If you click outside the shape but within the enclosing circle, the control fires an event to that effect. When you build the project the control is automatically registered for you.

                        I had no trouble instantiating this control in Visual Basic or PowerBASIC. Below is the PowerBASIC source for a program that will instantiate the control.

                        #Compile            Exe
                        #Include            ""
                        #Include Once       ""
                        Global pPolyCtl     As IPolyCtl
                        Global pPolyEvents  As IPolyCtlEventsImpl
                        Type WndEventArgs
                          wParam            As Long
                          lParam            As Long
                          hWnd              As Dword
                          hInst             As Dword
                        End Type
                        Interface IPolyCtl Guid$("{873F0176-C037-4613-9A8F-A0BCD2EF5B2E}") : Inherit IDispatch
                          Property Set FillColor <-510> (Byval Dword)  ' IID = {873F0176-C037-4613-9A8F-A0BCD2EF5B2E}
                          Property Get FillColor <-510> () As Dword    ' IPolyCtl Interface
                          Property Get Sides <1> () As Integer         ' Attributes = 4160 [&H1040] [Dual] [Dispatchable]
                          Property Set Sides <1> (Byval Integer)       ' Inherited interface = IDispatch
                        End Interface
                        Class CIPolyCtlEvents Guid$("{C22266B9-EFB0-45D6-98D8-066734F7BA0B}") As Event
                          Interface IPolyCtlEventsImpl Guid$("{FF786299-5C0A-4D47-B753-44C8892838DC}") As Event
                            Inherit IDispatch                                    ' Class CIPolyCtlEvents
                            Method ClickIn<1>(Byval x As Long, Byval y As Long)  ' Interface name = _IPolyCtlEvents
                              MsgBox("You Clicked In The Colored Triangle")      ' IID = {FF786299-5C0A-4D47-B753-44C8892838DC}
                            End Method                                           ' _IPolyCtlEvents Interface
                            Method ClickOut<2>(Byval x As Long, Byval y As Long) ' Attributes = 4096 [&H1000] [Dispatchable]
                              MsgBox("You Clicked Outside The Colored Triangle") ' Code generated by the TypeLib Browser 4.0.11 (c) 2008 by José Roca
                            End Method                                           ' Date: 09 Nov 2008   Time: 18:02:43
                          End Interface
                        End Class
                        Function fnWndProc_OnCreate(Wea As WndEventArgs) As Long
                          Local pStream,ppUnkContainer As IUnknown
                          Local oPolyCtl As Dispatch
                          Local strCtrl As String
                          Local vVnt As Variant
                          Local hCtrl As Long
                          Call AtlAxWinInit()
                          strCtrl=UCode$("Polygon.PolyCtl")   'Program ID
                          pPolyEvents=Class "CIPolyCtlEvents"
                          If IsObject(pPolyCtl) Then
                             oPolyCtl=pPolyCtl         'Probably QueryInterfaces() For IID_IDISPATCH
                             vVnt=5                    :  Object Let oPolyCtl.Sides=vVnt
                             vVnt=RGB(&H255,&H0,&H0)   :  Object Let oPolyCtl.FillColor=vVnt
                             Events From pPolyCtl         Call pPolyEvents
                             Set oPolyCtl=Nothing
                          End If
                        End Function
                        Function fnWndProc_OnDestroy(Wea As WndEventArgs) As Long
                          If IsObject(pPolyCtl) And IsObject(pPolyEvents) Then
                             Events End pPolyEvents
                             Set pPolyEvents=Nothing : Set pPolyCtl=Nothing
                          End If
                          Call PostQuitMessage(0)
                        End Function
                        Function fnWndProc(ByVal hWnd As Long,ByVal wMsg As Long,ByVal wParam As Long,ByVal lParam As Long) As Long
                          Local Wea As WndEventArgs
                          Select Case As Long wMsg
                            Case %WM_CREATE
                              Wea.hWnd=hWnd : Wea.wParam=wParam : Wea.lParam=lParam
                              Exit Function
                            Case %WM_DESTROY
                              Call PostQuitMessage(0)
                              Exit Function
                          End Select
                        End Function
                        Function WinMain(ByVal hIns As Long,ByVal hPrev As Long,ByVal lpCL As Asciiz Ptr,ByVal iShow As Long) As Long
                          Local winclass As WndClassEx
                          Local szAppName As Asciiz*16
                          Local Msg As tagMsg
                          Local hWnd As Dword
                          szAppName="PolyCtrl"                                    : winclass.cbSize=SizeOf(winclass)
                 Or %CS_VREDRAW               : winclass.lpfnWndProc=CodePtr(fnWndProc)
                          winclass.cbClsExtra=0                                   : winclass.cbWndExtra=0
                          winclass.hInstance=hIns                                 : winclass.hIcon=LoadIcon(%NULL, ByVal %IDI_APPLICATION)
                          winclass.hCursor=LoadCursor(%NULL, ByVal %IDC_ARROW)    : winclass.hbrBackground=%COLOR_BTNFACE+1
                          winclass.lpszMenuName=%NULL                             : winclass.lpszClassName=VarPtr(szAppName)
                          Call RegisterClassEx(winclass)
                          hWnd=CreateWindow(szAppName,"PolyCtrl",%WS_OVERLAPPEDWINDOW Xor %WS_MAXIMIZEBOX,350,300,425,325,0,0,hIns,ByVal 0)
                          Call ShowWindow(hWnd,iShow)
                          While GetMessage(Msg,%NULL,0,0)
                            TranslateMessage Msg
                            DispatchMessage Msg
                        End Function
                        What's a bit interesting about the above code and relates I think to your comment about windowed vs. windowless controls is in the WM_CREATE message handler where I instantiate the control with...


                        The 2nd parameter is the handle of the window which the control will be attached to. I couldn't get the control to work if for a container I used a static control. I had to use the main form as the parent. If my WM_CREATE looked like this it would create the control within a window on the main window, but I couldn't get the events to work...

                        Function fnWndProc_OnCreate(Wea As WndEventArgs) As Long
                          Local pStream,ppUnkContainer,ppUnkControl As IUnknown
                          Local lpCreateStruct As CREATESTRUCT Ptr
                          Local hCtl,hContainer As Dword
                          Local hCtrl,iNull As Long
                          Local dwPtr As DWord Ptr
                          Local blnReturn As Long
                          Local strCtrl As String
                          Local fp As Integer
                          Open "Output.txt" For Output As #fp
                          lpCreateStruct=Wea.lParam   'Can use GetModuleHandle() here instead, or, use Global (Hate Globals!)
                          [email protected]
                          Call AtlAxWinInit()
                          hContainer= _
                          CreateWindowEx _
                          ( _
                            %WS_EX_OVERLAPPEDWINDOW,"static","",%WS_CHILD OR %WS_VISIBLE, _
                            10,10,400,250,Wea.hWnd,%ID_CONTAINER,Wea.hInst,Byval %NULL _
                          Print #fp,"@dwPtr = "@dwPtr
                          pPolyEvents=Class "CIPolyCtlEvents"
                          'Call AtlAxGetControl(hContainer,pPoly)    'neither works    'Global pPoly As IPolyCtl
                          pPolyCtl=AtlAxGetDispatch(hContainer)      '-------------    'Global pPolyEvents As IPolyCtlEventsImpl
                          If IsObject(pPolyCtl) Then
                             MsgBox("pPolyCtl Is An Object")
                             Events From pPolyCtl Call pPolyEvents
                             MsgBox("pPolyCtl Ain't Nothin")
                          End If
                          Print #fp, "hContainer = " hContainer
                          Print #fp,"@dwPtr = "@dwPtr
                          Close #fp
                        End Function
                        So to make a long story short, the thing refused to work if a control on the main form was set as the host of the control. I believe the C++ ATL wizard code created a 'windowless' control. At this point, its just a guess for me.

                        I'll include some additional information if anyone is interested. The above described ActiveX control is very simple. Its just a tutorial. But the wizard generated code clearly shows the interfaces that make up the guts of an ActiveX visual COM control. Although the project created many files (as MFC/ATL/C++ is famous for), the guts of the thing are in PolyCtl.h, and I'll post that code here shortly bellow. All the interfaces are listed in the class pretty much entertwined within pretty ugly macros and templates the workings of which I have not as yet figured out. But one can easily see there is some complexity involved. That being said, I fully expect for this particular turorial/object most of the implementation is probably nothing much more than 'stub' code. In that sense I wouldn't be to surprised if implementing it all in PowerBASIC wouldn't be that hard - as Dominic is saying. Anyway, here's the Atl C++ code. Here and there I made a few comments.

                        // PolyCtl.h : Declaration of the CPolyCtl
                        #ifndef __POLYCTL_H_
                        #define __POLYCTL_H_
                        #include <math.h>
                        #include "resource.h"       // main symbols
                        #include <atlctl.h>
                        #include "PolygonCP.h"
                        // CPolyCtl
                        class ATL_NO_VTABLE CPolyCtl :                       //these 19 objects below are publically inherited template classes
                         public CComObjectRootEx<CComSingleThreadModel>,
                         public CStockPropImpl<CPolyCtl, IPolyCtl, &IID_IPolyCtl, &LIBID_POLYGONLib>,
                         public CComControl<CPolyCtl>,
                         public IPersistStreamInitImpl<CPolyCtl>,
                         public IOleControlImpl<CPolyCtl>,
                         public IOleObjectImpl<CPolyCtl>,
                         public IOleInPlaceActiveObjectImpl<CPolyCtl>,
                         public IViewObjectExImpl<CPolyCtl>,
                         public IOleInPlaceObjectWindowlessImpl<CPolyCtl>,
                         public ISupportErrorInfo,
                         public IConnectionPointContainerImpl<CPolyCtl>,
                         public IPersistStorageImpl<CPolyCtl>,
                         public ISpecifyPropertyPagesImpl<CPolyCtl>,
                         public IQuickActivateImpl<CPolyCtl>,
                         public IDataObjectImpl<CPolyCtl>,
                         public IProvideClassInfo2Impl<&CLSID_PolyCtl, &DIID__IPolyCtlEvents, &LIBID_POLYGONLib>,
                         public IPropertyNotifySinkCP<CPolyCtl>,
                         public CComCoClass<CPolyCtl, &CLSID_PolyCtl>,
                         public CProxy_IPolyCtlEvents< CPolyCtl >
                         CPolyCtl()  //Constructor
                          m_nSides = 3;
                          m_clrFillColor = RGB(0, 0xFF, 0);
                         DECLARE_REGISTRY_RESOURCEID(IDR_POLYCTL)             //these are all the interfaces ActiveX
                         DECLARE_PROTECT_FINAL_CONSTRUCT()                    //controls typically implement.  I expect
                         BEGIN_COM_MAP(CPolyCtl)                              //for this trivial control many of them
                           COM_INTERFACE_ENTRY(IPolyCtl)                      //amount to little more than stubs
                           COM_INTERFACE_ENTRY2(IOleWindow, IOleInPlaceObjectWindowless)
                           COM_INTERFACE_ENTRY2(IPersist, IPersistStreamInit)
                           PROP_DATA_ENTRY("_cx",, VT_UI4)
                           PROP_DATA_ENTRY("_cy",, VT_UI4)
                           PROP_ENTRY("FillColor", DISPID_FILLCOLOR, CLSID_StockColorPage)
                           // Example entries
                           // PROP_ENTRY("Property Description", dispid, clsid)
                           // PROP_PAGE(CLSID_StockColorPage)
                           MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
                         // Handler prototypes:
                         //  LRESULT MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
                         //  LRESULT CommandHandler(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
                         //  LRESULT NotifyHandler(int idCtrl, LPNMHDR pnmh, BOOL& bHandled);
                         // ISupportsErrorInfo
                         STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid)
                          static const IID* arr[] = { &IID_IPolyCtl,};
                          for(int i=0; i<sizeof(arr)/sizeof(arr[0]); i++)
                              if(InlineIsEqualGUID(*arr[i], riid))
                                 return S_OK;
                          return S_FALSE;
                         // IViewObjectEx
                         // IPolyCtl
                         STDMETHOD(get_Sides)(/*[out, retval]*/ short *pVal);
                         STDMETHOD(put_Sides)(/*[in]*/ short newVal);
                         void CalcPoints(const RECT& rc);
                         HRESULT OnDraw(ATL_DRAWINFO& di)
                          RECT& rc = *(RECT*)di.prcBounds;
                          HDC hdc  = di.hdcDraw;
                          COLORREF    colFore;
                          HBRUSH      hOldBrush, hBrush;
                          HPEN        hOldPen, hPen;
                          // Translate m_colFore into a COLORREF type
                          OleTranslateColor(m_clrFillColor, NULL, &colFore);
                          // Create and select the colors to draw the circle
                          hPen = (HPEN)GetStockObject(BLACK_PEN);
                          hOldPen = (HPEN)SelectObject(hdc, hPen);
                          hBrush = (HBRUSH)GetStockObject(WHITE_BRUSH);
                          hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);
                          Ellipse(hdc, rc.left,, rc.right, rc.bottom);
                          // Create and select the brush that will be used to fill the polygon
                          hBrush    = CreateSolidBrush(colFore);
                          SelectObject(hdc, hBrush);
                          Polygon(hdc, &m_arrPoint[0], m_nSides);
                          // Select back the old pen and brush and delete the brush we created
                          SelectObject(hdc, hOldPen);
                          SelectObject(hdc, hOldBrush);
                          return S_OK;
                         OLE_COLOR m_clrFillColor;
                         short m_nSides;
                         POINT m_arrPoint[100];
                         LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
                          HRGN hRgn;
                          WORD xPos = LOWORD(lParam);  // horizontal position of cursor
                          WORD yPos = HIWORD(lParam);  // vertical position of cursor
                          // Create a region from our list of points
                          hRgn = CreatePolygonRgn(&m_arrPoint[0], m_nSides, WINDING);
                          // If the clicked point is in our polygon then fire the ClickIn
                          //  event otherwise we fire the ClickOut event
                          if(PtInRegion(hRgn, xPos, yPos))
                             Fire_ClickIn(xPos, yPos);
                             Fire_ClickOut(xPos, yPos);
                          // Delete the region that we created
                          return 0;
                        #endif //__POLYCTL_H_
                        Towards the bottom can be seen an OnDraw() and OnLButtonDown() message handler that contain nothing more than good ol' sdk api code.

                        Just occurred to me that PowerBASIC has already implemented the Connection Point interfaces, so somehow or other one would just need to tie into them. All that leaves then are those other interfaces!

                        Finally, it wouldn't bother me if PowerBASIC never implemented the ability to create ActiveX control. What they did do is make the ability to use already existing ones quite easy. That satisfies me.
                        Last edited by Fred Harris; 17 Dec 2008, 01:19 PM. Reason: add line


                        • #13
                          The DllGetClassObject() ... is (I believe???) only called one time where as the
                          class factories are called for every instantiated object, I suppose.
                          No. To see why this statement is not right, compare the documentation for CoCreateInstance and
                          CoGetClassObject. As you read the docs, keep in mind that CoCreateInstance calls CoGetClassObject.

                          You can use either CoCreateInstance or CoGetClassObject to create an instance of a control.
                          CoCreateInstance involves less work by the programmer but comes at a price.

                                                                                                                          pointer to
                          CoCreateInstance -> CoGetClassObject -> DllGetClassObject -> IClassFactory:::CreateInstance ->  interface 
                                            /|\                                                                       /|\ on object
                                                                   Hidden from programmer
                          This is fine if you are creating only one control but not very efficient when creating many controls with the
                          same CLSID at the same time. Let's say we want to create ten controls with the same CLSID. If CoCreateInstance
                          is used, DllGetClassObject will be called ten times. If CoGetClassObject is used, DllGetClassObject will be
                          called only once. That is because once we have a pointer to the IClassFactory interface, the CreateInstance
                          method can be called as many times as we like to instantiate a control of the same class.
                          Dominic Mitchell
                          Phoenix Visual Designer