Introduction
This article illustrates the implementation of a simple OLE container, used for hosting a transparent or windowed Flash Player object.
Part 1. OLE Interfaces
The simplest OLE control implementation for an ActiveX object should consist of several interfaces: IOleClientSite
, IOleInPlaceSiteWindowless
, IOleInPlaceFrame
, and IStorage
.
My implementation of the OLE container is called COleContainerWnd
. It is declared as follows:
template<CLASS TObj> class COleContainerWnd :
virtual public IOleClientSite,
virtual public IOleInPlaceSiteWindowless,
virtual public IOleInPlaceFrame,
virtual public IStorage
where TObj
is the desired interface of an ActiveX object.
Part 2. Flash object
Importing the Flash object into VC++ is done using the #import
command:
#import "c:\\windows\\system32\\macromed\\flash\\flash.ocx" named_guids
The named_guids
directive is used to generate the CLSID_ShockwaveFlash
class ID. If you are running under Windows 2000, change the system folder to "winnt".
As a result of the #import
command, the compiler will generate the flash.tli and flash.tlh files containing the Flash Player interface declarations.
Part 3. CFlashWnd derivative class
This class is based on COleContainer
, and uses ShockwaveFlashObjects::IShockwaveFlash
as the template parameter:
class CFlashWnd :
public COleContainerWnd,
public ShockwaveFlashObjects::_IShockwaveFlashEvents
It also implements the _IShockwaveFlashEvents
interface to receive fscommand()
events from a Flash movie.
The creation of the CFlashWnd
object:
g_flashWnd = new CFlashWnd;
g_flashWnd->Create(ShockwaveFlashObjects::CLSID_ShockwaveFlash,
WS_EX_LAYERED, WS_POPUP | WS_VISIBLE | WS_CLIPSIBLINGS,
g_hWnd, g_hInst);
The first parameter is the class ID of the Flash Player object. The second parameter is the extended window style, which should be set to WS_EX_LAYERED
for a transparent Flash control and 0 for non-transparent. The third is the window style, then goes the owner window and the application instance.
The HWND
handle for the OLE container can be retrieved by using the GetHWND()
function.
Part 4. Inside CFlashWnd::Create()
First, the window class is registered. Then, a window with the specified styles is created.
The OleCreate
function is used to create an instance of the IOleObject
object. It passes COleContainer
's IOleClientSite
and IStorage
to the IOleObject
object.
Then, OleSetContainedObject
is called to inform the object of its embedded state.
The IShockwaveFlash
interface is obtained from IOleObject
using QueryInterface
. IViewObjectEx
is obtained in the same way.
If a windowless control is created, then the container needs the IOleInPlaceObjectWindowless
interface to dispatch messages to the object since the object does not have its own window. In other words, the IOleInPlaceObject
interface is required to draw the object.
IOleObject::DoVerb()
is used to show the object and switch it to the running state.
hr = m_lpO->DoVerb(OLEIVERB_SHOW, NULL, (IOleClientSite *)this, 0, NULL, NULL);
Now the Flash Player object is fully created.
Part 5. Transparent window drawing
It is not quite trivial to draw semitransparent translucent windows. The algorithm follows:
- Create a
WS_POPUP
window with the WS_EX_LAYERED
style.
- Create a 32 bit DIB Section using the
CreateDIBSection()
function and select it to any compatible DC. It will be an off-screen plain to render window contents to.
- Render the window contents, preserving the alpha channel.
- Call the
UpdateLayeredWindow()
function to draw the window to the screen.
To render the Flash player contents, I use the OleDraw
helper function, which internally calls the IViewObject::Draw()
method:
hr = OleDraw(lpV, DVASPECT_TRANSPARENT, hdcDraw, &rTotal);
lpV
- the IViewObject
interface of the Flash Player control
hdcDraw
- off-screen plain
rTotal
- client rectangle of the container window
The DVASPECT_TRANSPARENT
drawing aspect tells the object to draw its contents using alpha blending.
While implementing this, I met with a serious bug in Flash Player Control 8. This bug is only in this version. Players 7 and 9 are free of it. The bug is in the way Flash Control fills the alpha channel of a 32 bit device context. If at least 1 of 255 alpha values is applied to a pixel, the colors are mixed correctly, but the resulting alpha channel is set to 255, even if it was initially zero. This makes it impossible to create semitransparent windows. So I had to develop a solution to fix this bug. The solution is quite simple:
These equations are used by the Flash Player control for alpha blending:
R' = Rdst * (1 - alpha) + Rsrc * alpha
G' = Gdst * (1 - alpha) + Gsrc * alpha
B' = Bdst * (1 - alpha) + Bsrc * alpha
If I draw the contents of Flash onto a black surface, I get:
R'black = Rsrc * alpha
G'black = Gsrc * alpha
B'black = Bsrc * alpha
If I draw the contents of Flash onto a white surface, I get:
R'white = 255 * (1 - alpha) + Rsrc * alpha
G'white = 255 * (1 - alpha) + Rsrc * alpha
B'white = 255 * (1 - alpha) + Rsrc * alpha
Here is the system of equations:
R'black = Rsrc * alpha
R'white = 255 * (1 - alpha) + Rsrc * alpha
where alpha
and Rsrc
are unknown. After solving them, you will get:
(255-Alpha) = R'white - R'black
Alpha = 255 - (R'white - R'black)
So, a solution was found. Now, we can draw the contents of a Flash Player twice and then correct the corrupted alpha channel.
Part 6. Events
Flash Control events are handled using IDispatch
. After the Flash control is created, we retrieve a IConnectionPointContainer
and try to find the DIID__IShockwaveFlashEvents
connection point:
hr = m_lpControl->QueryInterface(IID_IConnectionPointContainer, (void**)&m_lpConCont);
if (FAILED(hr))
return FALSE;
hr = m_lpConCont->FindConnectionPoint(
ShockwaveFlashObjects::DIID__IShockwaveFlashEvents, &m_lpConPoint);
if (FAILED(hr))
return FALSE;
hr = m_lpConPoint->Advise((ShockwaveFlashObjects::_IShockwaveFlashEvents*)this,
&m_dwConPointID);
if (FAILED(hr))
return FALSE;
After successful Advise()
, we will receive the events in the IDispatch::Invoke
method.
Part 7. DirectDraw (Updated)
The Flash control can slightly improve drawing performance by using a DirectDraw3 interface. The Flash object queries it through the ShockwaveFlashObjects::IServiceProvider::raw_RemoteQueryService
method. It passes the IID_IDirectDraw3
GUID as guidService
and RIID parameters. The implementation looks like this:
#include <ddraw.h>
#ifndef DEFINE_GUID2
#define DEFINE_GUID2(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
const GUID name \
= { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
#endif
DEFINE_GUID2(CLSID_DirectDraw,0xD7B70EE0,0x4340,0x11CF,
0xB0,0x63,0x00,0x20,0xAF,0xC2,0xCD,0x35);
DEFINE_GUID2(CLSID_DirectDraw7,0x3c305196,0x50db,0x11d3,
0x9c,0xfe,0x00,0xc0,0x4f,0xd9,0x30,0xc5);
DEFINE_GUID2(IID_IDirectDraw,0x6C14DB80,0xA733,0x11CE,0xA5,
0x21,0x00,0x20,0xAF,0x0B,0xE5,0x60);
DEFINE_GUID2(IID_IDirectDraw3,0x618f8ad4,0x8b7a,0x11d0,
0x8f,0xcc,0x0,0xc0,0x4f,0xd9,0x18,0x9d);
DEFINE_GUID2(IID_IDirectDraw4,0x9c59509a,0x39bd,0x11d1,0x8c,
0x4a,0x00,0xc0,0x4f,0xd9,0x30,0xc5);
DEFINE_GUID2(IID_IDirectDraw7,0x15e65ec0,0x3b9c,0x11d2,0xb9,
0x2f,0x00,0x60,0x97,0x97,0xea,0x5b);
_COM_SMARTPTR_TYPEDEF(IDirectDraw4, IID_IDirectDraw4);
HRESULT __stdcall CFlashWnd::raw_RemoteQueryService (
GUID * guidService,
GUID * riid,
IUnknown * * ppvObject )
{
HRESULT hr;
if (IsEqualGUID(*guidService, IID_IDirectDraw3))
{
if (!m_lpDD4)
{
m_lpDD4 = new IDirectDraw4Ptr;
hr = m_lpDD4->CreateInstance(CLSID_DirectDraw, NULL,
CLSCTX_INPROC_SERVER);
if (FAILED(hr))
{
delete m_lpDD4;
m_lpDD4 = NULL;
return E_NOINTERFACE;
}
}
if (m_lpDD4 && m_lpDD4->GetInterfacePtr())
{
*ppvObject = m_lpDD4->GetInterfacePtr();
m_lpDD4->AddRef();
return S_OK;
}
}
return E_NOINTERFACE;
}