views:

691

answers:

3

How do I copy a buffer that would save to a ".BMP" file to the clipboard using the win32 API? I.e., I have a raw buffer of a Windows V3 Bitmap (including the header) that I can literally write() to a file and will result in a valid .BMP file, but I want to copy it to the clipboard instead.

On OS X, in plain C, the code would look something like this (which works as intended):

#include <ApplicationServices/ApplicationServices.h>
int copyBitmapToClipboard(char *bitmapBuffer, size_t buflen)
{
 PasteboardRef clipboard;
 CFDataRef data;

 if (PasteboardCreate(kPasteboardClipboard, &clipboard) != noErr) {
  return PASTE_OPEN_ERROR;
 }

 if (PasteboardClear(clipboard) != noErr) return PASTE_CLEAR_ERROR;

 data = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, bitmapBuffer, buflen,
                                    kCFAllocatorNull);
 if (data == NULL) {
  CFRelease(clipboard);
  return PASTE_DATA_ERROR;
 }

 if (PasteboardPutItemFlavor(clipboard, 42, kUTTypeBMP, data, 0) != noErr) {
  CFRelease(data);
  CFRelease(clipboard);
  return PASTE_PASTE_ERROR;
 }

 CFRelease(data);
 CFRelease(clipboard);
 return PASTE_WE_DID_IT_YAY;
}

I am unsure how to accomplish this with the win32 API. This is as far as I've gotten, but it seems to silently fail (that is, the function returns with a successful error code, but when attempting to paste, the menu item is disabled).

#include <windows/windows.h>
int copyBitmapToClipboard(char *bitmapBuffer, size_t buflen)
{
 if (!OpenClipboard(NULL)) return PASTE_OPEN_ERROR;
 if (!EmptyClipboard()) return PASTE_CLEAR_ERROR;

 if (SetClipboardData(CF_DSPBITMAP, bitmapBuffer) == NULL) {
  CloseClipboard();
  return PASTE_PASTE_ERROR;
 }

 CloseClipboard();
 return PASTE_WE_DID_IT_YAY;
}

Could anyone provide some insight as to how to fix this?

Edit

Per Aaron and martinr's suggestions, I've now modified the code to the following:

#include <windows/windows.h>
int copyBitmapToClipboard(char *bitmapBuffer, size_t buflen)
{
 HGLOBAL hResult;
 if (!OpenClipboard(NULL)) return PASTE_OPEN_ERROR;
 if (!EmptyClipboard()) return PASTE_CLEAR_ERROR;

 hResult = GlobalAlloc(GMEM_MOVEABLE, buflen);
 if (hResult == NULL) return PASTE_DATA_ERROR;

 memcpy(GlobalLock(hResult), bitmapBuffer, buflen);
 GlobalUnlock(hResult);

 if (SetClipboardData(CF_DSPBITMAP, hResult) == NULL) {
  CloseClipboard();
  return PASTE_PASTE_ERROR;
 }

 CloseClipboard();
 return PASTE_WE_DID_IT_YAY;
}

But it still has the same result. What am I doing wrong?

Final Edit

The working code:

#include <windows/windows.h>
int copyBitmapToClipboard(char *bitmapBuffer, size_t buflen)
{
 HGLOBAL hResult;
 if (!OpenClipboard(NULL)) return PASTE_OPEN_ERROR;
 if (!EmptyClipboard()) return PASTE_CLEAR_ERROR;

 buflen -= sizeof(BITMAPFILEHEADER);
 hResult = GlobalAlloc(GMEM_MOVEABLE, buflen);
 if (hResult == NULL) return PASTE_DATA_ERROR;

 memcpy(GlobalLock(hResult), bitmapBuffer + sizeof(BITMAPFILEHEADER), buflen);
 GlobalUnlock(hResult);

 if (SetClipboardData(CF_DIB, hResult) == NULL) {
  CloseClipboard();
  return PASTE_PASTE_ERROR;
 }

 CloseClipboard();
 GlobalFree(hResult);
 return PASTE_WE_DID_IT_YAY;
}

Thanks, martinr!

+2  A: 

I think the hMem needs to be a return value from LocalAlloc, an HMEMORY rather than a pointer.

EDIT

Sorry yes, GlobalAlloc with GMEM_MOVEABLE is required, not LocalAlloc.

EDIT

I suggest you use CF_DIB clipboard data format type.

DIB is the same as BMP except it is without the BITMAPFILEHEADER, so copy the source bytes except for the first sizeof(BITMAPFILEHEADER) bytes.

EDIT

From OpenClipboard() documentation (http://msdn.microsoft.com/en-us/library/ms649048(VS.85).aspx):

"If an application calls OpenClipboard with hwnd set to NULL, EmptyClipboard sets the clipboard owner to NULL; this causes SetClipboardData to fail."

You need to set up a window; even if you're not doing WM_RENDERFORMAT type stuff.

I found this a lot with Windows APIs. I haven't used the Clipboard APIs per se but with other APIs I usually found that creating a hidden window and passing that handle to the relevant API was enough to keep it quiet. There's usually some notes on issues to do with this if you're creating a window from a DLL rather than an EXE; read whatever is the latest Microsoft word about DLLs, message loops and window creation.

As regardsBITMAPINFO, that's not the start of the stream the clipboard wants to see :- the buffer you give to SetClipboardData should start right after where the BITMAPFILEHEADER stops.

martinr
When attempting to paste data in Paint copied with CF_DIB, I receive the following two errors (one after the other): 1.) "Error getting Clipboard Data!" and 2.) "There is not enough memory or resources to complete operation. Close some programs, and then try again."Any idea why this is?
Sam
Also, the documentation states that a CF_DIB is "a memory object containing a BITMAPINFO structure followed by the bitmap bits", which seems to contradict your description.
Sam
Oh sorry, I see I misread BITMAPFILEHEADER as BITMAPINFO. You're right.
Sam
+3  A: 

You need to pass a HANDLE to SetClipboard() (that is - memory allocated with GlobalAlloc()) rather than passing a straight pointer to your bitmap.

Aaron
I've updated the example with this in mind but it still doesn't appear to work. Would you mind looking again?
Sam
A: 

Echo Aaron and martinr. Example here, crucial section:

    // Allocate a global memory object for the text. 

    hglbCopy = GlobalAlloc(GMEM_MOVEABLE, 
        (cch + 1) * sizeof(TCHAR)); 
    if (hglbCopy == NULL) 
    { 
        CloseClipboard(); 
        return FALSE; 
    } 

    // Lock the handle and copy the text to the buffer. 

    lptstrCopy = GlobalLock(hglbCopy); 
    memcpy(lptstrCopy, &pbox->atchLabel[ich1], 
        cch * sizeof(TCHAR)); 
    lptstrCopy[cch] = (TCHAR) 0;    // null character 
    GlobalUnlock(hglbCopy); 

    // Place the handle on the clipboard. 

    SetClipboardData(CF_TEXT, hglbCopy); 
J.J.
That code implies that you do not need to GlobalFree() buffers passed to SetClipboardData(), is that true?
Sam
I'm not 100% on the clipboard architecture, but I think yes: you do not call GlobalFree. I don't think the clipboard makes a copy of the data, but uses internally the buffer you GlobalAlloc'ed.
J.J.
I tried using GlobalFree() in my code and it appears to work as intended. This actually makes a big difference in my function — leaking a ".BMP" file is a BIG memory leak.
Sam
Ha! Well, half the Internet is wrong. "The clipboard is now responsible to GlobalFree the data, not ourselves." -- http://netez.com/2xExplorer/shellFAQ/adv_clip.html thanks for the follow up.
J.J.