Windows 为什么可以';我不能把DIB部分放在剪贴板上吗?

Windows 为什么可以';我不能把DIB部分放在剪贴板上吗?,windows,winapi,clipboard,gdi,Windows,Winapi,Clipboard,Gdi,我试图捕获屏幕内容,直接修改抓取图像的位,然后将结果放在剪贴板上。(事实上,我最终对剪贴板并不感兴趣,而是将其作为一个测试步骤。) 我从以下一个答案中的一个例子开始。但是,它使用CreateCompatibleBitmap,据我所知,无法直接访问使用该函数创建的位图位,因此我尝试使用CreateDIBSection。以下是我到目前为止的情况: void GetScreenShot(void) { int x1, y1, w, h; // get screen dimension

我试图捕获屏幕内容,直接修改抓取图像的位,然后将结果放在剪贴板上。(事实上,我最终对剪贴板并不感兴趣,而是将其作为一个测试步骤。)


我从以下一个答案中的一个例子开始。但是,它使用
CreateCompatibleBitmap
,据我所知,无法直接访问使用该函数创建的位图位,因此我尝试使用
CreateDIBSection
。以下是我到目前为止的情况:

void GetScreenShot(void)
{
    int x1, y1, w, h;

    // get screen dimensions
    x1  = GetSystemMetrics(SM_XVIRTUALSCREEN);
    y1  = GetSystemMetrics(SM_YVIRTUALSCREEN);
    w  = GetSystemMetrics(SM_CXVIRTUALSCREEN);
    h  = GetSystemMetrics(SM_CYVIRTUALSCREEN);

    // copy screen to bitmap

    HDC hScreen = GetDC(NULL);

    HDC hDC = CreateCompatibleDC(hScreen);
    if( !hDC )
        throw 0;

    // This works:
    //HBITMAP hBitmap = CreateCompatibleBitmap(hScreen, w, h);

    BITMAPINFO BitmapInfo;
    BitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    BitmapInfo.bmiHeader.biWidth = w;
    BitmapInfo.bmiHeader.biHeight = h;
    BitmapInfo.bmiHeader.biPlanes = 1;
    BitmapInfo.bmiHeader.biBitCount = 24;   // assumption; ok for our use case
    BitmapInfo.bmiHeader.biCompression = BI_RGB;
    BitmapInfo.bmiHeader.biSizeImage = ((w * 3 + 3) & ~3) * h;
    BitmapInfo.bmiHeader.biXPelsPerMeter = (int)(GetDeviceCaps( hScreen, LOGPIXELSX ) * 39.3701 + 0.5);
    BitmapInfo.bmiHeader.biYPelsPerMeter = (int)(GetDeviceCaps( hScreen, LOGPIXELSY ) * 39.3701 + 0.5);
    BitmapInfo.bmiHeader.biClrUsed = 0;
    BitmapInfo.bmiHeader.biClrImportant = 0;
    BitmapInfo.bmiColors[0].rgbBlue = 0;
    BitmapInfo.bmiColors[0].rgbGreen = 0;
    BitmapInfo.bmiColors[0].rgbRed = 0;
    BitmapInfo.bmiColors[0].rgbReserved = 0;

    void *pBits;
    // This does not work:
    HBITMAP hBitmap = CreateDIBSection( hScreen, &BitmapInfo, DIB_RGB_COLORS, &pBits, NULL, 0 );
    if( !hBitmap )
        throw 0;

    HGDIOBJ old_obj = SelectObject(hDC, hBitmap);
    if( !old_obj )
        throw 0;

    if( !BitBlt(hDC, 0, 0, w, h, hScreen, x1, y1, SRCCOPY) )
        throw 0;

    if( !SelectObject(hDC, old_obj) )
        throw 0;

    if( !GdiFlush() )
        throw 0;

    // this is where we would modify the image

    // save bitmap to clipboard

    if( !OpenClipboard(NULL) )
        throw 0;

    if( !EmptyClipboard() )
        throw 0;

    if( !SetClipboardData( CF_BITMAP, hBitmap ) )   // CF_DIB causes the throw
        throw 0;

    if( !CloseClipboard() )
        throw 0;

    // clean up
    DeleteDC(hDC);
    ReleaseDC(NULL, hScreen);
    DeleteObject(hBitmap);
}
然而,这是行不通的。所有调用都报告成功,但图像不会出现在剪贴板上

当我在调试器中运行它时,我可以看到调用
BitBlt
pBits
处的图像数据,尽管有点可疑,因为前一串值都是R、G、B,但我屏幕的左下角实际上是蓝色的。无论如何,即使实际的位是错误的,我应该在剪贴板上得到一些图像,但我没有

我尝试使用
CF_DIB
作为
SetClipboardData
的第一个参数,而不是
CF_BITMAP
,但调用失败


