But All I Want to Do Is Copy Some Simple Data!

I imagine that at this point you are screaming or cursing that OLE makes the clipboard far too complex. To copy even the simplest piece of data—maybe a short, but passionate, string such as "Why is Microsoft doing this to me?"—requires an implementation of a data object, a FORMATETC enumerator that supports cloning, and the complexity of taking a snapshot of the data involved so as to support delayed rendering. Ouch! Whatever happened to OpenClipboard, SetClipboardData, and CloseClipboard?

Well, in reality, the Windows API has never been quite that simple because somewhere along the way you have to allocate global memory and copy your data into it. Before calling SetClipboardData with a string, for example, you usually call some function to make a copy of it:


HGLOBAL CopyStringToHGlobal(LPTSTR psz)
{
HGLOBAL hMem;
LPTSTR pszDst;

hMem=GlobalAlloc(GHND, (DWORD)(lstrlen(psz)+1));

if (NULL!=hMem)
{
pszDst=GlobalLock(hMem);
lstrcpy(pszDst, psz);
GlobalUnlock(hMem);
}

return hMem;
}

With this function, the code to copy the text to the clipboard would appear as follows:


HGLOBAL    hMem;

hMem=CopyStringToHGlobal(TEXT("Why is Microsoft doing this to me?"));

if (NULL!=hMem)
{
if (OpenClipboard(hWndMain))
{
SetClipboardData(CF_TEXT, hMem);
CloseClipboard();
}
else
GlobalFree(hMem); //We must clean up.
}

Under OLE, however, we would like to write simple code that takes advantage of data objects:


IDataObject    *pIDataObject;
HRESULT hr;
FORMATETC fe;
STGMEDIUM stm;

stm.tymed=TYMED_HGLOBAL;
stm.hGlobal=
CopyStringToHGlobal(TEXT("Why is Microsoft doing this to me?"));

if (NULL!=stm.hGlobal)
{
hr=FunctionToCreateADataObject(&pIDataObject)

if (SUCCEEDED(hr))
{
SETDefFormatEtc(fe, CF_TEXT, TYMED_HGLOBAL);
pIDataObject->SetData(&fe, &stm);
OleSetClipboard(pIDataObject);
pIDataObject->Release();
}
else
GlobalFree(stm.hGlobal);
}

This code shows how we would like to translate existing Windows API code into OLE-based code. SetClipboardData(cf, hMem) becomes IDataObject::SetData(&fe, &stm), followed by OleSetClipboard. CloseClipboard turns into IDataObject::Release. But what about FunctionToCreateADataObject? Where does this come from? Certainly something like this would be quite convenient, but alas, my friends, OLE does not supply such an implementation. You could cheat and use CreateDataCache for this purpose, except for the small problem that the data cache does not implement IDataObject::EnumFormatEtc. Bummer.

What we need is a function to create a data object that we can stuff through IDataObject::SetData, and then we need to throw that data object on the clipboard. The data object would maintain all the data until the object was released, and then it would free all that data automatically. So I wrote such a component, named DataTran, found in CHAP12\DATATRAN.