Last time around, I discussed a slightly
kludgy approach for automatic testing of clipboard code, which
was based on
clipbrd.exe
and some VBscript code.
That wasn't bad, but I wasn't really satisfied with this. After all,
the original goal was to write rock-solid unit tests for clipboard code.
I wanted a more reliable tool to copy data from and to the clipboard in arbitrary
formats. I needed more control. And, most important of all, I was in the mood for
reinventing wheels (really fancy ones, of course).
The key Win32 APIs for clipboard handling are
SetClipboardData and
GetClipboardData. Their signatures are as follows:
HANDLE SetClipboardData(UINT uFormat, HANDLE hMem);
HANDLE GetClipboardData(UINT uFormat);
So when you post to the clipboard, all you need to specify is a format and a memory
handle, as it seems! This looks so trivial that I had my strategy
laid out almost immediately: I would allocate a global memory block using
GlobalAlloc.
Then I would read some clipboard data from a file into that block, and finally call
SetClipboardData
- like in the following code:
HANDLE ReadFileToMemory(const TCHAR *filename)
{
FILE *f;
errno_t error = _tfopen_s(&f, filename, _T("rb"));
if (error) {
_ftprintf(stderr, _T("ERROR: Cannot open %s\n"), filename);
return 0;
}
// get size of file
fseek(f, 0, SEEK_END);
long size=ftell(f);
fseek(f, 0, SEEK_SET);
// allocate memory
HANDLE hMem = ::GlobalAlloc(GMEM_MOVEABLE, size);
if (!hMem) {
fclose(f);
return 0;
}
LPVOID mem = ::GlobalLock(hMem);
if (!mem) {
fclose(f);
::GlobalFree(hMem);
return 0;
}
// read the file into memory
size_t bytes_read = fread(mem, 1, size, f);
fclose(f);
::GlobalUnlock(mem);
if (bytes_read != size) {
::GlobalFree(hMem);
return 0;
}
return hMem;
}
bool FileToClipboard(TCHAR *filename, UINT clipid, HWND ownerWindow)
{
if (::OpenClipboard(ownerWindow)) {
HANDLE hClip = ReadFileToMemory(filename);
::EmptyClipboard();
HANDLE h = ::SetClipboardData(clipid, hClip);
::CloseClipboard();
::DestroyWindow(owner);
return true;
}
return false;
}
And the reverse code to get data from the clipboard and save it to
a file would be just as simple:
FILE *f = fopen(filename, "wb");
if (f) {
HANDLE hClip = GetClipboardData(clipID);
// clipID culled from looping over
// available formats using EnumClipboardFormats
void *pData = (void*)GlobalLock(hClip);
if (pData) {
SIZE_T sz = ::GlobalSize(pData);
if (sz) {
size_t written = fwrite(pData, 1, sz, f);
ret = (written == sz);
}
}
::GlobalUnlock(hClip);
fclose(f);
}
Piece of cake! Mission accomplished! I slapped the usual boilerplate code for a
console app onto the above, and ran my first successful tests: I could read
text from the clipboard, and post text to it just fine.
However, several formats stubbornly refused to cooperate. In particular, the really
useful stuff, like metafiles. Or bitmaps. What was going on?
When calling
GetClipboardData
for these formats, the code that
tried to interpret the returned handle as a global memory handle flatly
fell on its face. It turns out that I had jumped to conclusions way
too early when I read the first few lines of the
SetClipboardData
documentation - some of those clipboard handles are actually anything
but
memory handles! Examples for such formats:
-
CF_ENHMETAFILE
, CF_DSPENHMETAFILE
-
CF_METAFILEPICT
, CF_DSPMETAFILEPICT
-
CF_BITMAP
, CF_DSPBITMAP
-
CF_PALETTE
And then, of course, there are whole classes of clipboard formats
which I had not even explored yet, such as application-defined formats.
Next time: How I learnt to peacefully coexist with all the various
classes of clipboard formats.