【转】浅析Windows编程的剪贴板
摘要: 本文对Windows剪贴板机制作了深入、全面的阐述,具体内容包括:文本、位图、DSP、自定义格式剪贴板的使用和多数据项和延迟提交技术。
关键词: VC++6.0; 剪贴板机制;数据格式;延迟提交
Windows剪贴板
Windows剪贴板是一种比较简单同时也是开销比较小的IPC(InterProcess Communication,进程间通讯)机制。Windows系统支持剪贴板IPC的基本机制是由系统预留的一块全局共享内存,用来暂存在各进程间进行交换的数据:提供数据的进程创建一个全局内存块,并将要传送的数据移到或复制到该内存块;接受数据的进程(也可以是提供数据的进程本身)获取此内存块的句柄,并完成对该内存块数据的读取。
为使剪贴板的这种IPC机制更加完善和便于使用,需要解决好如下三个问题:提供数据的进程在结束时Windows系统将删除其创建的全局内存块,而接受数据的进程则希望在其退出后剪贴板中的数据仍然存在,可以继续为其他进程所获取;能方便地管理和传送剪贴板数据句柄;能方便设置和确定剪贴板数据格式。为完善上述功能,Windows提供了存在于USER32.dll中的一组API函数、消息和预定义数据格式等,并通过对这些函数、消息的使用来管理在进程间进行的剪贴板数据交换。
Windows系统为剪贴板提供了一组API函数和多种消息,基本可以满足编程的需要。而且Windows还为剪贴板预定义了多种数据格式。通过这些预定义的格式,可以使接收方正确再现数据提供方放置于剪贴板中的数据内容。
文本剪贴板和位图剪贴板的使用
这两种剪贴板是比较常用的。其中,文本剪贴板是包含具有格式CF_TEXT的字符串的剪贴板,是最经常使用的剪贴板之一。在文本剪贴板中传递的数据是不带任何格式信息的ASCII字符。若要将文本传送到剪贴板,可以先分配一个可移动全局内存块,然后将要复制的文本内容写入到此内存区域。最后调用剪贴板函数将数据放置到剪贴板:
DWORD dwLength = 100; // 要复制的字串长度 HANDLE hGlobalMemory = GlobalAlloc(GHND, dwLength + 1); // 分配内存 LPBYTE lpGlobalMemory = (LPBYTE)GlobalLock(hGlobalMemory); // 锁定内存 for (int i = 0; i < dwLength; i++) // 将"*"复制到全局内存块 *lpGlobalMemory++ = '*'; GlobalUnlock(hGlobalMemory); // 锁定内存块解锁 HWND hWnd = GetSafeHwnd(); // 获取安全窗口句柄 ::OpenClipboard(hWnd); // 打开剪贴板 ::EmptyClipboard(); // 清空剪贴板 ::SetClipboardData(CF_TEXT, hGlobalMemory); // 将内存中的数据放置到剪贴板 ::CloseClipboard(); // 关闭剪贴板 |
这里以OpenClipboard()打开剪贴板,并在调用了EmptyClipboard()后使hWnd指向的窗口成为剪贴板的拥有者,一直持续到CloseClipboard()函数的调用。在此期间,剪贴板为拥有者所独占,其他进程将无法对剪贴板内容进行修改。
从剪贴板获取文本的过程与之类似,首先打开剪贴板并获取剪贴板的数据句柄,如果数据存在就拷贝其数据到程序变量。由于GetClipboardData()获取的数据句柄是属于剪贴板的,因此用户程序必须在调用CloseClipboard()函数之前使用它:
HWND hWnd = GetSafeHwnd(); // 获取安全窗口句柄 ::OpenClipboard(hWnd); // 打开剪贴板 HANDLE hClipMemory = ::GetClipboardData(CF_TEXT);// 获取剪贴板数据句柄 DWORD dwLength = GlobalSize(hClipMemory); // 返回指定内存区域的当前大小 LPBYTE lpClipMemory = (LPBYTE)GlobalLock(hClipMemory); // 锁定内存 m_sMessage = CString(lpClipMemory); // 保存得到的文本数据 GlobalUnlock(hClipMemory); // 内存解锁 ::CloseClipboard(); // 关闭剪贴板 |
大多数应用程序对图形数据采取的是位图的剪贴板数据格式。位图剪贴板的使用与文本剪贴板的使用是类似的,只是数据格式要指明为CF_BITMAP,而且在使用SetClipboardData()或GetClipboardData()函数时交给剪贴板或从剪贴板返回的是设备相关位图句柄。下面这段示例代码将把存在于剪贴板中的位图数据显示到程序的客户区:
HWND hWnd = GetSafeHwnd(); // 获取安全窗口句柄 ::OpenClipboard(hWnd); // 打开剪贴板 HANDLE hBitmap = ::GetClipboardData(CF_BITMAP); // 获取剪贴板数据句柄 HDC hDC = ::GetDC(hWnd); // 获取设备环境句柄 HDC hdcMem = CreateCompatibleDC(hDC); // 创建与设备相关的内存环境 SelectObject(hdcMem, hBitmap); // 选择对象 SetMapMode(hdcMem, GetMapMode(hDC)); // 设置映射模式 BITMAP bm; // 得到位图对象 GetObject(hBitmap, sizeof(BITMAP), &bm); BitBlt(hDC, 0, 0, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY); //位图复制 ::ReleaseDC(hWnd, hDC); // 释放设备环境句柄 DeleteDC(hdcMem); // 删除内存环境 ::CloseClipboard(); // 关闭剪贴板 |
多数据项和延迟提交技术
要把数据放入剪贴板,在打开剪贴板后一定要调用EmptyClipboard()函数清除当前剪贴板中的内容,而不可以在原有数据项基础上追加新的数据项。但是,可以在EmptyClipboard()和CloseClipboard()调用之间多次调用SetClipboardData()函数来放置多个不同格式的数据项。例如:
OpenClipboard(hWnd); EmptyClipboardData(); SetClipboardData(CF_TEXT, hGMemText); SetClipboardData(CF_BITMAP, hBitmap); CloseClipboard(); |
这时如果用CF_TEXT或CF_BITMAP等格式标记去调用IsClipboardFormatAvailable()都将返回TRUE,表明这几种格式的数据同时存在于剪贴板中。以不同的格式标记去调用GetClipboardData()函数可以得到相应的数据句柄。
对于多数据项的剪贴板数据,还可以用CountClipboardFormats()和EnumClipboardFormats()函数得到当前剪贴板中存在的数据格式数目和具体的数据格式。EnumClipboardFormats()的函数原型为:
UINT EnumClipboardFormats(UINT format); |
参数format指定了剪贴板的数据格式。如果成功执行将返回format指定的格式的下一个数据格式值,如果format为最后的数据格式值,那么将返回0。由此不难写出处理剪贴板中所有格式数据项的程序段代码:
UINT format = 0; // 从第一种格式值开始枚举 OpenClipboard(hWnd); while(format = EnumClipboardFormats(format)) { …… // 对相关格式数据的处理 } CloseClipboard(); |
在数据提供进程创建了剪贴板数据后,一直到有其他进程获取剪贴板数据前,这些数据都要占据内存空间。如在剪贴板放置的数据量过大,就会浪费内存空间,降低对资源的利用率。为避免这种浪费,可以采取延迟提交(Delayed rendering)技术,即由数据提供进程先创建一个指定数据格式的空(NULL)剪贴板数据块,直到有其他进程需要数据或自身进程要终止运行时才真正提交数据。
延迟提交的实现并不复杂,只需剪贴板拥有者进程在调用SetClipboardData()将数据句柄参数设置为NULL即可。延迟提交的拥有者进程需要做的主要工作是对WM_RENDERFORMAT、WM_DESTORYCLIPBOARD和WM_RENDERALLFORMATS等剪贴板延迟提交消息的处理。
当另一个进程调用GetClipboardData()函数时,系统将会向延迟提交数据的剪贴板拥有者进程发送WM_RENDERFORMAT消息。剪贴板拥有者进程在此消息的响应函数中应使用相应的格式和实际的数据句柄来调用SetClipboardData()函数,但不必再调用OpenClipboard()和EmptyClipboard()去打开和清空剪贴板了。在设置完数据有也无须调用CloseClipboard()关闭剪贴板。如果其他进程打开了剪贴板并且调用EmptyClipboard()函数去清空剪贴板的内容,接管剪贴板的拥有权时,系统将向延迟提交的剪贴板拥有者进程发送WM_DESTROYCLIPBOARD消息,以通知该进程对剪贴板拥有权的丧失。而失去剪贴板拥有权的进程在收到该消息后则不会再向剪贴板提交数据。另外,在延迟提交进程在提交完所有要提交的数据后也会收到此消息。如果延迟提交剪贴板拥有者进程将要终止,系统将会为其发送一条WM_RENDERALLFORMATS消息,通知其打开并清除剪贴板内容。在调用SetClipboardData()设置各数据句柄后关闭剪贴板。
下面这段代码将完成对数据的延迟提交,WM_RENDERFORMAT消息响应函数OnRenderFormat()并不会立即执行,当有进程调用GetClipboardData()函数从剪贴板读取数据时才会发出该消息。在消息处理函数中完成对数据的提交:
进行延迟提交:
HWND hWnd = GetSafeHwnd(); // 获取安全窗口句柄 ::OpenClipboard(hWnd); // 打开剪贴板 ::EmptyClipboard(); // 清空剪贴板 ::SetClipboardData(CF_TEXT, NULL); // 进行剪贴板数据的延迟提交 ::CloseClipboard(); // 关闭剪贴板 |
在WM_RENDERFORMAT消息的响应函数中:
DWORD dwLength = 100; // 要复制的字串长度 HANDLE hGlobalMemory = GlobalAlloc(GHND, dwLength + 1); // 分配内存块 LPBYTE lpGlobalMemory = (LPBYTE)GlobalLock(hGlobalMemory); // 锁定内存块 for (int i = 0; i < dwLength; i++) // 将"*"复制到全局内存块 *lpGlobalMemory++ = '*'; GlobalUnlock(hGlobalMemory); // 锁定内存块解锁 ::SetClipboardData(CF_TEXT, hGlobalMemory); // 将内存中的数据放置到剪贴板 |
DSP和自定义数据格式的使用
Windows系统预定义了三个带“DSP”前缀的数据格式:CF_DSPTEXT、CF_DSPBITMAP和CF_DSPMETAFILEPICT。这是一些伪标准格式,用于表示在程序中定义的私有剪贴板数据格式。对于不同的程序,这些格式的规定是不同的,因此这些格式只针对某一具体程序的不同实例才有意义。
为使用DSP数据格式,必须确保进程本身与剪贴板拥有者进程同属一个程序。可以调用GetClipboardOwner()函数来获取剪贴板拥有者窗口句柄,并调用GetClassName()来获取窗口类名:
HWND hClipOwner = GetClipboardOwner(); GetClassName(hClipOwner, &ClassName, 255); |
如果剪贴板拥有者窗口类名同本进程的窗口类名一致,就可以使用带有DSP前缀的剪贴板数据格式了。
除了使用Windows预定义的剪贴板数据格式外,也可以在程序中使用自定义的数据格式。对于自定义的数据格式lpszFormat,可以调用RegisterClipboardFormat()函数来登记,并获取其返回的格式标识值:
UINT format = RegisterClipboardFormat(lpszFormat); |
对此返回的格式标识值的使用与系统预定义的格式标识是一样的。可以通过GetClipboardFormatName()函数来获取自定义格式的ASCII名。
小结
本文主要对Windows编程中的剪贴板机制作了较为深入的讨论,对其中常用的文本、位图、DSP和自定义数据格式的使用方法以及多数据项和延迟提交等重要技术一并做了阐述。并给出了具体的程序示例代码,使读者能够更好的掌握剪贴板机制的使用。
【转】浅析Windows编程的剪贴板的更多相关文章
- 【Windows编程】系列第九篇:剪贴板使用
上一篇我们学习了常见的通用对话框,本篇来了解剪贴板的使用,它常用于复制粘贴功能. 剪贴板是Windows最早就加入的功能,由于该功能非常实用,我们几乎每天都会使用到.通过剪贴板,我们就可以将数据从一个 ...
- Windows编程基础
主要内容:介绍Windows编程的一些基础概念 1.窗口的概念 <1>一个应用程序的窗口通常包括控制菜单框.下拉菜单. 工作区以及最大化按钮.最小化按钮, 还有垂直滚动条.水平滚动条 &l ...
- 【Windows编程】系列第六篇:创建Toolbar与Statusbar
上一篇我们学习了解了如何使用Windows GDI画图,该应用程序都是光光的静态窗口,我们使用Windows应用程序,但凡稍微复杂一点的程序都会有工具栏和状态栏,工具栏主要用于一些快捷功能按钮.比如典 ...
- 【Windows编程】系列第十一篇:多文档界面框架
前面我们所举的例子中都是单文档界面框架,也就是说这个窗口里面的客户区就是一个文档界面,可以编写程序在里面输入或者绘制文本和图形输出,但是不能有出现多个文档的情况.比如下面的UltraEdit就是一个典 ...
- 【Windows编程】系列第十篇:文本插入符
大家知道,在使用微软的编程环境创建工程时会让你选择是控制台模式还是Windows应用程序.如果选择控制台的console模式,就会在运行时出现一个黑洞洞的字符模式窗口,里面就有等待输入一闪一闪的插入符 ...
- 【Windows编程】系列第八篇:通用对话框
上一篇我们学习了菜单的基本编程,本篇来了解一下通用对话框的使用.Windows系统之所以是目前最流行的桌面系统,也是因为Windows有一套标准化,统一友好的交互界面,比如菜单.工具栏.状态栏以及各个 ...
- 【Windows编程】系列第七篇:Menubar的创建和使用
上一篇我们学习了利用windows API创建工具栏和菜单栏,与上一篇紧密联系的就是菜单栏,菜单栏是一个大多数复杂一些的Windows应用程序不可或缺的部分.比如下图就是Windows自带的记事本的菜 ...
- 【Windows编程】系列第五篇:GDI图形绘制
上两篇我们学习了文本字符输出以及Unicode编写程序,知道如何用常见Win32输出文本字符串,这一篇我们来学习Windows编程中另一个非常重要的部分GDI图形绘图.Windows的GDI函数包含数 ...
- 【Windows编程】系列第四篇:使用Unicode编程
上一篇我们学习了Windows编程的文本及字体输出,在以上几篇的实例中也出现了一些带有“TEXT”的Windows宏定义,有朋友留言想了解一些ANSI和Unicode编程方面的内容,本章就来了解和学习 ...
随机推荐
- LoadRunner调用Java程序—性能测试
为了充分利用LoadRunner的场景控制和分析器,帮助我们更好地控制脚本加载过程,从而展现更直观有效的场景分析图表.本次将重点讨论LoadRunner如何调用Java测试代码,完成压力测试. 通常我 ...
- Flex 箭头(军标)库封装完成
封装的一个月,在这个月期间还完成的一些其它的工作:公司有规定不能公布代码,我可以讲一下大致的流程,真的很抱歉! 1.用B样条曲线画箭头的两侧的曲线,这个要注意了,不要贝塞尔曲线,因为贝塞尔曲线的算法会 ...
- Windows下Lua+Redis 断点调试环境搭建==Linux下类似
Lua+Redis 断点调试环境搭建 windows环境,使用Redis,写lua脚本头疼的问题之一不能对脚本断点调试,google加上自己的摸索,终于搞定. 1.下载ZeroBraneStudio, ...
- 【HTML】Beginner3:ParagraphsEmphasisLine breaks
1.Paragraphs </p> distinct blocks of the text Think of the HTML contents as if it were ...
- Linux 设备模型浅析之 uevent 篇(2)
Linux 设备模型浅析之 uevent 篇 本文属本人原创,欢迎转载,转载请注明出处.由于个人的见识和能力有限,不可能面 面俱到,也可能存在谬误,敬请网友指出,本人的邮箱是 yzq.seen@gma ...
- 有关window.location 对象
之前在有关location的使用就一种方式给他的href赋值使页面跳转.今天看项目中的代码看到了一个不一样的用法 window.location.href = 'http://' + document ...
- 用gooreplacer来加速你的浏览器
博客搬到了fresky.github.io - Dawei XU,请各位看官挪步.最新的一篇是:用gooreplacer来加速你的浏览器.
- Cactus入门
这是一个WebProject,有关Cactus用法详见本文测试用例 首先是web.xml <?xml version="1.0" encoding="UTF-8&q ...
- Windows Server 2012 R2中的网络诊断命令
Get-NetAdapter Get-NetIPAddress Get-NetIPConfiguration(GIP) TNC :Pinging Servers and Trace Route tnc ...
- DirectoryEntry 活动目录的使用
public class DirectoryHelper { public static string DomainName = System.Environment.UserDomainName; ...