Transparent Flash Control in Plain C++

This article illustrates the implementation of a simple OLE container that is used to host 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
  • IStorage

My implementation of the OLE container is called COleContainerWnd. It is declared as in the following:

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 by using the #import command:

#import "c:\\windows\\system32\\macromed\\flash\\flash.ocx" named_guids

The named_guids directive is used to generate a 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. They contain your 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<ShockwaveFlashObjects::IShockwaveFlash>,
   public ShockwaveFlashObjects::_IShockwaveFlashEvents

It also implements the _IShockwaveFlashEvents interface to receive fscommand() events from the Flash movie.

The creation of 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; it should be set to WS_EX_LAYERED for transparent Flash control and 0 for non-transparent. The third is the window style, followed by the owner window and 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, the window with your specified styles is created.

The OleCreate function creates an instance of an IOleObject object. It passes COleContainer's IOleClientSite and IStorage to the IOleObject object. Then, the OleSetContainedObject is called to inform the object of its embedded state. TheIShockwaveFlash interface is obtained from IOleObject by using QueryInterface. IViewObjectEx is obtained in the same way.

If a windowless control is created, the container needs the IOleInPlaceObjectWindowless interface to dispatch messages to the object because the object does not have its own window. In another case, the IOleInPlaceObject interface is required to draw the object.

IOleObject::DoVerb() is used to show the object and switch it to its 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 is the following:

  1. Create a WS_POPUP window with WS_EX_LAYERED style.
  2. Create a 32-bit DIB Section using the CreateDIBSection() function and select it to any compatible DC. It will be an offscreen plain to render window contents to.
  3. Render window contents, preserving the alpha channel.
  4. Call the UpdateLayeredWindow() function to draw the window to the screen.

To render Flash player contents, I use the OleDraw helper function. It internally calls the IViewObject::Draw() method:

hr = OleDraw(lpV, DVASPECT_TRANSPARENT, hdcDraw, &rTotal);
  • lpV: IViewObject interface of flash player control
  • hdcDraw: Offscreen plain
  • rTotal: Client rectangle of the container window

The DVASPECT_TRANSPARENT drawing aspect tells the object to draw its content using alpha blending.

When implementing this, I met 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 the following:

  • 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 this:

  • 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 it, you will get:

  • (255-Alpha) = R'white - R'black
  • Alpha = 255 - (R'white - R'black)

So, the solution is found. Now, you can draw the contents of the Flash player twice and then correct the spoiled alpha channel.

Part 6. Events

Flash Control Events are handled by using IDispatch. After the Flash control is created, you retrieve a IConnectionPointContainer and try to find 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 a successful Advise(), you will receive events in the IDispatch::Invoke method.



About the Author

Igor Makarov

Senior Visual C++ developer.

Downloads