Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Overriding MFC's Default Client Site to Implement IServiceProvider

0.00/5 (No votes)
5 Oct 2003 1  
Describes how to override the default control container provided by MFC in order to provide a custom client site for provisioning of services to ActiveX controls hosted by the container.

Introduction

This article describes how to override the default client site implementation provided by MFC for hosting ActiveX controls. At first glance this may seem a bit arcane, but one of several cases in which you may find a real need for this is when you are developing an MFC application and using an ActiveX control that expects a custom service implementation in the hosting application to be accessible through the standard OLE control containment framework. You may also find that understanding this technique is useful in cases where you are the developer of the ActiveX control and wish to leverage services provided by the hosting application.

As an example, I once developed an MFC application that used a third-party ActiveX control that required the hosting application to implement some authentication-related services and to provide the control with access to them through IServiceProvider and the control containment model. This required the sort of override technique described in this article in order to point the control to a custom client site I created rather than MFC�s default client site.

For this article, I've modified Microsoft's driller sample (available at MSDN) to use as an example of how to accomplish the override. Though the driller sample has been freely available for some time, the modification and description of it in this article are useful for two reasons. First, the Microsoft sample is focused much more on demonstrating the manipulation of an embedded WebBrowser control than on explaining the mechanics of the control container/client site override. Second, the limited documentation has very little explanation of the concepts behind the sample. For these reasons it can be difficult to understand exactly how the MFC container override is accomplished and how it can be of use to you in contexts other than using the WebBrowser control.

Background

First, an important caveat: Microsoft provides no officially supported means (that I am aware of) for accomplishing what this article describes. Microsoft offers the driller sample as one possible approach and I use that, along with some undocumented MFC features, as the basis for the approach described in this article. Microsoft's documentation of their sample states that it "is implementation specific to MFC. So, if future versions of MFC change... this sample (and your code if you use this technique) might not work. We are looking at possible ways to have MFC expose the client site for customization." Be careful to consider what this warning means to you if you are planning to use this approach in your code.

Control Containers and Client Sites

I won't discuss the details of ActiveX control containment here, but a few words of background will be helpful for those unfamiliar with the concept. You can think of a control container as just what the name implies: a �container� in the hosting application that allows the application to include ActiveX controls and make meaningful use of them. For each ActiveX control "contained" by the application, the container must implement a client site.

A client site provides for communication between a control and the container. In support of this, client sites implement a number of defined interfaces that every control knows it can call on in order to report to the container or to request services from it. Likewise, controls can implement a number of well-known interfaces that the container can confidently call through every control�s client site.

This model of controls being sited in containers is as old as OLE, the technology model from which today's ActiveX controls derive. Though it has become well hidden for developers over the years, the control/site/container model remains fundamental to interactions between controls and their hosts. It is still in place today and you work with it, either directly or indirectly, every time you use an ActiveX control.

Requesting Service Interfaces Through Client Sites

As in the example I cited at the beginning of this article, it is not uncommon for ActiveX controls to request access to custom services that are implemented outside the client site by objects in the hosting application. A client site can implement IServiceProvider as a means of handling these requests from a control and then passing service interfaces from the object back to the control in response to the request. The control can then communicate directly with the service implementation in the host application without explicit reference to a client site or container. To implement this, a control may query its client site for IServiceProvider and then call IServiceProvider::QueryService to request a specific service interface. The control can then use this interface to call on the service.

The difficulty presented by MFC in this case is that the MFC framework provides a default implementation for container and client site construction and management with very little exposure of the internals. Though in most cases this abstraction is quite helpful, more control over the process is sometimes needed, as in the case of needing to point a control to a custom client site implementation that handles IServiceProvider-provisioned service interfaces.

What the Code Sample Demonstrates

