Silverlight ActiveX Hosting

Silverlight ActiveX Hosting

WindowlessMedia.zip - 525K
WindowlessMedia_demo.zip - 815K

Silverlight ActiveX Control

Introduction

I finally got some time for an update of the WindowlessMedia library. This update is mainly to explain how you can host Silverlight interface as binary resource in your application. This is a first folks, I hope you will find it useful.

Description

The library is quite stable now, I've been using it in two different projects. 3D based version of the container is available but unfortunately, Silverlight is not supported at this time. But this article is not about that so let me explain how to host Silverlight in your Windows application as a resource.
There are two ways you can use Silverlight in your C++ application. One method involves using an external resource which can be a file (file:///) or HTTP resource (http://). The second method access a Silverlight animation from your application resource. This is more advanced but can be used to completely hide your .xap file (this is a .zip file) from preying eyes!

Method 1

You can host Silverlight ActiveX control in the container simply by inserting the control to the dialog surface. As I've explained in my previous post about ActiveX hosting. You can safely rename your base class from CAxDialogImpl to CAxWindowlessHost. This class inherits from CDialogImplBaseT, thus, will have the same capabilities as any Windows dialog. Typically, the following code is all you need.
    class CMainDlg : public CAxWindowlessHost<CMainDlg>
    {
    public:
        enum { IDD = IDD_MAINDLG };

        BEGIN_MSG_MAP(CMainDlg)
            CHAIN_MSG_MAP(CAxWindowlessHost<CMainDlg>)
            MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
            COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
            COMMAND_ID_HANDLER(IDOK, OnOK)
            COMMAND_ID_HANDLER(IDCANCEL, OnCancel)
        END_MSG_MAP()

        LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
        LRESULT OnAppAbout(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
        LRESULT OnOK(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
        LRESULT OnCancel(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
    };
But to truly get Silverlight in Windowless mode, you need to initialize the control manually because the Silverlight Control doesn't have a public property for the Windowless mode. You can initialize the control from OnInitDialog like this:
    HRESULT hr;
    ActiveXSite* pSite;
    pSite = CAxWindowlessHost<CMainDlg>::CreateControlSite(L"AgControl.AgControl", NULL, IDC_AGCONTROL1);
    if ( pSite != NULL )
    {
        // disable right-click!
        pSite->SetAllowRClick(false);
        // set moniker URL
        CComBSTR bstrUrl("file:///C:/Temp/SilverlightDemo.xap");
        pSite->SetUrl(bstrUrl);

        PropertyParams props;
        props.push_back( PropertyParam(L"Windowless", L"true") );
        props.push_back( PropertyParam(L"MinRuntimeVersion", L"2.0.31005.0") );
        props.push_back( PropertyParam(L"Source", static_cast<LPCWSTR>(bstrUrl)) );
        props.push_back( PropertyParam(L"InitParams", L"") );
        hr = pSite->ActivateAx(rc, false, props);
    }

Method 2

Under some scenarios, you may want to embedded your Silverlight application in a Windows resource DLL. This section will show you how to proceed.
Due to security restrictions, you can't use a resource moniker (res://). But here's what is possible (as of version 2.0). You can host the control by implementing the IXcpControlHost2 interface. With this interface, the resource can be loaded from your own storage and simply pass an IStream data object to the control. My proposed method to do this is to use a file URL to point to your application resource and then, load the resource manually when Silverlight calls the DownloadUrl method.
Your dialog class or container would look like this:
    class CMainDlg : public CAxWindowlessHost<CMainDlg>,
                     public IXcpControlHostImpl
    {
    public:
        enum { IDD = IDD_MAINDLG };

        BEGIN_MSG_MAP(CMainDlg)
            MESSAGE_HANDLER(WM_LBUTTONUP, OnWmLButtonUp)
            CHAIN_MSG_MAP(CAxWindowlessHost<CMainDlg>)
            MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
            COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
            COMMAND_ID_HANDLER(IDOK, OnOK)
            COMMAND_ID_HANDLER(IDCANCEL, OnCancel)
        END_MSG_MAP()

        // IUnknown
        virtual ULONG STDMETHODCALLTYPE AddRef();
        virtual ULONG STDMETHODCALLTYPE Release();
        STDMETHOD(QueryInterface)(REFIID iid, void** object);

        // IXcpControlHost
        virtual HRESULT STDMETHODCALLTYPE GetHostOptions(DWORD* pdwOptions);
        virtual HRESULT STDMETHODCALLTYPE GetBaseUrl(BSTR * pbstrUrl);
        virtual HRESULT STDMETHODCALLTYPE DownloadUrl(BSTR bstrUrl, IXcpControlDownloadCallback *pCallback,
                                          IStream **ppStream);

        LRESULT OnInitDialog(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
        LRESULT OnAppAbout(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
        LRESULT OnOK(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
        LRESULT OnCancel(WORD /*wNotifyCode*/, WORD wID, HWND /*hWndCtl*/, BOOL& /*bHandled*/);
        LRESULT OnWmLButtonUp(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);
    };
The only drawback to this method is the fact you need to handle WM_LBUTTONUP and set the focus to the control manually. The Silverlight ActiveX control will no longer grab the focus when you implement this interface.
Silverlight Control calls your DownloadUrl implementation. From that method you can load the resource and present it the the callback IXcpControlDownloadCallback. Your code will typically look like this:
HRESULT STDMETHODCALLTYPE CMainDlg::DownloadUrl(BSTR bstrUrl, IXcpControlDownloadCallback *pCallback,
                                                IStream **ppStream)
{
    HRESULT hr = S_OK;
    if ( ppStream != NULL ) *ppStream = NULL;
    LPOLESTR pszPath = bstrUrl;
    if ( _wcsnicmp(L"file:///", bstrUrl, 8) == 0 )    // 8 = wcslen(L"file:///")
        pszPath += 8;
    else
        hr = S_FALSE;
    if ( hr == S_OK )
    {
        // Typically, you can parse the URL and find the correct resource
        // Url format: "file:///#resource"
        // where:  is your application path (e.g.: c:/program files/yourapplication/yourapp.exe)
        //        #resource is your resource number (e.g.:#1001) which represents your .xap resource file.
        HGLOBAL hGlobal = GetResourceData(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDR_BIN1), _T("BIN"));
        if ( hGlobal != NULL )
        {
            CComPtr<IStream> pStream;
            hr = CreateStreamOnHGlobal(hGlobal, TRUE, &pStream);
            if ( SUCCEEDED(hr) )
            {
                if ( pCallback != NULL ) {
                    hr = pCallback->OnUrlDownloaded(hr, pStream);
                }
                else if ( ppStream != NULL ) {
                    *ppStream = pStream.Detach();
                }
            }
            else
            {
                GlobalFree(hGlobal);
                hr = E_FAIL;
            }
        }
        else
        {
            hr = E_FAIL;
        }
    }
    return hr;
}
The method GetResourceData loads and copies the resource to memory.
HGLOBAL GetResourceData(HMODULE hModule, LPCTSTR lpName, LPCTSTR lpType)
{
    HGLOBAL hMemory = NULL;
    HRSRC hrsrc = FindResource(hModule, lpName, lpType);
    if ( hrsrc != NULL )
    {
        HGLOBAL hResData = LoadResource(hModule, hrsrc);
        hMemory = GlobalAlloc(GHND, SizeofResource(hModule, hrsrc));
        if ( hMemory != NULL )
        {
            LPVOID lpvRes = LockResource( hResData );
            LPVOID lpvMem = GlobalLock( hMemory );
            if (lpvMem != NULL && lpvRes != NULL)
            {
                memcpy(lpvMem, lpvRes, SizeofResource(hModule, hrsrc));
            }
            UnlockResource( hResData );
            GlobalUnlock( hMemory );
        }
    }
    return hMemory;
}
WSilverlightHost demo shows how to use IXcpControlHost2 interface. The Silverlight Application (SilverlightDemo.xap) is very simple but you can use the same technique to host your own user interface. Basically, the XAML code for the demo is shown below:
<UserControl x:Class="SilverlightDemo.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="200" Height="200">
    <StackPanel Orientation="Horizontal">
        <Button Width="100" Content="OK" Click="Button_Click"></Button>
    </StackPanel>
</UserControl>    

Conclusion

This article shows how you can use the WindowlessMedia library to host Silverlight as a resource. The steps are still very straightforward. Hopefully, you will find this information useful for your need. Of course, feel free to use the feedback page if you have a question about this library.

History

04/17/2009: Created WSilverlightHost demo
06/29/2009: Updated for Silverlight keyboard handling
05/20/2010: Updated samples and library
0 Comments