Windows编程的剪贴板机制 天极网

卫衣 / 双肩包 / T 恤/羽绒服等任选,下单送 CSDN 年卡 程序员周边任选一款实物,直接赠送 CSDN 会员年卡+ Coding Plan,写代码学习装备一起拿下 立即抢购
Windows编程的剪贴板机制  
天极网
2004-9-21 9:51:00 文/郎锐
 

  摘要: 本文对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和自定义数据格式的使用方法以及多数据项和延迟提交等重要技术一并做了阐述。并给出了具体的程序示例代码,使读者能够更好的掌握剪贴板机制的使用。

 
C/C++开发环境配置全攻略:从编译器选型到VS Code实战 在软件开发领域,编译器和构建系统是程序从源代码到可执行文件的核心技术基础。编译器如GCC、Clang和MSVC负责将高语言翻译为机器码,而构建系统如CMake则管理多文件项目的编译流程,确保依赖关系正确。这些工具的技术价值在于提供了跨平台、高效且标准化的代码构建能力,是现代软件工程不可或缺的环节。在应用场景上,无论是嵌入式开发、系统编程还是高性能计算,都离不开稳定可靠的开发环境配置。本文聚焦于C/C++开发环境的搭建,详细解析了**MSYS2**在Windows平台下的应用,以及如何通过**CMake** 阅读详情

相关推荐

MacOS下Trae IDE搭建现代C++开发环境全攻略

C++作为高性能系统编程的核心语言,其开发环境的搭建直接影响开发效率与代码质量。现代C++开发环境通常围绕编译器工具链、构建系统、代码智能感知和调试工具构建。在macOS平台上,LLVM/Clang编译器与CMake构建系统的组合已成为行业标准,而Language Server Protocol(LSP)的普及使得代码补全和跳转功能得以跨编辑器统一实现。这些技术的价值在于为开发者提供高效、可靠的工程化支持,特别是在处理大型项目或团队协作时。本文以Trae IDE为具体应用场景,详细演示如何集成Clang编译

weixin_34411563的博客 327

C++ AI模型统一接入引擎(第一篇):项目介绍与环境搭建

本项目从零开发大模型接入层,旨在帮助程序员掌握AI模型技术并实现实际应用。项目背景分析了中国互联网发展历程及AI技术爆发趋势,强调开发者需深入理解大模型底层技术而非仅使用现成产品。系统架构包含应用层、ChatSDK(模型管理、会话管理、数据存储)和第三方服务,支持云端和本地模型统一接入。目前已实现DeepSeek、GPT-4等主流模型的对接,并构建了网页版智能聊天助手,支持会话管理、多模型切换等功能。项目采用cpp-httplib、spdlog等第三方,通过CMake构建,为开发者提供完整的大模型接入方案

无限进步!!! 521

从跳表无锁插入到元数据原子发布:现代 C++ 存储内核如何跨越物理 I/O 与 CPU 内存模型