如果我注释掉对
CreateDIBSection
的调用,并取消对
CreateCompatibleBitmap
的调用的注释,则它可以工作,但我没有机会直接修改图像位


我想我可以先捕获我的DIB部分,修改它,然后调用
CreateCompatibleBitmap
并将blit从DIB部分复制到“兼容位图”中,但是,毫无明显原因地再次复制这些位似乎有点愚蠢

为什么我不能将DIB部分传递给
SetClipboardData


(我必须说,我讨厌使用GDI等工具。通常情况下,它就像泥一样清晰。)

当我发现时,我终于明白了。MSDN的API文档对此相当含糊,可能是因为它本身可以追溯到很久以前,但剪贴板函数似乎都使用Windows 3.x风格的内存分配系统(
GlobalAlloc
等)

系统剪贴板直接向应用程序公开共享内存是有意义的,而操作系统必须将数据复制到内部缓冲区。但是剪贴板的功能可以追溯到很久以前,新的基于页面文件的共享内存方案并不存在,因此它们必须使用
GlobalAlloc
内存。当32位Windows出现时,只模拟这种机制而不是破坏现有的应用程序代码更有意义


我强烈怀疑,出于类似的原因,大多数GDI句柄实际上也是
GlobalAlloc
句柄,这就是为什么您可以将
CreateCompatibleBitmap
的返回传递到剪贴板。相比之下,
CreateDIBSection
没有完全使用旧式分配,这一点很明显,因为您可以告诉它将位存储在文件映射中。(我怀疑它返回的句柄仍然来自
GlobalAlloc
,但这样分配的块又包含一个指向图像数据虚拟内存的直接指针,
SetClipboardData
对此进行了测试,因为这是一个明显的“问题”。)

所以我通过让
CreateDIBSection
分配到它想分配的任何地方来修复一切,因为不管怎样,都不可能将它交给
SetClipboardData
,然后在我想发送到剪贴板时这样做:

void CScreenshot::SendToClipboard( void )
{
    HGLOBAL hClipboardDib = GlobalAlloc( GMEM_MOVEABLE | GMEM_SHARE, cbDib );
    if( !hClipboardDib )
        throw 0;

    void *pClipboardDib = GlobalLock( hClipboardDib );
    memcpy( pClipboardDib, &BitmapInfo, sizeof(BITMAPINFOHEADER) );
    memcpy( (BITMAPINFOHEADER*)pClipboardDib+1, pBits, BitmapInfo.bmiHeader.biSizeImage );
    GlobalUnlock( hClipboardDib );

    if( !OpenClipboard( NULL ) )
    {
        GlobalFree( hClipboardDib );
        throw 0;
    }

    EmptyClipboard();
    SetClipboardData( CF_DIB, hClipboardDib );
    CloseClipboard();
}
不幸的是,我必须在这里创建一个冗余副本,但好的一面是,我强烈怀疑读取剪贴板的应用程序会看到相同的副本,而不是Windows在内部进行任何进一步的复制


如果我想成为一个完全高效的瘾君子,我怀疑从
CreateCompatibleBitmap
返回的句柄可以用于调用
GlobalLock
,然后您可以直接获取位,而不会在
CScreenshot::SendToClipboard
中产生副本,因为您可以直接将其传递给
SetClipboardData
。然而,我也强烈怀疑这是一种没有记录的行为(如果我错了,请纠正我!),所以这是一个非常糟糕的主意。您还必须跟踪是否已将其传递到剪贴板,如果已传递,则不要对其调用
DeleteObject
。但我不确定。我还怀疑
SetClipboardData
无论如何都必须复制它,因为它可能没有分配
GMEM\u共享


感谢评论员让我更接近了解它。

CF_DIB
想要一个压缩位图,即一块包含
BITMAPINFO
后跟位图位的内存,而不是GDI句柄
CreateDIBSection
允许您使用文件映射句柄和偏移量指定位图的放置位置。创建一个足够大的内存映射文件,以包含头和位,并适当地设置
dwOffset
,使其指向头。@JonathanPotter谢谢你的提示。。有一件事不清楚,那就是传递什么作为
SetClipboardData
的第二个参数。我尝试过从
CreateDIBSection
返回,从
CreateFileMapping
返回,以及从
(HANDLE)((BITMAPINFO*)pBits)-1
返回,但都不起作用。当使用
CF\u DIB
时,它们都会导致错误
0x00000006句柄无效。
我已经将头复制到映射中(
CreateDIBSection
显然没有),但它仍然不起作用。另一个奇怪的事情是
CF\u BITMAP
从未生成错误。甚至
SetClipboardData(CF_BITMAP,(HANDLE)0xcccc)
都声称成功!这个API看起来有点乱。
CreateCompatibleBitmap
允许您直接访问位。
GetDIBits
有什么问题?@BarmakShemiran