A Data Transfer Component

An object that would simplify clipboard transfers is essentially a data cache with IDataObject slapped on it, one that you could grab with a quick call to CoCreateInstance. DataTran is a server for such an object; its CLSID_DataTransferObject is defined in INC\BOOKGUID.H. Be sure to create registry entries for DataTran using the REG file in the sample's directory before you attempt to run Cosmo or Patron from this or any subsequent chapter. Both Cosmo and Patron call CoCreateInstance with this CLSID in place of FunctionToCreateADataObject in the code listed earlier.

DataTran is simply an archetypal in-process component server for a single object, implemented in a C++ class named CDataObject in DATAOBJ.H, DATAOBJ.CPP, and IDATAOBJ.CPP. The FORMATETC enumerator is in IENUMFE.CPP.

During initialization, the data object creates a hidden list box control. In the list, it stores whatever renderings you care to stuff in it using a custom data structure named RENDERING (DATAOBJ.H). This structure manages a FORMATETC, a STGMEDIUM, and an IUnknown pointer to the data's real owner:


typedef struct tagRENDERING
{
FORMATETC fe; //Format
STGMEDIUM stm; //Actual data
LPUNKNOWN pUnkOrg; //Real owner
} RENDERING, *PRENDERING;

DataTran allocates and stores a RENDERING structure in each call to its IDataObject::SetData (IDATAOBJ.CPP); the structure holds a copy of the FORMATETC and STGMEDIUM structures passed to SetData, with one exception. DataTran replaces the STGMEDIUM::pUnkForRelease field with its own IUnknown so that DataTran can share that rendering with multiple consumers. It uses its own reference count to keep the data valid. However, we must still preserve the original pUnkForRelease, which ends up in the RENDERING::pUnkOrg field. DataTran restores this pointer before calling ReleaseStgMedium to free the data. Through all of this, DataTran's SetData member acts a lot like the Windows SetClipboardData, with the added feature that calling SetData with NULL pointers cleans out the entire data object:


STDMETHODIMP CImpIDataObject::SetData(LPFORMATETC pFE
, LPSTGMEDIUM pSTM, BOOL fRelease)
{
PRENDERING prn;

//We have to remain responsible for data.
if (!fRelease)
return ResultFromScode(E_FAIL);

if (NULL==pFE œœ NULL==pSTM)
{
m_pObj->Purge();
return NOERROR;
}

prn=new RENDERING;

if (NULL==prn)
return ResultFromScode(E_OUTOFMEMORY);

prn->fe=*pFE;
prn->stm=*pSTM;
prn->pUnkOrg=pSTM->pUnkForRelease;
prn->stm.pUnkForRelease=this;

SendMessage(m_pObj->m_hList, LB_ADDSTRING, 0, (LONG)prn);
return NOERROR;
}

With a list of RENDERING structures, the implementations of QueryGetData and EnumFormatEtc need only to look in the list for their formats. EnumFormatEtc creates an enumerator object that copies the FORMATETC structures from each RENDERING in the list into its own array for enumeration.

That leaves GetData and Release. GetData walks through the list looking for a match on the requested FORMATETC. (GetDataHere is the same as GetData except that it is restricted to IStorage mediums.) If the format exists, GetData copies the STGMEDIUM structure for that format into the caller's structure and then calls AddRef on itself:


STDMETHODIMP CImpIDataObject::GetData(LPFORMATETC pFE
, LPSTGMEDIUM pSTM)
{
UINT i, cItems;
PRENDERING pRen;
DWORD cb;
HWND hList;

if (NULL==m_pObj->m_hList œœ NULL==pFE œœ NULL==pSTM)
return ResultFromScode(DATA_E_FORMATETC);

hList=m_pObj->m_hList;
cItems=(UINT)SendMessage(hList, LB_GETCOUNT, 0, 0L);

for (i=0; i < cItems; i++)
{
cb=SendMessage(hList, LB_GETTEXT, i, (LPARAM)&pRen);

if (LB_ERR!=cb)
{
if (pFE->cfFormat==pRen->fe.cfFormat
&& (pFE->tymed & pRen->fe.tymed)
&& pFE->dwAspect==pRen->fe.dwAspect)
{
*pSTM=pRen->stm;
AddRef();
return NOERROR;
}
}
}

return ResultFromScode(DATA_E_FORMATETC);
}

The AddRef call accounts for the Release call that will come from inside the consumer's later call to ReleaseStgMedium.1 The consumer doesn't know that we're not giving it an independent copy, but because of the pUnkForRelease field in FORMATETC, we can control the data with a simple reference count. Only when we reset or free the entire data object will we actually free the data. This happens in CDataObject::Purge:


void CDataObject::Purge(void)
{
UINT i, cItems;
PRENDERING pRen;
DWORD cb;

if (NULL==m_hList)
return;

cItems=(UINT)SendMessage(m_hList, LB_GETCOUNT, 0, 0L);

for (i=0; i < cItems; i++)
{
cb=SendMessage(m_hList, LB_GETTEXT, i, (LPARAM)&pRen);

(continued)


        if (LB_ERR!=cb)
{
pRen->stm.pUnkForRelease=pRen->pUnkOrg;
ReleaseStgMedium(&pRen->stm);
delete pRen;
}
}

SendMessage(m_hList, LB_RESETCONTENT, 0, 0L);
return;
}

In this way, DataTran shows a good use for pUnkForRelease: to control the ownership of the data, taking over from the real source until the time when the data must really be freed.

DataTran handles probably the most common case in which you need a data object to work with the OLE Clipboard. But in the case of Component Cosmo, having a data object isn't necessary.

1 Because ReleaseStgMedium will call DataTran's Release to free the data we do not need to make an extra AddRef call to IStorage or IStream pointers in the medium. The consumer will not call these objects' Release members. Only our call to ReleaseStgMedium in CDataObject::Purge can do it.