4

My understanding is that in general when you add a handle to the clipboard, the clipboard then owns the handle and you are not responsible for deleting it, and you should not delete it. This is what it says here: https://msdn.microsoft.com/en-us/library/windows/desktop/ms649051%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396

If SetClipboardData succeeds, the system owns the object identified by the hMem parameter. The application may not write to or free the data once ownership has been transferred to the system, but it can lock and read from the data until the CloseClipboard function is called. (The memory must be unlocked before the Clipboard is closed.) If the hMem parameter identifies a memory object, the object must have been allocated using the function with the GMEM_MOVEABLE flag.

And indeed I see lots of examples where people call GlobalAlloc(), put some text in the global handle, call SetClipboardData(), and then don't free the global handle, because the clipboard owns it.

But in the case of HBITMAP data, added via SetClipboardData(CF_BITMAP, hBitmap), I see lots of examples like these:

https://stackoverflow.com/a/7292773/384670

https://stackoverflow.com/a/28248531/384670

In these cases, the code does delete the HBITMAP after adding it to the clipboard.

Is there a difference between an HBITMAP handle and a GlobalAlloc() handle with respect to the clipboard? Is there something special about the CF_BITMAP that's an exception to the rule, and the clipboard copies the handle instead of owns it? Can you point me to official (MSDN) documentation that explains the difference?

Edit:

Here is one more example that involves both types of handle in one article: http://www.codeproject.com/Articles/42/All-you-ever-wanted-to-know-about-the-Clipboard

Notice that in the case of the bitmap the author specifically says:

//copy has been made on clipboard so we can delete

Community
  • 1
  • 1
M Katz
  • 4,604
  • 3
  • 36
  • 59
  • 1
    I would say those examples are wrong. The docs clearly say the clipboard owns the object after a successful call to `SetClipboardData`. – Jonathan Potter Aug 19 '15 at 04:55
  • Yeah, I agree that's what the docs say. But I've added yet another example where both types of handle are used in one article. It certainly *seems* like there's something special about bitmap data. – M Katz Aug 19 '15 at 05:14
  • 1
    I think the only thing special is that the OS has protection against a GDI handle being deleted by other than its owner. – Jonathan Potter Aug 19 '15 at 05:22
  • It's easy to test - put a `HBITMAP` on the clipboard, clear the clipboard, close the clipboard, and then try to use the bitmap handle. – Jonathan Potter Aug 19 '15 at 05:40
  • Do you have an MSDN reference saying that GDI handles are protected from incorrect deletion? That at least would mean it's "safe" to have the code in there to delete it -- if you're supposed to delete it, no leak; if you're not supposed to delete it, no problem. – M Katz Aug 19 '15 at 07:45
  • 1
    It's just a guess, although the `DeleteObject` docs do say *"If the specified handle is not valid... the return value is zero"* which implies at least some sanity checking. – Jonathan Potter Aug 19 '15 at 08:01
  • Well, I've been running code with the delete in there, after the SetClipboardData() call, so you'd think if it was blindly deleting it and it was owned by the clipboard I'd see a crash, which I haven't seen. – M Katz Aug 19 '15 at 08:12
  • 1
    That's my point, the system protects against GDI objects being deleted incorrectly. I've given you a suggestion for how you can test this conclusively, and I've seen nothing anywhere that suggests the `SetClipboardData` docs are wrong. – Jonathan Potter Aug 19 '15 at 08:14
  • Right, my previous comment was just adding evidence to your point. As for the test, it doesn't seem conclusive that just because the clipboard owns the handle I will necessarily have trouble using it. But if I *do* have trouble using it, you are right that it indicates it's not mine anymore. I will try it. – M Katz Aug 19 '15 at 08:20
  • 1
    The test works because if the clipboard owns the handle, and you then **clear the clipboard**, the handle would be destroyed by the clipboard. If the bitmap still exists after that then the clipboard didn't own the handle after all. – Jonathan Potter Aug 19 '15 at 09:22
  • I have added the experiment above. – M Katz Aug 19 '15 at 23:06
  • 1
    Is `hbmScreen` selected into a DC before you place it in the clipboard? That's the only other factor I can think of that might effect it. Otherwise, looks like you have answered your own question! – Jonathan Potter Aug 19 '15 at 23:47
  • Yes, hbmScreen is selected in hdcMemDC the whole time, including when it's added to the clipboard. I guess you are suggesting that maybe the clipboard would take control of the handle if it wasn't selected into a DC at the time it's added? In any case, I will leave that experiment to someone else. Thanks for your help. – M Katz Aug 19 '15 at 23:57

