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.
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);
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.
STDMETHOD(QueryService)(REFGUID rsid, REFIID riid, void** ppvObj);
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.
static HRESULT CreateXcpControl(HWND hwnd);
static HRESULT DestroyXcpControl();
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.
STDMETHOD(GetBaseUrl)(BSTR* pbstrUrl);
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