Topics on Microsoft COM

The difference between class factory and class object

A class factory is an object that implements the IClassFactory interface, whereas a class object implements a custom activation interface instead of, or in addition to IClassFactory. A custom activation interface must inherit from IUnknown. To instantiate a coclass via this custom interface, we would do something like this:

IXXXFactory pXXXFactory;

CoGetClassObject(CLSID_XXX, CLSCTX_SERVER, NULL, IID_IXXXFactory,
(void**)&pXXXFactory);

// invoke methods of IXXXFactory to instantiate a coclass
pXXXFactory->...;
Monikers is an example of objects that cannot be created using the IClassFactory interface (and therefore using the CoCreateInstance() function), and must be created with CoGetClassObject() instead.

Embedding or reusing a WebBrowser control using the Active Template Library (ATL)



What I intend to demonstrate is possibly the simplest way of embedding a WebBrowser control using ATL (see screenshot above). ATL has the advantage of being lightweight, but is (at least for me) difficult to understand, not to mention master. However by studying the code below, hopefully you will get a better understanding of ATL and also learn how to get started. Let us start with the main application loop in file UtBrowser.cpp.

UtBrowser.cpp:
#include "StdAfx.h"
#include "resource.h"
#include "CUtFrame.h"

CComModule _Module;

/////////////////////////////////////////////////////////////////////////////
//
extern "C" int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE /*hPrevInstance*/, LPTSTR lpCmdLine, int /*nShowCmd*/)
{
    CUtFrame frame;
    HICON hIcon;
    HMENU hMenu;
    RECT rcPos = { CW_USEDEFAULT, 0, 0, 0 };
    MSG msg;

    // _Module.Init(NULL, hInstance) no longer works as of ATL7
    _Module.Init(NULL, hInstance, &LIBID_ATLLib);

    hIcon = LoadIcon(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDI_ICON));
    frame.GetWndClassInfo().m_wc.hIcon = hIcon;

    hMenu = LoadMenu(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDR_MENU));
    frame.Create(GetDesktopWindow(), rcPos, _T("UtBrowser"), 0, 0, (UINT)hMenu);

    frame.ShowWindow(SW_SHOWNORMAL);

    while (GetMessage(&msg, 0, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    _Module.Term();

    return 0;
}

It is obvious what the code does. It instantes the module, loads icon, loads the menu resource, shows the main window which is implemented by CUtFrame, and gets into a typical Windows application loop, before terminating the module. The code for CUtFrame follows:

CUtFrame.h:
#ifndef __CUtFrame_h__
#define __CUtFrame_h__

#include "resource.h" // main symbols

/////////////////////////////////////////////////////////////////////////////
// CUtFrame - Example of generic ActiveX Hosting window

class CUtFrame : public CWindowImpl<
    CUtFrame,
    CWindow,
    CWinTraits<WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPCHILDREN |
    WS_CLIPSIBLINGS, WS_EX_APPWINDOW | WS_EX_WINDOWEDGE> >
{
public:

    CAxWindow m_wndBrowserContainer;
    IWebBrowser2 *m_pBrowserControl;

    DECLARE_WND_CLASS_EX(NULL, 0, 0)

    BEGIN_MSG_MAP(CUtFrame)
        MESSAGE_HANDLER(WM_CREATE, OnCreate)
        MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
        COMMAND_ID_HANDLER(ID_FILE_EXIT, OnFileExit)
        MESSAGE_HANDLER(WM_SIZE, OnSize)
        MESSAGE_HANDLER(WM_ERASEBKGND, OnErase)
    END_MSG_MAP()

    void OnFinalMessage(HWND /*hWnd*/)
    {
        ::PostQuitMessage(0);
    }

    LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
       if (m_pBrowserControl != NULL) m_pBrowserControl->Release();
        return 0;
    }

    LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
    {
        RECT rcClient;
        HRESULT hresult;
        IUnknown *pUnk;
        VARIANT v; //just a tmp VARIANT

        AtlAxWinInit();

        GetClientRect(&rcClient);
        m_wndBrowserContainer.Create(m_hWnd, rcClient, _T(""),
            WS_CHILD|WS_VISIBLE|WS_CLIPCHILDREN|WS_CLIPSIBLINGS|WS_HSCROLL|WS_VSCROLL);

        // whenever the first argument is a RESID, a browser control will
        // automatically be created:
        hresult = m_wndBrowserContainer.CreateControlEx(
            ID_WEBBROWSER, NULL, NULL, &pUnk, IID_NULL, NULL);
        if (FAILED(hresult)) {
            OutputDebugString("CreateControlEx() failed.\n");
        } else {
            if (pUnk != NULL) {
                pUnk->QueryInterface(IID_IWebBrowser2, (void**)&m_pBrowserControl);
                pUnk->Release();
                if (m_pBrowserControl == NULL) {
                    OutputDebugString("Failed getting IWebBrowser2 from IUnknown.\n");
                    return 0;
                }
            } else {
                OutputDebugString("CreateControlEx() succeeded but pUnk is NULL.\n");
            }
        }

        v.vt=VT_I4;
        v.lVal=navNoHistory;
        m_pBrowserControl->Navigate(L"http://www.google.com", &v, NULL, NULL, NULL);

        return 0;
    }

    LRESULT OnErase(UINT /* uMsg */, WPARAM /* wParam */, LPARAM /* lParam */, BOOL& bHandled)
    {
        return 1;
    }

    LRESULT OnSize(UINT /* uMsg */, WPARAM /* wParam */, LPARAM /* lParam */, BOOL& /* lResult */)
    {
        RECT rcClient;
        GetClientRect(&rcClient);
        m_wndBrowserContainer.MoveWindow(0,0,rcClient.right,rcClient.bottom,TRUE);
        return 0;
    }

    LRESULT OnFileExit(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
    {
        DestroyWindow();
        return 0;
    }
};

#endif

The big picture is clear and simple: we have to host the WebBrowser control in a suitable container, and all we have to do is to make CUtFrame such a container. The function that plays the central role is AtlAxWinInit(). Next you create an intemediary window that acts as a container to host the WebBrowser control, by using function (or method to be precise) CAxWindow.Create(). Finally call CAxWindow.CreateControlEx() on the container to instantiate the WebBrowser control. This function as a side effect returns an IUnknown pointer to the WebBrowser control. By calling QueryInterface on the returned IUnknown pointer, the pointer to the IWebBrowser2 interface is easily obtained. The rest of the code is self-explanatory. For completeness, the file StdAfx.h which is pretty standard is given below, but the file resource.h and the associated icon and menu resource can easily be authored yourself. That's it, and please say this is easy :)

StdAfx.h:
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#ifndef STRICT
#define STRICT
#endif

#ifndef VC_EXTRALEAN
#define VC_EXTRALEAN
#endif

//#define _ATL_APARTMENT_THREADED

#include <atlbase.h>

extern CComModule _Module; //necessary

#include <atlcom.h>
#include <atlhost.h>
#include <atlctl.h>

On casting...

Casting is an essential technique in COM programming. Usually beginners are confused with these casting operators: static_cast<>(), dynamic_cast<>(), reinterpret_cast<>(). These operators are intended to remove some of the ambiguity and danger inherent in old style C language casts:

dynamic_cast Used for conversion of polymorphic types.
static_cast Used for conversion of nonpolymorphic types.
const_cast Used to remove the const, volatile, and __unaligned attributes.
reinterpret_cast Used for simple reinterpretation of bits.

Both of the expressions static_cast<type-id>(expression), dynamic_cast<type-id>(expression) convert expression to the type of type-id. Both mechanisms move a pointer throughout a class hierarchy, but static_cast is based solely on the types present in the expression -- no run-time type check is made to ensure the safety of the conversion. dynamic_cast uses run-time type information to check whether a pointer actually points to a complete object.

 class B { ... };
class D : public B { ... };
void f(B* pb) {
D* pd1 = dynamic_cast<D*>(pb);
D* pd2 = static_cast<D*>(pb);
}

If pb really points to an object of type D, then pd1 and pd2 will get the same value. They will also get the same value if pb == 0. If pb points to an object of type B and not to the complete D class, then dynamic_cast will know enough to return zero. However, static_cast relies on the programmer's assertion that pb points to an object of type D and simply returns a pointer to that supposed D object. Consequently, static_cast can do the inverse of implicit conversions, in which case the results are undefined. It is left to the programmer to ensure that the results of a static_cast conversion are safe. A fine example of usage of static_cast is provided by the ATL: there's a macro offsetofclass defined as:

 #define offsetofclass(base, derived) \
((DWORD)(static_cast((derived *)8))-8)
The value of 8 has no significance, it just has to be something apart from 0, because in C++, casting a null pointer always results in a null pointer. The macro works by
  1. Treat the number 8 as a pointer to derived.
  2. Cast the "fake" derived pointer to a base pointer, by offsetting the pointer.
  3. Subtract the original number 8 from the offsetted value.
  4. Finally cast the result to a DWORD (a 32-bit unsigned integer or the address of a segment and its associated offset).

The reinterpret_cast operator allows any pointer to be converted into any other pointer type, e.g. char* to int*, or One_class* to Unrelated_class*, which are inherently unsafe.

Glossary

ActiveX
A set of technologies which uses COM to enable software components to interact with each other in a networked environment, regardless of the language in which they are created. ActiveX includes both client and server technologies, e.g. ActiveX Control, ActiveX Documents, ActiveX Scripting.
ActiveX control
  • An in-process server (typically a small object) that can be used in any OLE container that supports ActiveX control;
  • Interacts with its container via a 2-way communication mechanism: the control fires events to the container whereas the container modifies the properties and calls the methods of the control. The Control inherits the event-firing capability from the COleControl class, and exposes its sets of properties and methods using a dispatch map;
  • Can have 2 modes: windowed and windowless. Windowless mode offers smaller instance object size and lower creation time.
ActiveX document
  • A full-scale, conventional document that is hosted by a container;
  • Can have more than one page, has complete control over their pages, and are always in-place active;
  • Also controls part of the user interface, merging their menus with that of the container, occupies the entire editing area of the container and controls the views and the layout of the printer page;
  • Can exploit the complete native functionality of the server that creates it.
COM+
The name of COM when you combine it with Microsoft Transaction Server (MTS), and Distributed COM (DCOM). COM+ provides a set of middle-tier-oriented services. In particular, COM+ provides process management and database and object connection pooling.
Connection point
Something that allows an object to "talk back" to its client(s). Objects that support connection points are called connectable objects. Each connection point supports the IConnectionPoint interface. The connectable object exposes its connection points to the client through the IConnectionPointContainer interface.
Distributed Component Object Model (DCOM)
A protocol based on the Open Software Foundation's DCE-RPC specification, that enables COM components to communicate directly over a network in a reliable, secure, and efficient manner.
Microsoft Transaction Server (MTS)
A transaction service in the Microsoft Windows NT operating system that simplifies the development and deployment of server-centric applications built using COM. It's essentially a TP monitor and an object request broker (ORB) combined into an easy-to-use package.
Moniker
An object that identifies another object. Sometimes also known as intelligent names. Monikers implement the IMoniker interface. This interface can be thought of as a kind of factory interface, the method IMoniker::ParseDisplayName() of which can be used to create other moniker objects. Note: IMoniker::ParseDisplayName() might or might not call IParseDisplayName::ParseDisplayName() depending on the implementation.
Comments