【问题标题】:Why can't I put a DIB section on the clipboard?为什么我不能在剪贴板上放一个 DIB 部分?
【发布时间】:2018-05-13 07:04:54
【问题描述】:

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

我从this question 的答案之一开始。但是,它使用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一样,但我屏幕的左下角实际上是蓝色的。无论如何,即使实际的位是错误的,我也应该在剪贴板上得到 something 的图像,但我没有。

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

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

我想我可以先捕获我的 DIB 部分,对其进行修改,然后调用 CreateCompatibleBitmap 并将 DIB 部分中的 blit 复制到“兼容位图”中,但毫无理由地再次复制这些位似乎有点愚蠢。

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

(我必须说我讨厌使用 GDI 等。它通常像泥巴一样清晰。)

【问题讨论】:

  • CF_DIB 想要一个打包的位图,即包含 BITMAPINFO 后跟位图位的单个内存块 - 而不是 GDI 句柄。 CreateDIBSection 允许您使用文件映射句柄和偏移量指定位图的放置位置。创建一个足够大的内存映射文件以包含标头和位,并适当地设置dwOffset 以指向标头。
  • @JonathanPotter 感谢您的提示.. 有一点不清楚是什么作为SetClipboardData 的第二个参数传递。我试过从CreateDIBSection返回,从CreateFileMapping返回,和(HANDLE)(((BITMAPINFO*)pBits) - 1),但它们都不起作用。使用CF_DIB时,都会报错0x00000006 The handle is invalid.我已经把header复制到了映射中(CreateDIBSection显然没有),但是还是不行。
  • 另一个奇怪的是CF_BITMAP never 会产生错误。甚至SetClipboardData( CF_BITMAP, (HANDLE)0xCCCCCCCC ) 声称要成功!这个API好像有点乱。
  • CreateCompatibleBitmap 让您可以直接访问这些位。 GetDIBits 怎么了?
  • @BarmakShemirani 谢谢你的提示。如果我理解正确,GetDIBits 复制 位,那么一旦我做出更改,我就必须致电SetDIBits 将它们放回原处。但我想我得试试这个,因为我不知道还能做什么。有点令人沮丧,我不得不为本来只需要 2 份的东西买 4 份副本……难怪现代程序这么慢!

标签: windows winapi clipboard gdi


【解决方案1】:

当我找到this 时,终于想通了。 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_SHARE

感谢评论者让我更接近于弄清楚。

【讨论】:

  • 你总是需要调用DeleteObject(hbitmap) 注意SetClipboardData 创建单独的副本(否则我可能错了!)CreateCompatibleBitmap 创建 DDB hbitmap,SetClipboardData(CF_BITMAP...) 将该 DDB 转换为 DIB。 GMEM_SHARE 也已过时并被忽略。如果您使用GMEM_FIXED,则不需要锁定和解锁。
  • @BarmakShemirani From MSDN: "如果SetClipboardData 成功,则系统拥有hMem 参数标识的对象。一旦所有权转移,应用程序可能无法写入或释放数据到系统...如果hMem 参数标识一个内存对象,则该对象必须是使用带有GMEM_MOVEABLE 标志的函数分配的。” GMEM_FIXED 可能会起作用(未记录!),但这可能会迫使系统进行复制。
  • 是的,我很困惑。您正在使用memcpy 自己制作副本,文档说不要调用GlobalFree( hClipboardDib ),除非SetClipboardData 失败,或者OpenClipboard 在它之前失败。 DeleteObject(hbitmap) 仍然是必需的。在最后一部分中,您提出了一些奇怪的建议,我认为这涉及将 void *pBits; 替换为 GlobalAlloc 我不知道这将如何工作。不要担心那部分。现代 CPU 非常擅长分配和复制大块内存。
  • @BarmakShemirani 是的,这主要是学术性的,尽管我仍然认为每次 CPU 变得更快时,我们都会随着我们越来越多的(尽管是明智的)懒惰而减慢它们的速度。 :) 我不是在CreateCompatibleBitmap 之后建议GlobalAlloc,而是建议它返回的句柄可能是“全局内存句柄”,然后您可以GlobalLock 直接获取位,但它可能没有记录所以有点冒险。
  • 另外,如果你说CF_BITMAPSetClipboardData转换为DIB,它可能无法避免工作,尽管我似乎记得剪贴板可以同时具有多种格式,例如@987654363 @ CF_DIB.
猜你喜欢
  • 1970-01-01
  • 2012-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-24
相关资源
最近更新 更多