在现代工业 LSM-Tree 存储引擎的内核设计中,C++ 工程师对“无锁化(Lock-Free)”与“纳秒确定性”的极致偏执,几乎被雕刻进了每一行代码里。为了抹平动态内存分配器的碎片与互斥锁竞争,我们在写入路径上采用紧凑的 Arena 内存池与无锁单写多读跳表(),将插入操作的延迟死死压制在 CPU 缓存流水线的物理极限;为了让前台点查(Point Get)彻底告别昂贵的全局锁争用与特权上下文切换,引擎巧妙融合了线程局部缓存(

weixin_45715405的博客 31

Ubuntu 22.04 C++开发环境搭建:从系统安装到IDE配置全攻略

C++作为系统编程的核心语言,其开发环境的搭建是每个开发者必须掌握的基础技能。在Linux系统中,GCC/G++编译器与GDB调试器构成了C++开发的基石,通过理解工具链的工作原理,开发者能够构建高效稳定的开发环境。现代C++项目通常依赖CMake这样的构建系统来管理复杂的依赖关系,而集成开发环境如VS Code和CLion则大幅提升了编码效率。本文聚焦于在Ubuntu 22.04 LTS系统上,从虚拟机与物理机双系统安装选择开始,逐步配置软件源、安装GCC编译器、掌握CMake构建工具,并深入解决Wi-

weixin_30323631的博客 453

解锁B站知识宝藏:BiliBiliCCSubtitle字幕下载与转换全攻略

在信息爆炸的时代,B站(哔哩哔哩)已成为知识获取的重要平台,无数优质UP主为视频添加了精心制作的CC字幕。然而,平台并未提供直接下载这些字幕的功能,这让语言学习者、内容创作者和研究者们感到困扰。BiliBiliCCSubtitle正是为解决这一痛点而生的开源工具,它能够智能下载B站CC字幕并将其转换为通用的SRT格式,让知识真正"落地"。 ## 从问题到解决方案:字幕下载的技术路径 ### 为

gitblog_00128的博客 231

C++——C++基础】

本文系统介绍了C++中的命名空间、输入输出、缺省参数、函数重载与引用机制。命名空间有效避免标识符冲突,通过namespace和::实现作用域隔离;C++输入输出流(cin/cout)基于std命名空间,支持自动类型识别;缺省参数需从右向左连续定义,且声明中指定默认值;函数重载通过形参差异实现多态;引用作为变量别名,无额外内存开销,广泛用于传参与返回值,提升效率,但不可改变指向,且不能返回局部变量引用。

SHARK_pssm的博客 176

Qwen3-ForcedAligner-0.6B 私有化部署实战:从模型离线、Docker封装到会议原声精准回听

text=text,模型离线下载版本固定SHA256目录规划环境隔离Docker封装GPU绑定健康检查长音频切片全局时间恢复临时文件清理日志脱敏离线验证模型升快速回滚这些内容看起来和“模型算法”关系不大,却决定了一个 AI 模块究竟只能停留在 Demo,还是能长期运行在真实系统里。已经知道说了什么↓准确找到它什么时候说逐字稿点击回听搜索定位纪要复核会议资料追溯从产品界面上看,它可能只是用户点击一句话以后,播放器准确跳到了对应位置。

shxjnpl的博客 178

顺序表(Sequential List)详解:从数组到 C 语言实现

顺序表是用连续存储单元依次存储数据元素的线性结构,数组则是其最常见的实现方式,两者本质相同、仅视角不同——数组关注物理存储,顺序表关注逻辑关系。顺序表支持随机访问 $O(1)$,但插入删除需移动元素,效率为 $O(n)$。本文用 C 语言静态数组完整实现了顺序表的初始化、增删改查操作,帮助初学者在不涉及指针的前提下理解其核心原理,为后续学习动态顺序表打下基础。

Guangyu536的博客 299

C++】 定时器进阶:多层时间轮原理与任务迁移

单层时间轮虽然能够以较低的开销管理大量定时任务,但当任务延迟时间跨度很大时,要么需要大量槽位,要么需要额外维护轮数。多层时间轮通过将不同时间范围的任务放到不同层中,实现类似“秒针、分针、时针”的分管理。本文主要介绍多层时间轮为什么出现、各层如何划分、定时任务如何选择层,以及高层任务如何逐步迁移到低层并最终执行。

qq_64148519的博客 114

​​​​​​​第 4 节 如何用 C++ 设计一个 AI 推理 SDK 的核心接口

AI推理SDK核心接口应遵循“环境→模型→执行器→张量”四层抽象,采用不透明指针保证ABI稳定,以C API为底座、C++ API为封装,统一错误处理,并权衡张量、内存、异步、线程安全与插件扩展。参考ONNXRuntime、TensorRT、OpenVINO设计,实现易用、高性能、可扩展的工业接口。

Question_123456的博客 517

附录 C:AOSP 不在公共框架 API 中采用 Kotlin 的原因

以及源码。契约生命周期约束设备发布后会锁定某个 SDK 版本长达接近十年。每一条签名,必须兼容未来所有 kotlinc 版本、Kotlin 元数据格式变更、标准改动。内部 Kotlin 不受该限制:编译器签名变化可以通过下一次全量重编译抹平。二进制 ABI 映射差异Java 源码声明与 JVM 签名一一对应。Kotlin 一条源码会产出一组 JVM 产物:重载方法、名字混淆、伴生对象访问器、合成辅助函数、Continuation 参数、元数据块;组合结果完全依赖编译器。

dengfuma的专栏 723

DeepSeek Harness 系列(02):万物皆插件——Cordis 核心设计深度解读

dsh 的所有能力都建立在 Cordis 插件框架之上。这篇文章结合真实源码,深度解读 Cordis 五个核心机制:Plugin、Context、Service、Event、Effect——每个机制讲清楚「是什么、怎么用、为什么这么设计」。读完之后,dsh 的一切都会豁然开朗。

Cheson的专栏 526

实现pytorch resnet18功能(二,c++ cudnn)

/输出32*32*32-----------------------显然输入也是32*32*32。, _ci(ci), _co(co), _h(h), _w(w), _jjh(juanjihe), _pad(pading) {//int stride=2,juanjihe=1时,pading=0;原因是残差块中最后一步只实现了相加,并没有实现relu,所以这个架构中,所有残差块类都跟有relu!

ganggangwawa的博客 199

Tera Term 鸿蒙 PC 适配全记录:用 ArkUI 与 Native C++ 重建多协议终端工作流

开源鸿蒙PC社区推出Tera Term终端模拟器适配版本,基于ArkTS/ArkUI与C++ Native分层架构,实现TCP、Telnet、SSH及串口通信功能。项目在HarmonyOS PC真机上成功验证五类核心场景:应用启动、网络连接、双向输入输出、多会话独立管理与终端渲染。通过Node-API桥接前后端,保留原生终端语义,不依赖Win32兼容层,为嵌入式与运维用户提供稳定高效的跨平台终端体验。

稻草人 1万+

C++速通2

int a = 3;int b = 4;// 声明名字空间int main()int a = 1;// 1// ::表示全局作用域 2// 3// 4return 0;```一个函数使用virtual关键字修饰,就是虚函数,虚函数是函数覆盖的前提。在QtCreator中使用斜体字。```cpppublic:// 虚函数cout << "动物爱吃饭" << endl;

qq_63858767的博客 442

CCFCSP算法专栏:关于二分

left = mid + 1 # mid 可能是答案,但先往右探else:return left - 1 # 注意减 1为什么 return left - 1?因为这里的left是“第一个 > target 的位置”,所以最后一个 <= target 的位置是left - 1。场景区间循环条件更新方式返回值精确查找[l, r]l <= rl=mid+1r=mid-1循环内 return mid,否则 -1左边界(第一个 >= target)[l, r)l < rl=mid+1。

Chester_1999的博客 200

ARM64 Linux 6.10 内核驱动(14):drivers/base/platform.c:板设备主战场

本文详解 drivers/base/platform.c 在 QEMU virt + ARM64 平台下如何实现平台设备驱动模型。核心是将无自动注册能力的 MMIO 设备通过 platform_bus_init 建立伪总线,设备通过 platform_device_add 挂载至该总线,驱动则通过 __platform_driver_register 注册并绑定。匹配机制优先依赖 DT 的 compatible(of_match_table),其次为旧式 id_table 和名称匹配。platform_pr

T1mzhou的博客 181

【408数据结构 03】线性表与顺序表:C++手写SeqList

顺序表用一段地址连续的存储单元依次存放线性表元素。假设每个元素占个字节,第一个元素地址为LOC(a1),则第i这就是 408 常考的随机访问公式。下标从 0 还是 1 开始408 题目中位置i通常从 1 开始,代码里数组下标从 0 开始,注意转换。插入位置范围是容易漏掉length + 1,也就是表尾插入。删除位置范围是删除不能到length + 1。插入从后往前移动,删除从前往后移动方向搞反会覆盖数据。平均移动次数公式要记牢插入n/2,删除(n-1)/2。顺序表长度和容量不是一回事。

xiangyun61的博客 257

C++第一课

你暂时不用背它。

qq_56657939的博客 140
上一篇: 分析与理解消息反射机制
下一篇: 自身支持排序的CSortListCtrl
skyremember
博客等级 码龄20年 203粉丝 484原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值