我是靠谱客的博主 难过星月,这篇文章主要介绍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: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进程通信之剪贴板内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(52)

评论列表共有 0 条评论

立即
投稿
返回
顶部