【关于HBITMAP, DC, MEM DC, Clipboard】将HBITMAP拷贝到Clipboard(Windows Clipboard & OLE Clipboard)

参考:

  • Programming Windows with MFC, 2nd. Chapter 18, 19. 建议把这两章学习完(至少到OLE drag-and-drop之前要学习完)再来尝试OLE Clipboard
  • Programming Windows 5th.
  • Chapter 12 - The Clipboard, Memory Allocation,

When your program transfers something to the clipboard, it must allocate a memory block and essentially hand it
over to the clipboard. When we've needed to allocate memory in earlier programs in this book, we've simply used
the malloc function that is supported by the standard C run-time library. However, because the memory blocks
stored by the clipboard must be shared among applications running under Windows, the malloc function is
inadequate for this task.

  • Chapter 14 - Bitmaps and Bitblts, after Figure 14-22,

For bitmaps, however, the clipboard items are not global handles but bitmap handles. When you use the
CF_BITMAP, the GetClipboardData function returns an HBITMAP object and the SetClipboardData function accepts
an HBITMAP object. If you want to transfer a bitmap to the clipboard but still have a copy of it for use by the
program itself, you must make a copy of the bitmap. Similarly, if you paste a bitmap from the clipboard, you
should also make a copy.

  • HBITMAP是怎么create, copy, destroy的?
    • Chapter 14 - Bitmaps and Bitblts, The GDI Bitmap Object
    • Creating a DDB
      •   You then obtain the handle by calling one of the DDB-creation functions: for example, CreateBitmap . These
        functions allocate and initialize some memory in GDI memory to store information about the bitmap as well as the
        actual bitmap bits. The application program does not have direct access to this memory. The bitmap is
        independent of any device context. When the program is finished using the bitmap, it should be deleted:                                      DeleteObject (hBitmap) ;
    • The Memory Device Context
      • This is the same function you use for selecting pens, brushes, fonts, regions, and palettes into device contexts.
        However, the memory device context is the only type of device context into which you can select a bitmap. (You
        can also select other GDI objects into a memory device context if you need to.)

什么是Memory Device Context?

[下面的所有引用都摘自Programming Windows 5th.]

先来看什么是Device Context

When you want to draw on a graphics output device such as the screen or printer, you must first obtain a handle
to a device context (or DC). In giving your program this handle, Windows is giving you permission to use the
device. You then include the handle as an argument to the GDI functions to identify to Windows the device on
which you wish to draw.

The device context (also called simply the "DC") is really just a data structure maintained internally by GDI. A
device context is associated with a particular display device, such as a video display or a printer. For a video
display, a device context is usually associated with a particular window on the display.

Some of the values in the device context are graphics "attributes." These attributes define some particulars of how
GDI drawing functions work. With TextOut , for instance, the attributes of the device context determine the color
of the text, the color of the text background, how the x-coordinate and y-coordinate in the TextOut function are
mapped to the client area of the window, and what font Windows uses when displaying the text.

上面作者说了一堆,意思就是:DC是Windows操作系统维护的Data Structure,DC描述了显示设备(例如屏幕、打印机)的一些属性,并被GDI functions所使用。

为什么需要DC?

原因很简单,

比如说TextOut这个GDI function,同样一个字符串,你往屏幕上TextOut和往打印机上TextOut,TextOut肯定要针对你当前的显示设备采用不同的implementation,对吧?而且不同的型号的显示器,不同型号的打印机,TextOut都要针对特定的设备提供不同的实现,对吧?你想想现在有多少款显示器,多少款打印机?如果Windows的开发者针对每一款设备都提供一个TextOut,那就不用玩了,TextOut_SamsungXXXX, TextOut_HPXXXX, TextOut_CanonXXXX,这尼玛得写多少个?更别提还有其他的N多GDI functions了!

所以,最好的方法就是Windows只提供一个版本的TextOut,这个TextOut只对DC进行操作。对,这里DC就扮演了一个虚拟的显示设备的角色,其实就是提供了一层抽象。采用的思想与JVM是一模一样的!(当然或许JVM出现的时间比DC要晚,不过不用纠结谁采用谁的思想,反正都是一个意思)。这样,由设备的驱动程序来提供设备信息,并由Windows填充到DC中,DC为所有的GDI functions提供统一的接口,GDI通过DC,再通过设备驱动程序,就实现输出了!这样一来,不管是张三家生产的显示屏(张三负责提供其驱动程序),还是李四家生产的打印机(李四负责提供其驱动程序),对Windows来说都一样:调用其驱动程序获取设备信息->填充DC->GDI对DC进行操作->操作指令送到驱动程序->设备完成绘制!(当然准确的过程不见得是这样,这只是我个人的理解,从大体上应该差不远的!)

言归正传,神马是Memory DC?

DC是虚拟的显示设备,那么Memory DC是啥意思?其实也很简单,屏幕的输出是在屏幕的LED(或别的什么材质)面板上,打印机输出是在纸张(或别的什么材料)上,这些我们可以形象地统称为“画布Canvas”。那么Memory DC的意思其实就是用memory来表示一个虚拟的Canvas,反正我用memory中的一个(或若干个)bit来表示你LED面板上的一个pixel或者纸上的一个墨点总可以吧?对,就是这个意思。当然,Memory DC本身并不是canvas,它本身还是个DC,memory才是canvas。那么就涉及到一个问题,我在memory里面绘图完毕,总要输出到实际的显示设备上吧?不是显示器就是打印机或者别的什么玩意儿。对,正因为如此,一个Memory DC必须与一个实际设备的DC兼容(compatible),因此你会发现,在创建Memory DC的时候,这个Memory DC总是和某个实际设备相关联的!正如下所述:

