Freeloading from the Data Cache

The data cache is a rich service that can do quite a lot for whatever happens to be using it. With the cache, you get a service that knows how to draw, save, and load a variety of presentation formats as well as how to connect itself to a remote object and automatically update its data when that object's data changes. The data cache obviously has a number of potential uses.

The primary use is that of implementing an object in an in-process handler, especially in OLE Documents. I mentioned earlier how a handler object must synchronize data changes with its local server to keep its implementation of IViewObject2::Draw up to date. If that handler object doesn't have reason to implement Draw itself, it can rely entirely on the cache for its implementation. The cache can then manage its own internal state by connecting to a remote object, asking for presentations through IDataObject::GetData, and watching for changes in those presentations through IAdviseSink::OnDataChange. The handler object doesn't even need its own advise sink! Because the data cache has such great utility in this manner, a handler object can aggregate on the cache for any interfaces it finds useful to expose. The handler object customizes only those interfaces that it really wants to control.

The second use of the data cache is as a cache for static graphics. In this case, it is never connected to a remote object. Instead, you can store and retrieve metafiles and bitmaps and take advantage of the cache's drawing and persistence capabilities. In Chapter 12, when we look at the OLE Clipboard, we'll add tenants to the Patron sample (last seen in Chapter 7) that can contain static metafiles or bitmaps. (This lends itself well to enabling tenants to contain compound document content objects, which we'll see in Chapter 17.) Many applications, especially word processors and business graphics applications, do something similar because their users like to integrate graphics into documents and slide shows. When I thought about adding these features to Patron, I felt a tad queasy: I would first have to write code to draw metafiles and bitmaps (not too bad) and then more code to save that data in a disk file and load it back into memory again. I don't know about you, but serializing graphics to a file and then figuring out how to load them again involve a degree of tedium that I avoid like the plague.

This is exactly what the data cache is good at doing already. The trick is to figure out how to freeload from OLE in the right way. This isn't all that hard because OLE supports two CLSIDs, named CLSID_Picture_Metafile (00000315-0000-0000-C000-000000000046) and CLSID_Picture_Dib (00000316-0000-0000-C000-000000000046), that are specific instances of the data cache that manage a single presentation format. OLE supports these CLSIDs for the purposes of static compound document objects, which are presentations without a known source.

To test how much I could take advantage of OLE's inherent services, I came up with the sample named Freeloader (CHAP11\FREELOAD), shown in Figure 11-3. This handy application can copy or paste any metafile or bitmap from the clipboard and load or save it to a compound file with the FRE extension. Freeloader is built on the sample code CLASSLIB, as are Patron and Cosmo, in which each document can contain one graphic. Freeloader demonstrates the use of the data cache and the client side of IViewObject2. We'll see an implementation of IViewObject2 in Chapter 19, where we implement a handler for use in conjunction with an OLE Documents server.

Figure 11-3.

The Freeloader program, with three open presentations.

In Freeloader, all the relevant code for working with the cache and its interfaces is found in DOCUMENT.CPP. In particular, this file includes the functions dealing with the clipboard: Clip, RenderFormat, and Paste members in the class CFreeloaderDoc. The Load and Save members call the cache's IPersistStorage members according to the contract described for this interface in Chapter 7, so there's little need to look at the code. More to the point, a Freeloader document saves itself by creating a compound file and asking the cache to save its data in that storage through IPersistStorage::Save. Loading is a matter of opening that file, creating a cache, and telling it to load through IPersistStorage::Load.

The CFreeloaderDoc::FMessageHook function shows how to call IViewObject2::Draw with a continuation function (for which OleDraw doesn't suffice):


BOOL CFreeloaderDoc::FMessageHook(HWND hWnd, UINT iMsg
, WPARAM wParam, LPARAM lParam, LRESULT *pLRes)
{
PAINTSTRUCT ps;
HDC hDC;
RECT rc;
RECTL rcl;
LPVIEWOBJECT2 pIViewObject2;
HRESULT hr;

if (WM_PAINT!=iMsg)
return FALSE;

hDC=BeginPaint(hWnd, &ps);
GetClientRect(hWnd, &rc);

if (NULL!=m_pIUnknown)
{
hr=m_pIUnknown->QueryInterface(IID_IViewObject2
, (PPVOID)&pIViewObject2);

if (SUCCEEDED(hr))
{
//Put "Hit Esc to stop" in status line.
m_pFR->StatusLine()->MessageSet(PSZ(IDS_HITESCTOSTOP));

RECTLFROMRECT(rcl, rc);
pIViewObject2->Draw(DVASPECT_CONTENT, -1, NULL, NULL
, 0, hDC, &rcl, NULL, ContinuePaint, 0);
pIViewObject2->Release();

m_pFR->StatusLine()->MessageDisplay(ID_MESSAGEREADY);
}
}

EndPaint(hWnd, &ps);
return FALSE;
}

BOOL CALLBACK ContinuePaint(DWORD dwContinue)
{
return !(GetAsyncKeyState(VK_ESCAPE) < 0);
}

In all truth, the Clip and RenderFormat functions do very little. Clip opens the clipboard, asks RenderFormat for a presentation, and sticks that presentation on the clipboard. RenderFormat does its thing by calling the cache's IDataObject::GetData:


HGLOBAL CFreeloaderDoc::RenderFormat(UINT cf)
{
LPDATAOBJECT pIDataObject;
FORMATETC fe;
STGMEDIUM stm;

if (NULL==m_pIUnknown)
return NULL;

[Set up fe and stm appropriately for format.]

m_pIUnknown->QueryInterface(IID_IDataObject
, (PPVOID)&pIDataObject);
pIDataObject->GetData(&fe, &stm);
pIDataObject->Release();

return stm.hGlobal;
}

But how on earth did we originally obtain the pointer to the object? The answer lies in CFreeloaderDoc::Paste:


BOOL CFreeloaderDoc::Paste(HWND hWndFrame)
{
UINT cf=0;
BOOL fRet=FALSE;
HRESULT hr;
DWORD dwConn;
LPUNKNOWN pIUnknown;
LPOLECACHE pIOleCache;
LPPERSISTSTORAGE pIPersistStorage;
FORMATETC fe;
STGMEDIUM stm;
CLSID clsID;

if (!OpenClipboard(hWndFrame))
return FALSE;

/*
* Try to get data in order of metafile, dib, bitmap. We set
* stm.tymed up front so that if we actually get something a
* call to ReleaseStgMedium will clean it up for us.
*/

stm.pUnkForRelease=NULL;
stm.tymed=TYMED_MFPICT;
stm.hGlobal=GetClipboardData(CF_METAFILEPICT);

if (NULL!=stm.hGlobal)
cf=CF_METAFILEPICT;

if (0==cf)
{
stm.tymed=TYMED_HGLOBAL;
stm.hGlobal=GetClipboardData(CF_DIB);

if (NULL!=stm.hGlobal)
cf=CF_DIB;
}

if (0==cf)
{
stm.tymed=TYMED_GDI;
stm.hGlobal=GetClipboardData(CF_BITMAP);

if (NULL!=stm.hGlobal)
cf=CF_BITMAP;
}

CloseClipboard();

//Didn't get anything? Then we're finished.
if (0==cf)
return FALSE;

//This now describes data we have.
SETDefFormatEtc(fe, cf, stm.tymed);

hr=CreateDataCache(NULL, CLSID_NULL, IID_IUnknown
, (PPVOID)&pIUnknown);

if (FAILED(hr))
{
ReleaseStgMedium(&stm);
return FALSE;
}

pIUnknown->QueryInterface(IID_IPersistStorage
, (PPVOID)&pIPersistStorage);
pIPersistStorage->InitNew(m_pIStorage);
pIPersistStorage->Release();

pIUnknown->QueryInterface(IID_IOleCache, (PPVOID)&pIOleCache);
pIOleCache->Cache(&fe, 0, &dwConn);

hr=pIOleCache->SetData(&fe, &stm, TRUE);
pIOleCache->Release();

if (FAILED(hr))
{
ReleaseStgMedium(&stm);
pIUnknown->Release();
return FALSE;
}

//Now that that's all done, replace our current with the new.
ReleaseObject();
m_pIUnknown=pIUnknown;
m_dwConn=dwConn;

FDirtySet(TRUE);

InvalidateRect(m_hWnd, NULL, TRUE);
UpdateWindow(m_hWnd);
return TRUE;
}

The first half of Paste looks like a reasonably normal piece of Windows code that gets a graphics image from the clipboard. The slightly odd thing about it is that I'm storing the data handle directly into a STGMEDIUM (because it's there). I also initialize a FORMATETC with the description of the data I actually pasted. I call CreateDataCache to create a cache object and tell it to initialize itself through IPersistStorage::InitNew. I then tell the cache object to cache the format I found on the clipboard through IOleCache::Cache, stuff that data into it with IOleCache::SetData, and then clean everything up.

Freeloader also uses IViewObject2::GetExtent to size the window to match the graphic when reopening a file. This occurs in CFreeloaderDoc::SizeToGraphic, (also called from Freeloader's Edit/Size To Graphic menu item).

That's really all there is to it, and it seems much simpler than having to write so much more code. (Code I don't need to write to make Patron functional in Chapter 12.) All compliments of OLE.