65.9K
CodeProject is changing. Read more.
Home

Host Silverlight Control in C++ using ATL

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (20 votes)

May 18, 2010

CPOL

2 min read

viewsIcon

176649

downloadIcon

1451

Hosting Silverlight control in C++ using ATL

Introduction

This article explains how we can host Microsoft Silverlight Control in a C++ application using ATL/WTL without using the browser (Internet Explorer/Firefox).

Mostly people think that Silverlight control can't be used in a C++ application. In this article, we will walk through hosting a Silverlight control and loading Silverlight contents (.xap) file in it.

Using this approach, we can write desktop based applications using Silverlight (without using Internet Explorer/Firefox) and distribute it using Microsoft Silverlight runtime which is very light weight as compared to the .NET Framework.

Why do we need to host the Silverlight Control in a C++ application? Why don't we use WPF right away? This approach gives you a way to make your desktop application with a Silverlight User Interface and without using the complete .NET Framework. We only need Silverlight runtime.

I am using Silverlight 4.0 for this sample.

COM Reference

Microsoft provides COM reference for Silverlight control. Please take a look at this URL.

Create an ATL Application

Create an ATL out-of-process(executable) project. I used "AtlProject" name as my sample.

Step 1

Implement IXcpControlHost2 Interface.

Open AtlProject.idl file and use following .idl file in order to implement IXcpControlHost to host the Silverlight ActiveX control. This .idl is provided by Microsoft.

// AtlProject.idl : IDL source for AtlProject
//
// This file will be processed by the MIDL tool to
// produce the type library (AtlProject.tlb) and marshalling code.

import "oaidl.idl";
import "ocidl.idl";
[
    object,
    uuid(2E340632-5D5A-427b-AC31-303F6E32B9E8),
    nonextensible,
    helpstring("IXcpControlDownloadCallback Interface"),
    pointer_default(unique)
]

interface IXcpControlDownloadCallback : IUnknown
{
    HRESULT OnUrlDownloaded(HRESULT hr, IStream* pStream);

};

[
    object,
    uuid(1B36028E-B491-4bb2-8584-8A9E0A677D6E),
    nonextensible,
    helpstring("IXcpControlHost Interface"),
    pointer_default(unique)
]

interface IXcpControlHost : IUnknown
{
    typedef enum
    {
        XcpHostOption_FreezeOnInitialFrame = 0x001,
        XcpHostOption_DisableFullScreen = 0x002,
        XcpHostOption_DisableManagedExecution = 0x008,
        XcpHostOption_EnableCrossDomainDownloads = 0x010,
        XcpHostOption_UseCustomAppDomain = 0x020,
        XcpHostOption_DisableNetworking = 0x040, 
        XcpHostOption_DisableScriptCallouts = 0x080,
        XcpHostOption_EnableHtmlDomAccess = 0x100,
        XcpHostOption_EnableScriptableObjectAccess = 0x200,
    } XcpHostOptions;

    HRESULT GetHostOptions([out, retval] DWORD* pdwOptions);
    HRESULT NotifyLoaded();
    HRESULT NotifyError([in] BSTR bstrError, [in] BSTR bstrSource, 
	[in] long nLine, [in] long nColumn);
    HRESULT InvokeHandler([in] BSTR bstrName, [in] VARIANT varArg1, 
	[in] VARIANT varArg2, [out, retval] VARIANT* pvarResult);
    HRESULT GetBaseUrl([out, retval] BSTR* pbstrUrl);
    HRESULT GetNamedSource([in] BSTR bstrSourceName, [out, retval] BSTR* pbstrSource);
    //
    // Called by Silverlight to allow a host to provide content for a specified URI. 
    // This is useful in cases where a resource would normally be loaded out of an 
    // XAP at runtime. At design time, no XAP exists, and
    // the host can provide content for that resource.
    //
    // This method can work synchronously or asynchronously. 
    // If the pCallback parameter is NULL, the host must
    // do the work synchronously and return the result in ppStream. 
    // If the pCallback parameter is non-NULL, the host
    // may do the work synchronously or asynchronously, 
    // invoking callback methods as defined by the 
    // IXcpControlDownloadCallback interface. 
    // If the host chooses to work asynchronously, the ppStream parameter is
    // ignored.
    //
    // The host should return S_FALSE if it cannot provide a resource 
    // for the requested URI, and S_OK on a
    // successful request.
    //
    HRESULT DownloadUrl([in] BSTR bstrUrl, 
	[in] IXcpControlDownloadCallback* pCallback, [out, retval] IStream** ppStream);
};