Normally, a device context refers to a particular graphics output device (such as a video display or a printer)
together with its device driver. A memory device context exists only in memory. It is not a real graphics output
device, but is said to be "compatible" with a particular real device.

注意:

下面的例子,在关闭程序之后Clipboard中还是有数据的,不会因为你的程序关闭就丢失,因为使用的是global memory(GlobalAlloc)

CImage::Load,然后CImage::Detach()得到一个HBITMAP hBitmap

拷贝到Windows Clipboard(因为CImage.Load进来的是DIB,而且经过测试,即便你在SetClipboardData用CF_DIB也没用,必须转换成DDB)

参考:http://stackoverflow.com/questions/32309180/why-cant-i-directly-send-an-hbimap-from-a-cimage-to-clipboard

 1 if (::OpenClipboard(this->GetSafeHwnd())) {
 2     CImage img;
 3     img.Load(_T("D:\scc.bmp"));
 4     HBITMAP hbitmap_dib = img.Detach();
 5     if (!hbitmap_dib)
 6         return;
 7 
 8     DIBSECTION ds;
 9     ::GetObject(hbitmap_dib, sizeof(DIBSECTION), &ds);
10 
11     //make sure compression is BI_RGB
12     ds.dsBmih.biCompression = BI_RGB;
13 
14     //Convert DIB to DDB
15     HDC hdc = ::GetDC(NULL);
16     HBITMAP hbitmap_ddb = ::CreateDIBitmap(
17         hdc, &ds.dsBmih, CBM_INIT, ds.dsBm.bmBits, (BITMAPINFO*)&ds.dsBmih, DIB_RGB_COLORS);
18     ::ReleaseDC(NULL, hdc);
19 
20     ::EmptyClipboard();
21     ::SetClipboardData(CF_BITMAP, hbitmap_ddb);
22     ::CloseClipboard();
23 }

拷贝到OLE Clipboard

方法1(参考https://social.msdn.microsoft.com/Forums/vstudio/en-US/ba75bba4-6f4d-43a4-905c-2caa7b0ea548/copy-a-gdi-bitmap-to-clipboard?forum=vcgeneral):

if(::AfxOleInit()) {
    COleDataSource* pods = new COleDataSource;
    STGMEDIUM clipboardData;
    ::memset(&clipboardData, 0, sizeof tagSTGMEDIUM);
    clipboardData.tymed = TYMED_GDI;
    clipboardData.hBitmap = hImage;
    pods->CacheData(CF_BITMAP, &clipboardData);
    pods->SetClipboard();
}

方法2(使用HGLOBAL),注意CacheGlobalData传入的数据必须位于从GlobalAlloc分配的memory,这一点和CacheData不同

根据该hBitmap所指向的bitmap的大小,首先用GlobalAlloc申请相应大小的memory,得到其HGLOBAL hGlobal(这一步我目前不知道该怎么做);经测试CacheGlobalData要求用GlobalAlloc得到的memory(用HGLOBAL来表示)才能工作,所以你直接把hBitmap交给CacheGlobalData是不行的(就如下面的拷贝"Hello, world"的例子,尽管HANDLE的定义为void*,你把szText直接传给CacheGlobalData也会报异常,你可以测试,即便szText是new出来的也不行,必须是GlobalAlloc弄出来的才行,对HBITMAP也是一样的道理)

然后:

if(AfxOleInit()) {
    COleDataSource* pods = new COleDataSource;
    pods->CacheGlobalData(CF_BITMAP, hGlobal);
    pods->SetClipboard();
}

 另外,经过测试,Programming Windows With MFC 2nd上1240页的例子应该如下才能工作:

char szText[] = "Hello, world"; // ANSI characters
HANDLE hData = ::GlobalAlloc(GMEM_MOVEABLE, ::strlen(szText) + 1);
LPSTR pData = (LPSTR) ::GlobalLock(hData);
::strcpy_s(pData, ::strlen(szText) + 1, szText);
::GlobalUnlock(hData);
        
if(::AfxOleInit()) {
    COleDataSource* pods = new COleDataSource;
    pods->CacheGlobalData(CF_TEXT, hData);
    pods->SetClipboard();
}

 注意,前面使用::SetClipboardData(CF_BITMAP, hImage)的时候,那个hImage所在的memory不是由GlobalAlloc分配的。但不知道为什么,还是能用。但是这个例子就必须用GlobalAlloc才行,即便你用::SetClipboardData也是一样。我只能说可能是因为::SetClipboardData对CF_BITMAP与CF_TEXT的处理不一样。

总结:

字符串必须用GlobalAlloc分配再放到clipboard

bitmap则不用GlobalAlloc,只要确保是clipboard能接收的DIB或者DDB即可

原文地址:https://www.cnblogs.com/qrlozte/p/4755773.html