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

Host Silverlight Control in C++ using ATL

0.00/5 (No votes)
18 May 2010 9  
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

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