本文由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:rn";
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 | EmptyClipboard | 清空剪贴板 |
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 MainProcessrnthe Data is ";
info+=tempData.data;
MessageBox(info,"Info",MB_OK);
}
else
{
if(tempData.command=='1')
{
CStringinfo="A Sub Process send Close response toMain Processrnthe 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 MainProcessrnthe Data is ";
info+=tempData.data;
MessageBox(info,"Info",MB_OK);
}
else
{
if(tempData.command=='1')
{
CStringinfo="B Sub Process send Close response toMain Processrnthe 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进程通信之剪贴板内容请搜索靠谱客的其他文章。
发表评论 取消回复