The code sample accompanying this article illustrates how to implement a custom client site in MFC and connect it into the MFC container framework. It consists of a VC++ 6 workspace (MFC Client Site Override) containing two projects: an MFC application (Client Site Sample project) and an ActiveX control (CS Test Control project). Client Site Sample demonstrates how to implement the client site and a container manager that allows your site to substitute for MFC's default implementation. It also demonstrates how to implement IServiceProvider in the custom client site for service provisioning. CS Test Control demonstrates how an ActiveX control might call on your custom client site for a service interface and is provided so you can see a service call in action as it is handled by the custom client site in the MFC application.

The Code

The following steps walk through the Client Site Sample project. Each code listing is followed by a description of the implementation. Note that to run the code some minor modifications may be required. See the relevant section later in this article for information on these modifications.

Step 1: Declare the Custom Client Site

To provide a custom client site requires that we subclass MFC's client site encapsulation class: COleControlSite. The code in listing 1 illustrates this.

Listing 1

// custsite.h


...

// declare our custom control site to serve as the client site

class CCustomControlSite:public COleControlSite
{
public:
    // constructor associates this site with the container

    CCustomControlSite(COleControlContainer *pCnt):COleControlSite(pCnt){}

protected:

    // declare the ServiceProvider interface to create a nested class 
// XServiceProvider on which we can implement the interface. Note that
// the IUnknown functions are not explicitly declared here because the
// macros automatically declare them for us
DECLARE_INTERFACE_MAP(); BEGIN_INTERFACE_PART(ServiceProvider, IServiceProvider) // declare the interface method(s) STDMETHOD(QueryService) ( /* [in] */ REFGUID guidService, /* [in] */ REFIID riid, /* [out] */ void __RPC_FAR *__RPC_FAR *ppvObject); END_INTERFACE_PART(ServiceProvider) };

In this code we declare our custom client site, CCustomControlSite, as deriving from MFC's COleControlSite. We define the constructor to accept a pointer to a control container with which the site will be associated, and to forward the container pointer to the base class.

Step 2: Declare Client Site Interfaces

Now that we have a client site we can declare any interfaces our site must support. In our example, we want the client site to implement IServiceProvider to support services queries from controls, so we include servprov.h and then declare IServiceProvider and its QueryService member. We use MFC's interface map macros to simplify things by having MFC provide, among other things, a standard IUnknown implementation for us. This step is shown in the latter part of Listing 1.

Step 3: Implement the Client Site

Now we implement the client site. The following code illustrates this.

Listing 2

// custsite.cpp


...

BEGIN_INTERFACE_MAP(CCustomControlSite, COleControlSite)
    INTERFACE_PART(CCustomControlSite, IID_IServiceProvider, ServiceProvider)
END_INTERFACE_MAP()

...

// ServiceProvider implementation


...

// IServiceProvider::QueryService

// Description: support queries for service interfaces from controls

STDMETHODIMP CCustomControlSite::XServiceProvider::QueryService(
REFGUID guidService, REFIID riid, void **ppvObject) { METHOD_PROLOGUE(CCustomControlSite, ServiceProvider) *ppvObject = NULL; // support the SMyService service if(guidService == SID_SMyService) { MessageBox(NULL, "QueryService for SID_SMyService",
"CCustomControlSite::XServiceProvider::QueryService", MB_OK); // support query for IMyInterface interface if(riid == IID_IMyInterface) { // in a real implementation the interface would be returned via
// ppvObject
MessageBox(NULL, "QueryService for IID_IMyInterface",
"CCustomControlSite::XServiceProvider::QueryService", MB_OK); return E_NOINTERFACE; } } return E_NOINTERFACE; }

Note that the MFC interface macros create a nested class (XServiceProvider) and we simply implement its members. The implementation of QueryService examines the service ID and responds accordingly. We support a fictitious SMyService service and IMyInterface interface. Note that in a realistic implementation we would return the interface pointer via ppvObject, but for simplicity (we have to stop somewhere!) the example does not. Note that the interface that is returned could be from any object in the containing application, even from outside the client site.

Step 4: Implement a Custom Control Container Manager for the Client Site

MFC manages the container and client site framework through a control container manager. We must provide MFC with a custom control container manager by which it can connect our new client site into a container within the MFC framework. To do so, we subclass MFC's OLE control container manager class: COccManager. The following code illustrates this.

