为什么我不能将 DIB 部分放在剪贴板上?
Why can't I put a DIB section on the clipboard?
我正在尝试抓取屏幕内容,直接修改抓取图像的位,然后将结果放入剪贴板。 (实际上,我最终对剪贴板并不感兴趣,只是将其用作测试步骤。)
我从 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 都是一样的,但我屏幕的左下角实际上是蓝色的。无论如何,即使实际位错了,我也应该在剪贴板上得到 某些东西 的图像,但我没有。
我试过使用 CF_DIB
作为 SetClipboardData
的第一个参数而不是 CF_BITMAP
,但是调用失败了。
如果我注释掉对 CreateDIBSection
的调用并取消注释对 CreateCompatibleBitmap
的调用,那么它可以工作,但我没有机会直接修改图像位。
我想我可以先捕获我的 DIB 部分,修改它,然后调用 CreateCompatibleBitmap
并从 DIB 部分 blit 到 "compatible bitmap",但再次复制这些位似乎有点愚蠢没有明显的原因。
为什么我不能将我的 DIB 部分传递给 SetClipboardData
?
(我必须说我讨厌使用 GDI 等。它通常很清楚。)
当我找到 this 时,终于想通了。 MSDN 上的 API 文档对此相当模糊,可能是因为它本身可以追溯到很久以前,但看起来剪贴板函数都使用 Windows 3.x 样式的内存分配系统(GlobalAlloc
等)。
系统剪贴板将共享内存直接暴露给应用程序是有意义的,而不是 OS 必须将数据复制到内部缓冲区。但是剪贴板功能的历史可以追溯到很久以前,以致于不存在更新的基于页面文件的共享内存方案,因此他们不得不使用 GlobalAlloc
内存。当 32 位 Windows 出现时,模仿该机制比破坏现有的应用程序代码更有意义。
我强烈怀疑出于类似的原因,大多数 GDI 句柄实际上也是 GlobalAlloc
句柄,这就是为什么您可以将 return 从 CreateCompatibleBitmap
传递到剪贴板的原因。相比之下,CreateDIBSection
没有 完全使用旧式分配,这一点很明显,因为您可以告诉它在文件映射中存储位。 (我怀疑 returns 的句柄仍然来自 GlobalAlloc
,但如此分配的块又包含指向图像数据虚拟内存的直接指针,并且 SetClipboardData
对此进行了测试因为这是显而易见的 "gotcha".)
所以我通过让 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
编辑的句柄 return 可以用于调用 GlobalLock
然后你可以直接获取这些位而不会在 CScreenshot::SendToClipboard
中产生副本,因为你可以直接将它传递给 SetClipboardData
。但是,我也强烈怀疑这将是未记录的行为(但如果我错了请纠正我!),这是一个非常糟糕的主意。您还必须跟踪是否将其传递到剪贴板,如果传递了,则不要对其调用 DeleteObject
。但我不确定。我还怀疑 SetClipboardData
无论如何都必须复制它,因为它可能没有分配给 GMEM_SHARE
.
感谢评论者让我更接近于弄清楚它。
我正在尝试抓取屏幕内容,直接修改抓取图像的位,然后将结果放入剪贴板。 (实际上,我最终对剪贴板并不感兴趣,只是将其用作测试步骤。)
我从 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 都是一样的,但我屏幕的左下角实际上是蓝色的。无论如何,即使实际位错了,我也应该在剪贴板上得到 某些东西 的图像,但我没有。
我试过使用 CF_DIB
作为 SetClipboardData
的第一个参数而不是 CF_BITMAP
,但是调用失败了。
如果我注释掉对 CreateDIBSection
的调用并取消注释对 CreateCompatibleBitmap
的调用,那么它可以工作,但我没有机会直接修改图像位。
我想我可以先捕获我的 DIB 部分,修改它,然后调用 CreateCompatibleBitmap
并从 DIB 部分 blit 到 "compatible bitmap",但再次复制这些位似乎有点愚蠢没有明显的原因。
为什么我不能将我的 DIB 部分传递给 SetClipboardData
?
(我必须说我讨厌使用 GDI 等。它通常很清楚。)
当我找到 this 时,终于想通了。 MSDN 上的 API 文档对此相当模糊,可能是因为它本身可以追溯到很久以前,但看起来剪贴板函数都使用 Windows 3.x 样式的内存分配系统(GlobalAlloc
等)。
系统剪贴板将共享内存直接暴露给应用程序是有意义的,而不是 OS 必须将数据复制到内部缓冲区。但是剪贴板功能的历史可以追溯到很久以前,以致于不存在更新的基于页面文件的共享内存方案,因此他们不得不使用 GlobalAlloc
内存。当 32 位 Windows 出现时,模仿该机制比破坏现有的应用程序代码更有意义。
我强烈怀疑出于类似的原因,大多数 GDI 句柄实际上也是 GlobalAlloc
句柄,这就是为什么您可以将 return 从 CreateCompatibleBitmap
传递到剪贴板的原因。相比之下,CreateDIBSection
没有 完全使用旧式分配,这一点很明显,因为您可以告诉它在文件映射中存储位。 (我怀疑 returns 的句柄仍然来自 GlobalAlloc
,但如此分配的块又包含指向图像数据虚拟内存的直接指针,并且 SetClipboardData
对此进行了测试因为这是显而易见的 "gotcha".)
所以我通过让 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
编辑的句柄 return 可以用于调用 GlobalLock
然后你可以直接获取这些位而不会在 CScreenshot::SendToClipboard
中产生副本,因为你可以直接将它传递给 SetClipboardData
。但是,我也强烈怀疑这将是未记录的行为(但如果我错了请纠正我!),这是一个非常糟糕的主意。您还必须跟踪是否将其传递到剪贴板,如果传递了,则不要对其调用 DeleteObject
。但我不确定。我还怀疑 SetClipboardData
无论如何都必须复制它,因为它可能没有分配给 GMEM_SHARE
.
感谢评论者让我更接近于弄清楚它。