Windows进程通信之剪贴板
本文由danny发表于http://blog.csdn.net/danny_share
前面两篇废话了这么多,本文开始上干货。
本文从剪贴板概念、剪贴板内容监听,普通数据类型,打开剪贴板,读剪贴板,写剪贴板、API汇总、他山之石、通过剪贴板实现进程间通信和总结共10个方面来较为深入地探讨Windows剪贴板的机制和使用。
说明:建议先下载本文配套工程,其中
(1) ClipBoardDemo工程主要演示剪贴板相关API使用
(2) MainProcess工程、ASubProcess工程、BSubProcess工程分别用于演示本文第九部分进程间通信的主进程和两个子进程
下载地址:http://download.csdn.net/detail/danny_share/7703279
一.剪贴板概念
剪贴板是系统级的堆空间,且任何一个应用程序都具备访问权,主要涉及复制(Ctrl+C),剪切(Ctrl+X)、粘贴(Ctrl+V)、清空和监听五个操作。
二.剪贴板内容监听
1.开始监听
【a】SetClipboardViewer将窗口加入监听链
【b】一旦剪贴板内容变化,系统会向监听链中的第一个注册监听的窗口发送WM_DRAWCLIPBOARD消息。为什么说是第一个呢?参见“消息链顺序”
【c】通过实验发现,如果新拷贝的内容和原来一样,系统还是会出发消息,说明普通的复制操作不管内容相不相同,都是直接覆盖原来剪贴板中的数据的
2.处理消息
(1)WM_CHANGECBCHAIN
【a】除自己以外,其他进程加入了监听链,或者退出了监听链,系统就会发出这个消息
【b】这里只说明监听者发生了变化,并不表示剪贴板中内容发生变化。
【c】如果存在下一个监听进程,那么需要将该消息发送给下一个监听者
否则下一个监听者将收不到该消息
【d】对于MFC,可通过重载OnChangeCbChain函数实现
(2)WM_DRAWCLIPBOARD
【a】进程首次运行时或者是剪贴板内容发生变化时,进程都会收到这个消息
【b】如果存在下一个监听进程,那么需要将该消息发送给下一个监听者
否则下一个监听者将收不到该消息
【c】对于MFC,可通过重载OnDrawClipboard函数实现
3.停止监听
【a】使用ChangeClipboardChain()函数停止监听
【b】本质是发送WM_CHANGECBCHAIN消息,且将自己从监听链中去除
【c】其他监听进程将会收到WM_CHANGECBCHAIN消息
4.消息链顺序
【a】刚才一直在说当剪贴板内容变化时,进程将会收到WM_DRAWCLIPBOARD消息,然后该进程处理完以后,需向下个监听者发送该消息,那下个监听者是谁呢?
【b】我们在WM_DRAWCLIPBOARD消息处理函数OnDrawClipboard中closeClipboard后sleepl两秒后弹窗
【c】然后拷贝ClipBoardOne.exe三份,依次启动,我们分别取名A、B和C
【d】当剪贴板内容变化时,进程弹出剪贴板变化的顺序也是A、B、C,说明加入监听的顺序和收到消息的顺序是一致的。
三.数据类型
分类 |
ID |
格式 |
说明 |
标准 |
1 |
CF_BITMAP |
位图句柄 |
2 |
CF_DIB |
||
3 |
CF_DIBV5 |
包含BITMAPV5HEADER结构且跟着位图颜色空间和位图数据 |
|
4 |
CF_DIF |
||
5 |
CF_DSPBITMAP |
||
6 |
CF_DSPENHMETAFILE |
||
7 |
CF_DSPMETAFILEPICT |
||
8 |
CF_DSPTEXT |
||
9 |
CF_ENHMETAFILE |
||
10 |
CF_GDIOBJFIRST |
||
11 |
CF_GDIOBJLAST |
||
12 |
CF_HDROP |
拖放服务,文件拷贝也是这个类型 |
|
13 |
CF_LOCALE |
||
14 |
CF_METAFILEPICT |
||
15 |
CF_OEMTEXT |
在窗口中执行DOS一起使用剪贴板 |
|
16 |
CF_OWNERDISPLAY |
||
17 |
CF_PALETTE |
调色板句柄,通常和CF_DIB配合 |
|
18 |
CF_PENDATA |
||
19 |
CF_PRIVATEFIRST |
||
20 |
CF_PRIIVATELAST |
||
21 |
CF_RIFF |
资源交换文件格式的多媒体数据 |
|
22 |
CF_SYLK |
||
23 |
CF_TEXT |
ANSI文本 |
|
24 |
CF_TIFF |
TIFF格式图片数据内存块 |
|
25 |
CF_UNICODETEXT |
Unicode格式字符 |
|
26 |
CF_WAVE |
wave文件 |
|
注册 |
27 |
自定义数值 |
RegisterClipboardFormat自定义格式 |
私有 |
28 |
值范围 CF_PRIVATEFIRST CF_PRIVATELAST |
(1) 不需要向系统注册 (2) WM_DESTROYCLIPBOARD消息释放资源 |
多重 |
29 |
实质没有新定义格式 |
(1) CoutClipboardFormats查询格式总数 (2) 要返回具体格式,EnumClipboardFormats |
转换 |
30 |
(1) 当剪贴板中为格式A,而Windows需格式B,假如A转成B在左侧表格中,则系统默认转换 (2) 例如拷贝BMP文件时,由于BMP文件和系统调色板相关,所以最好格式为CF_DIB或CF_DIBVS,这样系统请求CF_BITMAP时,会自动使用调色板 |
四.打开剪贴板
(1)打开剪贴板之前,我们首先需要查看是否有其他应用正在使用剪贴板
(2)Windows提供了两个和查看拥有者相关的函数,分别是
GetClipboardOwner和GetOpenClipboardWindow
(3)GetClipboardOwner函数一般指上次存放数据的进程
(4)GetOpenClipboardWindow才是真正指当前获得剪贴板操作权的进程
(5)因此OpenClipboard前需调用GetOpenClipboardWindow函数,当返回句柄为空才行。
HWND myHwnd=GetSafeHwnd();//当前窗口进程
CWnd* ownerCHwnd=GetClipboardOwner();//一般指上次使用读写剪贴板的进程
CWnd* wndUserCHwnd=GetOpenClipboardWindow();//这才是真正的当前取得剪贴板使用权的进程
(6)根据前面的分析,进程打开剪贴板使用完毕以后,一定要调用CloseClipboard交出使用权,否则其他进程将无法取得剪贴板使用权
五.读剪贴板
(1)读剪贴板之前,首先还是打开剪贴板,读完以后关闭剪贴板
(2)假如想读取特定格式的数据,则可先用IsClipboardFormatAvailable来判断该格式是否存在。
(3)读文本CF_TEXT
<pre name="code" class="cpp">void*tempData=NULL;if(this->myClip->readData(CBType_Text,&tempData)){char* test=(char*)tempData;CStringinfo="Read Success , the content is:\r\n";info+=test;MessageBox(info,MB_OK);}else{MessageBox("Read Failed",MB_OK);}
(1)读图片CF_BITMAP
这里复制时不是复制图片文件,而是类似于在浏览器里右击复制图片
这里我复制了百度首页的logo。原理跟读取CF_TEXT一样,先读出一个HBITMAP句柄,再保存成图片即可
(2)读文件CF_HDROP
【a】本质上是得到文件路径
【b】打开剪贴板前需
UINTuDropEffect=RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT);
注意,Windows系统在ShlObj.h文件中
<pre name="code" class="cpp">#define CFSTR_PREFERREDDROPEFFECT TEXT("Preferred DropEffect")
【c】通过DragQueryFile函数可遍历剪贴板中文件,先传入0得到一个UNIT指,而后将该值迭代传参即可
(3)枚举剪切板中所有格式
存在两个枚举格式相关函数。分别是
<pre name="code" class="cpp">CountClipboardFormats() //此函数是获取格式总数
EnumClipboardFormats() //首次传0,以后用返回值迭代,即可遍历
六.写剪贴板
都比较简单,详见ClipBoardDemo工程
1.写文本CF_TEXT
2.写图片CF_BITMAP
3.写文件CF_HDROP
(1)对于拷贝,标志位是DROPEFFECT_COPY
(2)对于剪切,标志位是DROPEFFECT_MOVE
4.写多种格式
原理一样,只要写数据的时候不清空,多种格式写完以后再关闭剪贴板即可。
七.API汇总
(1)MFC提供了两组本质是一样的API,一组属于全局函数,一组属于窗口的成员函数(全局函数中需传窗口句柄的函数都被MFC封装了一层)。
(2)我们在窗口button里调用this-> OpenClipboard(),然后debug F11进入观察一下
<pre name="code" class="cpp">_AFXWIN_INLINE BOOLCWnd::OpenClipboard(){ASSERT(::IsWindow(m_hWnd)); return::OpenClipboard(m_hWnd); }
所以窗口成员剪贴板函数实质上调用的仍是全局函数,不过在内部多传了个句柄
(3)所以我们自己封装时,也可借此思路,内部多传个句柄封装。
ID |
函数 |
功能 |
1 |
SetClipboardViewer |
监听剪贴板 |
2 |
ChangeClipboardChain |
取消监听剪贴板 |
3 |
OnChangeCbChain |
剪贴板监听对象变化事件 |
4 |
OnDrawClipboard |
剪贴板内容变化事件 |
5 |
AddClipboardFormatListener |
添加某种格式的监听 |
6 |
RemoveClipboardFormatListener |
去除某种格式的监听 |
7 |
GetClipboardSequenceNumber |
获取在监听链中的顺序 |
8 |
EnumClipboardFormats |
枚举格式 |
9 |
CountClipboardFormats |
获取格式数量 |
10 |
GetPriorityClipboardFormat |
获取最符合的格式 |
11 |
GetClipboardFormatName |
获取非预定义的格式名称 |
12 |
IsClipboardFormatAvailable |
判断剪贴板中是否含有目标格式 |
13 |
RegisterClipboardFormat |
注册格式 |
14 |
GetUpdatedClipboardFormats |
获取当前支持的格式 |
15 |
OpenClickborder |
打开剪贴板,取得使用权 |
16 |
CloseClipboard |
关闭剪贴板,释放使用权 |
17 |
GetClipboardViewer |
获取剪贴板监听链的第一个窗口句柄 |
18 |
GetOpenClipboardWindow |
获取当前正在使用剪贴板的窗口句柄 |
19 |
GetClipboardOwner |
一般指上次向剪贴板写数据的对象 |
20 |
清空剪贴板 |
|
21 |
SetClipboardData |
写数据,第二个参数NULL表示延迟写入 |
22 |
GetClipboardData |
读数据 |
八.他山之石
1.迅雷
当我们复制文本时,如果剪贴板中包含链接地址,且是迅雷支持的下载格式,则迅雷会弹出下载对话框,这其实就是使用了剪贴板的监听原理
2.QQ
我们在QQ聊天界面里粘贴时,如果之前复制的是文字,则文字内容复制到了输入框中,如果之前复制的是文件,则文件直接发给对方好友了。其原理就是在粘贴的时候判断当前剪贴板中到底是什么内容,然后做出不同的处理
九.通过剪贴板实现进程间通信
(1)需求设计
好,原理说了这么多,现在来实现我们在“Windows进程间通信之目录”中所说的需求,我们下面简化需求,形成下面的模型:
【a】 一个主进程启动两个子进程
【b】 主进程可随时发送不同的命令给不同的子进程
【c】 子进程收到命令以后去做相应的操作,完成后发送响应给主进程
【d】 主进程收到子进程的响应后,再做相应处理
使用剪贴板的思维来实现上述需求:
【a】 三个进程都监听剪贴板
【b】 设计协议格式,使得进程读取剪贴板中数据即可区分相关参数
我们简化,在剪贴板中放置纯文本数据来传输数据,协议格式如下:
(2)实现
贴上主进程关键代码,其他详见工程
voidCMainProcessDlg::analysisClipboard()
{CBProtocolStructtempData;m_protocol->getProtocolData(tempData);if(tempData.receiver=='M'){if(tempData.sender='A'){if(tempData.command=='0'){CStringinfo="A Sub Process send Work response to MainProcess\r\nthe Data is ";info+=tempData.data;MessageBox(info,"Info",MB_OK);}else{if(tempData.command=='1'){CStringinfo="A Sub Process send Close response toMain Process\r\nthe Data is ";info+=tempData.data;MessageBox(info,"Info",MB_OK);}}}else{if(tempData.sender='B'){if(tempData.command=='0'){CStringinfo="B Sub Process send Work response to MainProcess\r\nthe Data is ";info+=tempData.data;MessageBox(info,"Info",MB_OK);}else{if(tempData.command=='1'){CStringinfo="B Sub Process send Close response toMain Process\r\nthe Data is ";info+=tempData.data;MessageBox(info,"Info",MB_OK);}}}}}
}
voidCMainProcessDlg::OnDrawClipboard()
{CDialog::OnDrawClipboard();if(NULL!=this->nextWindow){::SendMessage(nextWindow,WM_DRAWCLIPBOARD, 0, 0);}analysisClipboard();
}
另外贴上子进程A的关键函数
void CASubProcessDlg::analysisClipboard()
{CBProtocolStruct tempData;m_protocol->getProtocolData(tempData);if(tempData.receiver=='A'){if(tempData.sender='M'){if(tempData.command=='0'){ CBProtocolStruct responseData;responseData.sender='A';responseData.receiver='M';responseData.command='0';m_protocol->setProtocolData(responseData);MessageBox("A receive Work command","Info",MB_OK);}else{if(tempData.command=='1'){CBProtocolStruct responseData;responseData.sender='A';responseData.receiver='M';responseData.command='1';responseData.data='Z';m_protocol->setProtocolData(responseData);MessageBox("A receive Close command","Info",MB_OK);SendMessage(WM_CLOSE,0,0);}}}}
}
void CASubProcessDlg::OnDrawClipboard()
{CDialog::OnDrawClipboard();if(NULL!=this->nextWindow){::SendMessage(nextWindow, WM_DRAWCLIPBOARD, 0, 0);}analysisClipboard();}
(1) 时序
我们以主进程向A发送MA00为例,来解析整个命令时序
【a】 第2步中A感知变化后第3步是B感知变化,而不是A处理变化的是因为,在OnDrawClipboard中是先SendMessage,然后再处理的。
void CASubProcessDlg::OnDrawClipboard()
{CDialog::OnDrawClipboard();if(NULL!=this->nextWindow){::SendMessage(nextWindow,WM_DRAWCLIPBOARD, 0, 0);}analysisClipboard();
}
【b】剪贴板内容变化后,感知顺序都是先A再B再M,是因为,我们启动程序的顺序就是A、B和M(参见本文第二部分监听剪贴板内容消息链顺序一节)
十.总结
优点:
(1) 原理简单,上手很快
(2) 支持不同的数据格式
缺点:
(1)后台虽有两个子进程,但由于剪贴板权限只有一个,无法实现两个子进程同时操作;
(2)剪贴板更适合传输数据,虽提供剪贴板监听机制达到传输命令和时序的目的,但需自己额外设计数据格式,比较繁琐,不像Windows多线程拥有众多基础设施。所以适合主子进程间命令交互不频繁、数据交互较多的应用。
Danny
2014年8月1号
于天津河西
Windows进程通信之剪贴板相关推荐
- Windows进程通信——剪贴板
1. 概述 1.1 介绍 剪贴板(Clipped Board)实质是Win32 API中一组用来传输数据的函数和消息,为Windows应用程序之间进行数据共享提供了一个中介,Windows已建立的剪切 ...
- 几种Windows进程通信
32位Windows采用虚拟内存技术使每个进程虚拟4G内存,在逻辑上实现了对进程之间数据代码的分离与保护.那么相应的进程之间的通信也就有必要整理掌握一下. Windows进程间通讯的方法有很多:管道. ...
- Windows进程通信之PE文件共享节
本文由danny发表于 http://blog.csdn.net/danny_share 说明:建议先下载本文配套工程,其中 SectionDLL.SectionMain工程.SectionASub工 ...
- Windows进程通信之共享内存通信(C++)
首先是概念:https://baike.baidu.com/item/%E5%85%B1%E4%BA%AB%E5%86%85%E5%AD%98/2182364?fr=aladdin 这是比较官方的解释 ...
- windows进程通信 -- WM_COPYDATA消息
WM_COPYDATA消息,在win32中用来进行进程间的数据传输. typedef struct tagCOPYDATASTRUCT { // cds DWORD dwData; DWORD cbD ...
- [转]WINDOW进程通信的几种方式
windows进程通信的几种方式 1 文件映射 文件映射(Memory-Mapped Files)能使进程把文件内容当作进程地址区间一块内存那样来对待.因此,进程不必使用文件I/O操作,只需简单的指针 ...
- 【转载】进程间的通信之剪贴板方法实现源码
1 源码无私奉献 2 3 void CClipboardDlg::OnBnClickedButton1() 4 { 5 // TODO: 在此添加控件通知处理程序代码 6 //打开剪贴板 7 if(O ...
- Windows下进程通信的几种方式介绍
Windows下进程通信的几种方式 一.消息通信 所谓消息通信,就是指Windows发出的一个通知,告诉应用程序某个事情发生了.例如,单击鼠标.改变窗口尺寸.按下键盘上的一个键都会使Windows发送 ...
- 【免杀前置课——Windows编程】十、进程间通信(COPY_DATA通信,邮槽通信)、实现两进程通信(附代码)
进程间通信 进程间通信 1.COPY_DATA方式 2.邮槽的方式 实现两进程通信 进程接收端 进程发送端 错误调试小技巧 进程间通信 每个进程都有自己独立的4G内存空间,彼此是不能直接互相访问的.如 ...
最新文章
- shell的各种运行模式?
- eyoucmsPHP企业网站内容管理系统
- Coding and Paper Letter(六)
- 小师妹学JVM之:JIT中的PrintAssembly续集
- Cookie的使用(js-cookie插件)
- Java static 静态代码块、代码块
- Linux文档阅读笔记-cut与sort的基本用法
- 半年之殇:困扰半年的MSP430的I2C总线问题在今天解决,发文总结
- Swing 显示良好JPanel保存为图片
- 【Linux系统管理】10 Shell 基础概念篇
- 指数函数和正弦函数相乘
- 货拉拉 Android 动态资源管理系统原理与实践(下)
- 华为数通笔记-AAA
- c语言中常量有何作用,正确的C语言常量是什么?
- 【自动化办公】python批量替换word中的内容
- 5个wordpress资源网站推荐
- HashMap扩容时的rehash方法中(e.hash oldCap) == 0算法推导
- c语言函数求1到n的k次方和
- 阿里最受追捧的「中高级技术核心」,助我拿下裹裹offer,附面经
- 外键约束的作用以及如何创建外键约束