本文由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

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 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进程通信之剪贴板相关推荐

  1. Windows进程通信——剪贴板

    1. 概述 1.1 介绍 剪贴板(Clipped Board)实质是Win32 API中一组用来传输数据的函数和消息,为Windows应用程序之间进行数据共享提供了一个中介,Windows已建立的剪切 ...

  2. 几种Windows进程通信

    32位Windows采用虚拟内存技术使每个进程虚拟4G内存,在逻辑上实现了对进程之间数据代码的分离与保护.那么相应的进程之间的通信也就有必要整理掌握一下. Windows进程间通讯的方法有很多:管道. ...

  3. Windows进程通信之PE文件共享节

    本文由danny发表于 http://blog.csdn.net/danny_share 说明:建议先下载本文配套工程,其中 SectionDLL.SectionMain工程.SectionASub工 ...

  4. Windows进程通信之共享内存通信(C++)

    首先是概念:https://baike.baidu.com/item/%E5%85%B1%E4%BA%AB%E5%86%85%E5%AD%98/2182364?fr=aladdin 这是比较官方的解释 ...

  5. windows进程通信 -- WM_COPYDATA消息

    WM_COPYDATA消息,在win32中用来进行进程间的数据传输. typedef struct tagCOPYDATASTRUCT { // cds DWORD dwData; DWORD cbD ...

  6. [转]WINDOW进程通信的几种方式

    windows进程通信的几种方式 1 文件映射 文件映射(Memory-Mapped Files)能使进程把文件内容当作进程地址区间一块内存那样来对待.因此,进程不必使用文件I/O操作,只需简单的指针 ...

  7. 【转载】进程间的通信之剪贴板方法实现源码

    1 源码无私奉献 2 3 void CClipboardDlg::OnBnClickedButton1() 4 { 5 // TODO: 在此添加控件通知处理程序代码 6 //打开剪贴板 7 if(O ...

  8. Windows下进程通信的几种方式介绍

    Windows下进程通信的几种方式 一.消息通信 所谓消息通信,就是指Windows发出的一个通知,告诉应用程序某个事情发生了.例如,单击鼠标.改变窗口尺寸.按下键盘上的一个键都会使Windows发送 ...

  9. 【免杀前置课——Windows编程】十、进程间通信(COPY_DATA通信,邮槽通信)、实现两进程通信(附代码)

    进程间通信 进程间通信 1.COPY_DATA方式 2.邮槽的方式 实现两进程通信 进程接收端 进程发送端 错误调试小技巧 进程间通信 每个进程都有自己独立的4G内存空间,彼此是不能直接互相访问的.如 ...

最新文章

  1. shell的各种运行模式?
  2. eyoucmsPHP企业网站内容管理系统
  3. Coding and Paper Letter(六)
  4. 小师妹学JVM之:JIT中的PrintAssembly续集
  5. Cookie的使用(js-cookie插件)
  6. Java static 静态代码块、代码块
  7. Linux文档阅读笔记-cut与sort的基本用法
  8. 半年之殇:困扰半年的MSP430的I2C总线问题在今天解决,发文总结
  9. Swing 显示良好JPanel保存为图片
  10. 【Linux系统管理】10 Shell 基础概念篇
  11. 指数函数和正弦函数相乘
  12. 货拉拉 Android 动态资源管理系统原理与实践(下)
  13. 华为数通笔记-AAA
  14. c语言中常量有何作用,正确的C语言常量是什么?
  15. 【自动化办公】python批量替换word中的内容
  16. 5个wordpress资源网站推荐
  17. HashMap扩容时的rehash方法中(e.hash oldCap) == 0算法推导
  18. c语言函数求1到n的k次方和
  19. 阿里最受追捧的「中高级技术核心」,助我拿下裹裹offer,附面经
  20. 外键约束的作用以及如何创建外键约束

热门文章

  1. Java排序算法——插入排序(Insertion Sort)
  2. 滑动平均滤波算法——MATLAB实现
  3. kali工具fping的简单使用之扫描IP
  4. 流量运营分析ESOP
  5. 超全的2022届数字IC面经汇总来了~
  6. 鸿蒙定时重启软件,电脑定时自动关机怎么设置?学到就是赚到,赶紧学学!
  7. MyDocument exe病毒查杀方法
  8. TI CC3200 IAR 开发环境搭建
  9. web应用测试的具体流程(等保测评相关)
  10. bp神经网络预测未来数据,bp神经网络数据预处理