Listing 3

// custsite.h

...
// declare our control container manager

class CCustomOccManager :public COccManager
{
public:
    CCustomOccManager(){}
    // creates an instance of our custom control site and associates it
// with the container
COleControlSite* CreateSite(COleControlContainer* pCtrlCont) { CCustomControlSite *pSite = new CCustomControlSite(pCtrlCont); return pSite; } }; ...

This code declares our custom control container manager, CCustomOccManager, as deriving from MFC's COccManager. We override COccManager::CreateSite to accept from MFC a pointer to a control container, to create an instance of our custom client site bound to that container, and to then return a pointer to the site. Note that an include of occimpl.h is required to support this.

Step 5: Connect the Custom Client Site to MFC's Container

Now we need to point MFC to our custom client site. We do this by (in our client site sample application's InitInstance), creating an instance of our custom container manager and then modifying MFC�s existing call to AfxEnableControlContainer to use this instance instead of the default. The following code illustrates this.

Listing 3

// client site sample.cpp


...

    // Create a custom control container manager class so we can overide the 
// client site
CCustomOccManager *pMgr = new CCustomOccManager; // Set our control containment up but using our control container // manager class instead of MFC's default AfxEnableControlContainer(pMgr); ...

Once this is in place, MFC will use our custom container manager to create our custom client site, which will in turn be passed in MFC's internal call to the ActiveX control's SetClientSite in order to inform the control of its site. This establishes our custom client site, with IServiceProvider support for custom service interfaces, as the site for the control. At this point we have successfully overridden the default client site for controls with our own client site implementation.

To Run the Sample Code

You can walk through the code to see the interactions between MFC, the container, the client site, and the control, but there are a few things you need to set up first.

Modifications and Dependencies

For simplicity, the Client Site Sample project references absolute paths in the #includes of occimpl.h and you may need to change these to your VC++ install folder for the code to compile. The project also uses the CSTest control implemented in the CS Test Control project to show control/client site interaction, so be sure to build and register CSTest.

Using the Sample ActiveX Control to Observe the Client Site

I've included the CSTest sample ActiveX control so you can run the client site sample application and observe MFC's creation of the custom client site, and also see the control query into the custom site for IServiceProvider and the IMyInterface custom service interface. One way to do this is to open the MFC Client Site Override workspace that includes both the site and control projects. Build the projects with a debug configuration, point the CS Test Control project to the Client Site project's exe for debugging, insert breakpoints in Client Site at the creation of the control manager in InitInstance, and in CS Test Control's SetClientSite function, then run CS Test Control and step through the code.

The client site sample will display message boxes when the control queries it, and the control will update its text when it receives word back that the site supports IServiceProvider. The following code from the CS Test Control project illustrates how this is done in the control by overriding the control's SetClientSite, which is called by the MFC container to notify the control of the client site that has been created for it. Note that in a real implementation the control's call to QueryService would return a pointer to an interface (in pMI) that could be stored by the control and used at any time to directly call service functions.

Listing 4

// cstest.cpp

...
STDMETHOD(SetClientSite)(IOleClientSite *pClientSite)
{
    if (pClientSite) {
        IServiceProvider *pSrvPrv = NULL;
        IMyInterface* pMI = NULL;

        pClientSite->QueryInterface(IID_IServiceProvider, (void**)&pSrvPrv);
    
        if(pSrvPrv) {
            m_bSupportISP = true;
            pSrvPrv->QueryService(SID_SMyService, IID_IMyInterface, 
(void**)&pMI); pSrvPrv->Release(); } if(pMI) pMI->Release(); } return IOleObjectImpl<CTEST>::SetClientSite(pClientSite); }

What About .NET and MFC7?

With MFC7 you accomplish this in essentially the same way, but you do need to change your occimpl.h include to point to afxocc.h instead. Also, the CDHTMLDialog provided in MFC7 provides the very useful CreateControlSite member that you can override to create custom control sites on a per control basis when using the DHTML Dialog class.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here