1 Answers1

0

Here are the results of the experiment encouraged by Jonathan Potter. The handle in question is in the variable HBITMAP hbmScreen. The result of this experiment is that I can access hbmScreen in all of the checkpoints (1) through (4), and can save the correct image at the end. So this tells me that the handle is still mine to use. As I say, I don't think this definitively shows that the handle is not also owned by the clipboard. But since calling DeleteObject( hbmScreen ) after all of this also appears to work without problem, I'm going to continue doing that.

if ( OpenClipboard( NULL ) && EmptyClipboard() )
{
    BITMAPINFOHEADER bi = { 0 };
    bi.biSize = sizeof( BITMAPINFOHEADER );
    bi.biPlanes = 1;
    bi.biBitCount = 32;
    bi.biWidth = rcClient.right - rcClient.left;
    bi.biHeight = rcClient.bottom - rcClient.top;
    bi.biCompression = BI_RGB;
    bi.biSizeImage = 0; // 3 * ScreenX * ScreenY;

    BYTE *lpbitmap = (BYTE *)malloc( bi.biWidth * bi.biHeight * 4 );

    // (1) call to make sure we can access hbmScreen here
    memset( lpbitmap, 0, bi.biWidth * bi.biHeight * 4 );
    GetDIBits( hdcScreen, hbmScreen, 0, (UINT)bi.biHeight, lpbitmap, (BITMAPINFO *)&bi, DIB_RGB_COLORS );

    HANDLE hResult = SetClipboardData( CF_BITMAP, hbmScreen );
    CloseClipboard();

    // (2) call to check if we can access hbmScreen here
    memset( lpbitmap, 0, bi.biWidth * bi.biHeight * 4 );
    GetDIBits( hdcScreen, hbmScreen, 0, (UINT)bi.biHeight, lpbitmap, (BITMAPINFO *)&bi, DIB_RGB_COLORS );

    OpenClipboard( NULL );
    EmptyClipboard();
    CloseClipboard();

    // (3) call to check if we can access hbmScreen here
    memset( lpbitmap, 0, bi.biWidth * bi.biHeight * 4 );
    GetDIBits( hdcMemDC, hbmScreen, 0, (UINT)bi.biHeight, lpbitmap, (BITMAPINFO *)&bi, DIB_RGB_COLORS );

    // (4) actually use the data from hbmScreen
    std::vector<unsigned char> image;
    int n = bi.biWidth * bi.biHeight * 4;
    image.resize( n );
    int i = 0;
    for ( int y = bi.biHeight - 1 ; y >= 0 ; y-- )
    {
        for ( int x = 0 ; x < bi.biWidth ; x++ )
        {
            int base = ( y * bi.biWidth + x ) * 4;
            image[ i++ ] = lpbitmap[ base + 2 ]; // r
            image[ i++ ] = lpbitmap[ base + 1 ]; // g
            image[ i++ ] = lpbitmap[ base ]; // b
            image[ i++ ] = lpbitmap[ base + 3 ]; // a
        }
    }
    free( lpbitmap );
    unsigned error = lodepng::encode( "C:/a.png", image, bi.biWidth, bi.biHeight );
}
Jonathan Potter
  • 33,927
  • 4
  • 52
  • 68
  • See the notes about `CF_BITMAP` in the [Clipboard Formats](https://msdn.microsoft.com/en-us/library/windows/desktop/ms649013.aspx) documentation. `CF_BITMAP` is a DDB and can't be stored as-is, it gets converted and stored as a DIB instead. – Remy Lebeau May 28 '16 at 20:18