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
...
class CCustomControlSite:public COleControlSite
{
public:
CCustomControlSite(COleControlContainer *pCnt):COleControlSite(pCnt){}
protected:
DECLARE_INTERFACE_MAP();
BEGIN_INTERFACE_PART(ServiceProvider, IServiceProvider)
STDMETHOD(QueryService) (
REFGUID guidService,
REFIID riid,
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
...
BEGIN_INTERFACE_MAP(CCustomControlSite, COleControlSite)
INTERFACE_PART(CCustomControlSite, IID_IServiceProvider, ServiceProvider)
END_INTERFACE_MAP()
...
...
STDMETHODIMP CCustomControlSite::XServiceProvider::QueryService(
REFGUID guidService, REFIID riid, void **ppvObject)
{
METHOD_PROLOGUE(CCustomControlSite, ServiceProvider)
*ppvObject = NULL;
if(guidService == SID_SMyService)
{
MessageBox(NULL, "QueryService for SID_SMyService",
"CCustomControlSite::XServiceProvider::QueryService", MB_OK);
if(riid == IID_IMyInterface)
{
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
...
class CCustomOccManager :public COccManager
{
public:
CCustomOccManager(){}
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
...
CCustomOccManager *pMgr = new CCustomOccManager;
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
...
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.