[
    object,
    uuid(fb3ed7c4-5797-4b44-8695-0c512ea27d8f),
    nonextensible,
    helpstring("IXcpControlHost2 Interface"),
    pointer_default(unique)
]

interface IXcpControlHost2 : IXcpControlHost
{
    HRESULT GetCustomAppDomain([out, retval] IUnknown** ppAppDomain);
    HRESULT GetControlVersion([out] UINT *puMajorVersion, [out] UINT *puMinorVersion);
};

[
    uuid(60074165-9D7F-43C9-B0A9-127FB2B8C267),
    version(1.0),
    helpstring("AtlProject 1.0 Type Library")
]

library AtlProjectLib
{
    importlib("stdole2.tlb");
    [
        uuid(1ADB6913-DD12-4F9E-95C8-46D4D01A8B65),
        helpstring("XcpControlHost Class")
    ]

    coclass XcpControlHost
    {
        [default] interface IXcpControlHost;
    };
};

Step 2

CXcpControlHost Implementation.

Inherit CXcpControlHost from CAxHostWindow and IXcpControlHost2 (defined in .idl file). CAxHostWindow will provide the hosting services and our application will act like a Silverlight control host.

class CXcpControlHost :
    public CAxHostWindow,
    public IXcpControlHost2
{

Step 3

We should override QueryService and return the XcpControlHost/IXCPControlHost2/IXCPControlHost3 interface when host queries for it. Host queries for it the moment the Silverlight Activex Control is created.

// XcpControlHost.h 
//IServiceProvider Implementation
STDMETHOD(QueryService)(REFGUID rsid, REFIID riid, void** ppvObj);

// XcpControlHost.cpp 
///////////////////////////////////////////////////////////////////////////////
// CXcpControlHost IServiceProvider Implementation
STDMETHODIMP CXcpControlHost::QueryService(REFGUID rsid, REFIID riid, void** ppvObj) 
{
    ATLASSERT(ppvObj != NULL);
    if (ppvObj == NULL)
        return E_POINTER;

    *ppvObj = NULL;
    HRESULT hr = E_NOINTERFACE;
    if ((rsid == IID_IXcpControlHost) && (riid == IID_IXcpControlHost)) 
    {
        ((IXcpControlHost*)this)->AddRef();
        *ppvObj = (IXcpControlHost*)this;
        hr = S_OK;
    }

    if ((rsid == IID_IXcpControlHost2) && (riid == IID_IXcpControlHost2)) 
    {
        ((IXcpControlHost2*)this)->AddRef();
        *ppvObj = (IXcpControlHost2*)this;
        hr = S_OK;
    }
    return hr;
}

Step 4

Control creation code.

// XcpControlHost.h
// Infrastructure for control creation.
static HRESULT CreateXcpControl(HWND hwnd);
static HRESULT DestroyXcpControl();

// XcpControlHost.cpp
HRESULT CXcpControlHost::CreateXcpControl(HWND hWnd) 
{
    AtlAxWinInit();
    HRESULT hr;
    static const GUID IID_IXcpControl = 
    { 0x1FB839CC, 0x116C, 0x4C9B, { 0xAE, 0x8E, 0x3D, 0xBB, 0x64, 0x96, 0xE3, 0x26 }};
    static const GUID CLSID_XcpControl = 
    { 0xDFEAF541, 0xF3E1, 0x4c24, { 0xAC, 0xAC, 0x99, 0xC3, 0x07, 0x15, 0x08, 0x4A }};
    static const GUID IID_IXcpControl2 = 
    { 0x1c3294f9, 0x891f, 0x49b1, { 0xBB, 0xAE, 0x49, 0x2a, 0x68, 0xBA, 0x10, 0xcc }};
    
    hr = CoCreateInstance(CLSID_XcpControl, NULL, CLSCTX_INPROC_SERVER, 
	IID_IUnknown, (void**)&pUnKnown);
    if (SUCCEEDED(hr)) 
    {
        CComPtr<IUnknown> spUnkContainer;
        hr = CXcpControlHost::_CreatorClass::CreateInstance(NULL, 
		IID_IUnknown, (void**)&spUnkContainer);
        if (SUCCEEDED(hr)) 
        {
            CComPtr<IAxWinHostWindow> pAxWindow;
            spUnkContainer->QueryInterface(IID_IAxWinHostWindow, (void**)&pAxWindow);
            pUnKnown->QueryInterface(IID_IXcpControl2, (void**)&pControl);
            hr = pAxWindow->AttachControl(pUnKnown, hWnd); 
            hControlWindow = hWnd;
            IOleInPlaceActiveObject *pObj;
            hr = pControl->QueryInterface(IID_IOleInPlaceActiveObject, (void**)&pObj);
        }    
    }

    return hr;
}

HRESULT CXcpControlHost::DestroyXcpControl()
{
    HRESULT hr = S_OK;
    if (pControl)
    {
        pControl->Release();
    }
    if (pUnKnown)
    {
        pUnKnown->Release();
    }
    return hr;
}

Step 5

Property Bag class for Silverlight control. This class is used to set control properties like background color, setting up the .xap file name and many more.

class CXcpPropertyBag:IPropertyBag
{
public:
	CXcpPropertyBag(){m_nRef=0;}
	~CXcpPropertyBag(){}
	HRESULT _stdcall QueryInterface(REFIID iid, void** ppvObject)
	{
		return S_OK;
	}

	ULONG _stdcall AddRef()
	{
		return ++m_nRef;
	}

	ULONG _stdcall Release()
	{
		if(--m_nRef == 0)
			delete this;
		return m_nRef;
	}
	ULONG m_nRef;
	STDMETHOD (Read)(LPCOLESTR pszPropName, VARIANT *pVar, IErrorLog *pErrorLog)
	{
		HRESULT hr = E_INVALIDARG;
		BSTR bstrValue = NULL;

		if (_wcsicmp(pszPropName, L"Source") == 0) 
		{
			bstrValue = SysAllocString(L"SilverlightTestApp.xap");
		} 
		else if (_wcsicmp(pszPropName, L"Background") == 0) 
		{
			bstrValue = SysAllocString(L"Gold");
		}
		else if (_wcsicmp(pszPropName, L"Windowless") == 0) 
		{
			V_VT(pVar) = VT_BOOL;
			V_BOOL(pVar) = VARIANT_FALSE;
			hr = S_OK;
		}
		if (bstrValue != NULL) 
		{
			V_VT(pVar) = VT_BSTR;
			V_BSTR(pVar) = bstrValue;
			hr = S_OK;
		}
		return hr;
	}

	STDMETHOD (Write)(LPCOLESTR pszPropName, VARIANT *pVar)
	{
		return S_OK;
	}
};

Step 6

Implement IXcpControl2::GetBaseUrl to set the base folder from where your .xap file and other assemblies will be loaded.

// XcpControlHost.h 

STDMETHOD(GetBaseUrl)(BSTR* pbstrUrl);

// XcpControlHost.cpp
STDMETHODIMP CXcpControlHost::GetBaseUrl(BSTR* pbstrUrl) 
{
	CAtlString strPath;
	TCHAR pBuff[MAX_PATH];
	GetCurrentDirectory(MAX_PATH, pBuff);
	strPath = pBuff;
	strPath += "\\";
	*pbstrUrl = SysAllocString(strPath);
	return S_OK;
}

Step 7

Insert a dialog box in your ATL project. Place a static control in the newly created dialog. In OnInitDialog(), call CreateXcpControl(...) by passing the handle of static control window.

CXcpControlHost::CreateXcpControl(GetDlgItem(IDC_STATIC_CONTROL).m_hWnd);

We are done with the hosting of Silverlight control. Now it's time to create a Silverlight app which we will load in a C++ application.

NOTE

If someone is using an older version of Silverlight, please change the following line in the code with the correct version of Silverlight.

#import "libid:283C8576-0726-4DBC-9609-3F855162009A" version("4.0")
to
#import "libid:283C8576-0726-4DBC-9609-3F855162009A" version("3.0")
or 
#import "libid:283C8576-0726-4DBC-9609-3F855162009A" version("2.0")

Conclusion

In this article, I've shown you how to host a Silverlight control in a C++ application.

Hopefully, this article will help you a little bit when it comes to hosting Silverlight Control in C++ application. Next steps would be communication between a Silverlight application and a Silverlight host in C++. Loading assemblies dynamically, etc...

For questions, comments and remarks, please use the commenting section at the bottom of this article.

History

  • 18th May, 2010: Initial post