MFC进程通信--剪贴板

进程通信——剪贴板

一、简介

1.1 定义

剪贴板是由操作系统维护的一块内存区域,这块内存区域不属于任何单独的进程,但是每一个进程又都可以访问这块内存区域,而实质上当在一个进程中复制数据时,就是将数据放到该内存区域中,而当在另一个进程中粘贴数据时,则是从该块内存区域中取出数据。 

Windows剪贴板是一种比较简单同时也是开销比较小的进程间通信方式。使用剪贴板传递数据使开发人员不必过多地考虑数据存储的共享空间,简化了通信过程。

例如:如果在物理内存中划分出一块内存,这一块内存不为任何的进程所私有,但是任何的进程又都可以访问这块内存,那么 进程 A 就可以往这块内存中存放数据 Data ,然后进程 B 也是可以访问这块内存,从而 进程 B 就可以访问到数据 Data 了,以上这种思路就是通过剪贴板实现了进程 A 和 进程 B 之间的通信。 

1.2 剪贴板通信机制

Windows系统支持剪贴板IPC的基本机制是由系统预留的一块全局共享内存,可用于被各进程暂时存储数据。写入进程首先创建一个全局内存块,并将数据写到该内存块;接受数据的进程通过剪贴板机制获取此内存块的句柄,并完成对该内存块数据的读取。

Windows系统在USER32.dll中为剪贴板提供了一组API函数和多种消息。还包括多种数据格式,使读取进程能够以原有格式读取剪贴板中的数据。 

1.3 剪贴板函数

介绍几个基本的剪贴板函数。更多关于剪贴板的函数可以在网上搜集。

1) 剪贴板的打开 – OpenClipboard

要想把数据放置到剪贴板中,则必须先打开剪贴板,而这是通过 OpenClipboard 成员函数实现:

BOOL  OpenClipboard(HWND  hWndNewOwner );

第一个参数 hWndNewOwner 指向一个与之关联的窗口句柄,即代表是这个窗口打开剪贴板,如果这个参数设置为 NULL 的话,则以当前的任务或者说是进程来打开剪贴板。

如果打开剪贴板成功,则该函数返回非 0 值,如果其他程序已经打开了剪贴板,

那么当前这个程序就无法再打开剪贴板了,所以会致使打开剪贴板失败,从而该函数返回 0 值。

其实这也好理解,剪贴板总共才那么一块内存区域,进程 A 要往里面写数据,进程 B 又要往里面写数据,那样会导致乱套,解决这个乱套的办法就是,如果进程 A 正在往剪贴板里面写数据(可以理解为 进程 A 打开剪贴板了),那么 进程 B 就不能往剪贴板里头写数据了,既然要让 进程 B 不能往剪贴板中写数据了,那我就让 进程 B 打开剪贴板失败不就得了。所以如果某个程序已经打开了剪贴板,那么其他应用程序将不能修改剪贴板,直到打开了剪贴板的这个程序调用了 CloseClipboard 函数,并且只有在调用了 EmptyClipboard 函数之后,打开剪贴板的当前窗口才能拥有剪贴板,注意是必须要在调用了 EmptyClipboard 函数之后才能拥有剪贴板。             

2) 剪贴板的清空 - EmptyClipboard

这个函数将清空剪贴板,并释放剪贴板中数据的句柄,然后将剪贴板的所有权分配给当前打开剪贴板的窗口,

因为剪贴板是所有进程都可以访问的,所以应用程序在使用这个剪贴板时,有可能已经有其他的应用程序把数据放置到了剪贴板上,因此该进程打开剪贴板之后,就需要调用 EmptyClipboard 函数来清空剪贴板,释放剪贴板中存放的数据的句柄,并将剪贴板的所有权分配给当前的进程,这样做之后当前打开这个剪贴板的程序就拥有了剪贴板的所有权,因此这个程序就可以往剪贴板上放置数据了。

BOOL EmptyClipboard(void);

3) 剪贴板的关闭 - CloseClipboard

如果某个进程打开了剪贴板,则在这个进程没有调用 CloseClipboard 函数关闭剪贴板句柄之前,其他进程都是无法打开剪贴板的,所以我们每次使用完剪贴板之后都应该关闭剪贴板。

注意,这里的关闭剪贴板并不代表当前打开剪贴板的这个程序失去了对剪贴板的所有权,

只有在别的程序调用了 EmptyClipboard 函数之后,当前的这个程序才会失去对剪贴板的所有权,而那个调用 EmptyClipboard 函数的程序才能拥有剪贴板。

BOOL CloseClipboard(void);               

4) 数据发送到剪贴板 - SetClipboardData

可以通过 SetClipboardData 函数来实现往剪贴板中放置数据,这个函数以指定的剪贴板格式向剪贴板中放置数据。

HANDLE  SetClipboardData(UINT uFormat,  HANDLE hMem ); 

第一个参数 uFormat 用来指定要放到剪贴板上的数据的格式,比如常见的有 CF_BITMAP ,CF_TEXT ,CF_DIB 等等(其他格式可以参考 MSDN)。

第二个参数 hMem 用来指定具有指定格式的数据的句柄,该参数可以是 NULL ,

如果该参数为 NULL 则表明直到有程序对剪贴板中的数据进行请求时,

该程序(也就是拥有剪贴板所有权的进程)才会将数据复制到剪贴板中,也就是提供指定剪贴板格式的数据,上面提及的就是延迟提交技术,这个延迟提交技术将会在后面做详细的介绍          

5) 剪贴板中数据格式判断 – IsClipboardFormatAvaliable

BOOL  IsClipboardFormatAvailable( UINT format ); 

该函数用来判断剪贴板上的数据格式是否为 format 指定的格式。 

6) 剪贴板中数据接收 - GetClipboardData

HANDLE  GetClipboardData( UINT uFormat ); 

该函数根据 uFormat 指定的格式,返回一个以指定格式存在于剪贴板中的剪贴板对象的句柄。 

1.4 全局内存分配 – HGLOBAL

剪贴板中的内存从何而来?

从上面的介绍中可以知道剪贴板其实就是一块内存,那么这块内存是什么时候分配的呢?难不成说一开机,操作系统就给剪贴板分配个几 M的内存的吧?这种方式也太逊色了,程序要往剪贴板中放置的数据,而事先又不晓得数据长度,所以,一开机操作系统究竟要给剪贴板分配多少内存呢?很明显,太不动态了,不可取。

要想动态的话,那有一种方案,就是当我的程序要往剪贴板中放置数据的时候来确定要分配给剪贴板的内存的大小,很明显,既然我都知道要往剪贴板中放置那些数据了,自然我也就知道了这些数据的长度,那么我就可以以这个数据长度来给剪贴板分配内存了,这是很动态的了吧,所以这种方案是可取的,但关键是,当我们以前在程序中分配内存的时候,都是使用的标准 C运行库中的 malloc或者是 C++中的 new关键字,(当然分配内存还有很多其他的函数,比如就有内核中的执行体中就有很多分配内存的函数,这里不讨论),而使用 malloc或者 new有一个问题,那就是,用这个两个东西来分配的内存空间都是在当前进程的私有地址空间上分配内存,也就是它们两个东东所分配的内存空间为进程私有地址空间所有,并不为所有进程所共享,上面提到了,任何进程之间都是不能访问对方的私有地址空间的,你把剪贴板中的内存分配到了你当前进程的私有地址空间上,而其他进程又不能访问你这个进程的私有地址空间,那怎么能够访问剪贴板呢?

很明显,不能使用 malloc和 new关键字来分配内存给剪贴板。

我们应该要使用另外一个特殊一点的函数来分配内存给剪贴板,这个特殊函数所分配的内存不能够是在进程的私有地址空间上分配,而是要在全局地址空间上分配内存,这样这个函数所分配的内存才能够被所有的进程所共享,这样,剪贴板中的数据就可以被其他的进程所访问了。                     

GlobalAlloc 函数

GlobalAlloc函数是从堆上分配指定数目的字节,

与其他的内存管理函数相比,全局内存函数的运行速度会稍微慢一些(等下会解释为什么会慢),但是全局函数支持动态数据交换,同时,其分配的内存也不为任何一个进程所私有,而是由操作系统来管理这块内存,所以用在给剪贴板分配内存空间是很适合的。

这里有读者可能会问:

为什么我们在自己的应用程序中不使用 GlobalAlloc函数来分配内存,而是要使用 malloc或者 new 来实现?

其实,这个也只用稍微想想就知道了,你想啊,使用 malloc或者 new分配的内存是在进程的私有地址空间上分配的,这片私有地址空间都是归这个进程所拥有,所管理的,自然,在以后对这块内存的读写会快很多的,而全局内存不属于这个进程,你下次要去访问全局内存的时候,还得通过映射转换,这样肯定是运行效率低下一些了,简单点就可以这样理解,你使用 malloc或者 new分配的内存和你的进程隔得很近,程序要过去拿数据 - 得,很近吧,

而是用 GlobalAlloc函数分配的内存和你的进程隔得很远,程序要过去拿数据 - 太远了,耗时。应用程序在调用了 SetClipboardData函数之后,系统就拥有了 hMem参数所标识的数据对象,该应用程序可以读取这个数据对象,但是在应用程序调用 CloseClipboard函数之前,它都是不能释放该对象的句柄的,或者锁定这个句柄,如果 hMem标识一个内存对象,那么这个对象必须是利用 GMEM_MOVEABLE标识调用 GlobalAlloc函数为其分配内存的。

HGLOBAL  WINAPI  GlobalAlloc( UINT uFlags,   SIZE_T dwBytes );

第一个参数 uFlags用来指定分配内存的方式。其取值如下列表所示(但是在剪贴板的使用中,由于要实现动态数据交换,所以必须得使用 GHND或者 GMEM_MOVEABLE):

描述

GHND

即 GMEM_MOVEABLE和 GMEM_ZEROINIT的组合。

GMEM_FIXED

分配一块固定内存,返回值是一个指针。

GMEM_MOVEABLE

分配一块可移动内存。

GMEM_ZEROINIT

初始化内存的内容为 0

GPTR

即 GMEM_FIXED和 GMEM_ZEROINIT的组合。

第二个参数 dwBytes用来指定分配的字节数。

GlobalReAlloc 函数

HGLOBAL WINAPI GlobalReAlloc(HGLOBAL hMem, SIZE_T dwBytes, UINT uFlags);

该函数为再分配函数,即在原有的数据对象 hMem 上,为其扩大内存空间。

第一个参数 hMem 代表由 GlobalAlloc 函数返回的数据对象句柄。

第二个参数 dwBytes 指定需要重新分配的内存的大小。

第三个参数 uFlags 指定分配的方式(可以参考 GlobalAlloc 函数)。                 

GlobalSize 函数

SIZE_T  WINAPI  GlobalSize( HGLOBAL  hMem );

该函数用来返回内存块的大小。

第一个参数 hMem 代表由 GlobalAlloc 函数返回的数据对象句柄。                

GlobalLock 函数

LPVOID  WINAPI  GlobalLock( HGLOBAL  hMem );

该函数的作用是对全局内存对象加锁,然后返回该对象内存块第一个字节的指针。

第一个参数 hMem 代表由 GlobalAlloc 函数返回的数据对象句柄。            

GlobalUnLock 函数

BOOL  WINAPI  GlobalUnlock( HGLOBAL  hMem );

通过上面的 GlobalLock 函数可以获得这块全局内存的访问权,加锁的意思就是你已经在使用这块全局内存了,别的程序就不能再使用这块全局内存了,而如果你一直不解锁,那也不是个事啊,别的程序将会一直都使用不了这块全局内存,那还叫全局内存干吗啊?所以这个函数就是用来对全局内存对象解锁。

第一个参数 hMem 代表由 GlobalAlloc 函数返回的数据对象句柄。                  

GlobalFree 函数

HGLOBAL  WINAPI  GlobalFree( HGLOBAL  hMem );

该函数释放全局内存块。

第一个参数 hMem 代表由 GlobalAlloc 函数返回的数据对象句柄。 

1.5 剪贴板通信类型

剪贴板通信有5种基本情况,

1.文本剪贴板

文本剪贴板是具有CF_TEXT格式的剪贴板。在文本剪贴板中传递的数据是不带任何格式信息的ASCII字符串。由于可以把任何数据格式化为ASCII字符串信息。事实上文本剪贴板可以用于存放任何数据。

2.位图剪贴板

位图剪贴板是具有CF_BITMAP格式的剪贴板。使用SetClipboardData时需要提供位图句柄,而使用GetClipboardData时返回的是位图的句柄。 

3.自定义格式

除了使用预定义的剪贴板数据格式外,也可以在程序中使用自定义的数据格式。使用RegisterClipboardFormat可以注册自定义的数据格式,输入自定义的格式名称,返回系统分配格式整型标识。RegisterClipboardFormat函数定义如下:

UINT RegisterClipboardFormat(

LPCTSTR  lpszFormat //自定义格式名称

);

具体的使用方式和文本剪贴板类似。

4.延迟提交

什么是延迟提交技术?

当把数据放入剪贴板中时,一般来说要制作一份数据的副本,也就是要分配全局内存,然后将数据再复制一份,然后再将包含这份副本的内存块句柄传递给剪贴板,对于小数据量来说,这个没什么,但是对于大数据量的话,就有问题了,你一使用剪贴板,就往里面复制个什么几百 MB 的数据,那这个数据在剪贴板中的数据被其他数据取代之前都是存放在内存中的啊,如果我就复制了一个 500MB 的数据,然后我一直不再复制其他的东西,那么这个 500MB 的数据就会一直驻留在内存中,太可怕了 !!!太浪费内存的使用效率了 !!!

为了解决上面这个问题,就需要通过使用延迟提交技术来避免内存的浪费,当使用延迟提交技术时,实际上,直到另一个程序需要数据时,程序才会提供这份数据,也就是,其实一开始 程序 A 并不往剪贴板中存放真实的数据,而只是告诉剪贴板,我往里面放了数据(其实数据还没有放进去),而后,如果有其他的 程序 B 访问了剪贴板中的数据,也就是执行了“粘贴”操作,那么此时操作系统就会去检查数据是不是真正的存放在了剪贴板中,

如果剪贴板中存放了数据,那么直接把数据送出去就可以了(这就没有使用延迟提交技术了),而如果剪贴板中没有数据,那么 Windows 就会给上次往剪贴板中存放数据(尽管没有存放实际的数据)的程序,也就是 程序 A发送消息,而后,我们的 程序 A 就可以再次调用 SetClipboardData 来将真实的数据放入到剪贴板中了,这样就是延迟提交技术了。

要实现延迟提交技术,则在 程序 A 中不应该将数据句柄传送给 Windows ,而是在 SetClipboardData 调用中使用 NULL。然后当另外一个 程序 B 调用 GetClipboardData 函数时,Windows 就会检查这种格式的数据在剪贴板中的句柄是否为 NULL ,如果为 NULL ,则 Windows 会给程序 A发送一个消息,从而请求到数据的实际句柄,这个数据的实际句柄是 程序 A 在响应消息的处理函数中重新调用 SetClipboardData 来提供的。

延迟提交技术中涉及的三个消息:

下面提及的 程序 A 代表剪贴板当前拥有者,也就是 程序 A 负责往剪贴板中写入数据,

而 程序 B 则代表从剪贴板中读取出数据,其没有对剪贴板的所有权。

WM_RENDERFORMAT :

当 程序 B 调用 GetClipboardData 时,Windows 将会给 程序 A 的窗口过程发送这个消息,其中 wParam 参数的值是所要求的格式。在处理这个消息时,程序 A 就不再需要打开或者清空剪贴板了,也就是不需要再次调用 OpenClipboard 和 EmptyClipboard 函数了,为什么不需要再次调用这两个函数?

这是因为,我们一开始的时候已经调用了这两个函数(如果一开始没有调用的话,窗口根本就不会接受到这个消息),而此举已经告诉操作系统剪贴板已经归我所有了,而且里面的数据已经被清空了,剪贴板所有权都归我了,那还去打开个鬼啊,不是浪费嘛?在处理这个消息时,应该为 wParam 所指定的格式创建一个全局内存块,然后再把数据传递到这个全局内存块中,并要正确的格式和数据句柄再一次调用 SetClipboardData 函数。也就是需要将数据真实的复制到剪贴板中了。

WM_RENDERALLFORAMTS :

如果 程序 A 在它自己仍然是剪贴板所有者的时候就要终止运行,并且剪贴板上仍然包含着该 程序 A 用 SetClipboardData 所设置的 NULL 数据句柄(延迟提交技术),也就是 程序 A 当前还是剪贴板的所有者,但是用户又单击了关闭窗口,而剪贴板中还没有真实的数据存在(因为使用了延迟提交技术),即数据还没有被提交给剪贴板,程序 A 就要死了,则此时 程 序 A 的窗口过程将接收到这个消息,这个消息的一般处理为打开剪贴板,并且清空剪贴板,然后把数据加载到内存中,并为每种格式调用 SetClipboardData ,然后再关闭剪贴板即可。

WM_DESTROYCLIPBOARD :

当在 程序 B 中调用 EmptyClipboard 时,Windows 将会给 程序 A 的窗口过程发送这个消息。即通知 程序 A 其已不再是剪贴板的拥有者了。 

延迟提交可以充分利用内存资源,当数据量比较大时尤为重要。剪贴板拥有者进程在调用SetClipboardData时设置参数为NULL,表明并不递交实际数据。当其他进程使用GetClipboardData读取剪贴板数据时,会向延时提交进程发送WM_RENDERALLFORAMTS消息。而延时提交进程正是在响应WM_RENDERALLFORAMTS消息时提交实际数据。

如果在其他进程使用EmptyClipboard函数清空剪贴板内容,系统将向原剪贴板拥有者进程发送 WM_DESTROYCLIPBOARD消息。以通知该进程对剪贴板拥有权丧失。如果延时提交剪贴板拥有者进程将要终止,系统将向其发送WM_RENDERALLFORAMTS消息,通知其打开并清除剪贴板中的内容。

5.多项数据

设置剪贴板数据之前首先需要使用EmptyClipboard清空剪贴板,而不能追加数据。但可以在打开剪贴板后连续多次调用SetClipboardData,为剪贴板设置多项数据。 

二、示例 

2.1 Win32控制台App

新建一个Win32工程,命名为 ProgressClipboard。在源文件ProgressClipboard.cpp中添加下面的代码:

 
  1. #include "stdafx.h"

  2. #include <Windows.h>

  3. #include <iostream>

  4. using namespace std;

  5.  
  6. const char * pStrData = "shufac---processclipboardtest";

  7.  
  8. void SetClipBoardData();

  9.  
  10. void GetClipBoardData();

  11.  
  12. int _tmain(int argc, _TCHAR* argv[])

  13. {

  14. SetClipBoardData();

  15.  
  16. GetClipBoardData();

  17.  
  18. system("pause");

  19. return 0;

  20. }

  21.  
  22. void SetClipBoardData()

  23. {

  24. //将 OpenClipboard 函数的参数指定为 NULL,表明为当前进程打开剪贴板

  25. if(OpenClipboard(NULL))

  26. {

  27. char * pDataBuf;

  28. //全局内存对象

  29. HGLOBAL hGlobalClip;

  30. //给全局内存对象分配全局内存

  31. hGlobalClip = GlobalAlloc(GHND, strlen(pStrData) + 1);

  32. //通过给全局内存对象加锁获得对全局内存块的引用

  33. pDataBuf = (char *)GlobalLock(hGlobalClip);

  34. strcpy(pDataBuf, pStrData);

  35. //使用完全局内存块后需要对全局内存块解锁

  36. GlobalUnlock(hGlobalClip);

  37. //清空剪贴板

  38. EmptyClipboard();

  39. //设置剪贴板数据,这里直接将数据放到了剪贴板中,而没有使用延迟提交技术

  40. SetClipboardData(CF_TEXT, hGlobalClip);

  41. //关闭剪贴板

  42. CloseClipboard();

  43. cout<<"设置剪贴板为: "<<pStrData<<endl<<endl;

  44. }

  45. }

  46. void GetClipBoardData()

  47. {

  48. if(OpenClipboard(NULL))

  49. {

  50. //判断剪贴板中的数据格式是否为 CF_TEXT

  51. if(IsClipboardFormatAvailable(CF_TEXT))

  52. {

  53. char* pDataBuf;

  54. HGLOBAL hGlobalClip;

  55. //从剪贴板中获取格式为 CF_TEXT 的数据

  56. hGlobalClip = GetClipboardData(CF_TEXT);

  57. pDataBuf = (char *)GlobalLock(hGlobalClip);

  58. GlobalUnlock(hGlobalClip);

  59. cout<<"从剪贴板中获取到数据: "<<pDataBuf<<endl<<endl;

  60. }

  61. CloseClipboard();

  62. }

  63. }

运行结果:

 

注:程序运行后,剪贴板上的内容“shufac---processclipboardtest”,可以打开一个文本文件,粘贴剪贴板上的内容,验证一下。

2.2 MFC示例程序

项目结构:

 

界面:

 

项目------类导向-------添加 3 个消息处理:

 

头文件消息映射以及变量声明: 

 
  1. public:

  2. afx_msg void OnDestroyClipboard();

  3. afx_msg void OnRenderAllFormats();

  4. afx_msg void OnRenderFormat(UINT nFormat);

  5. //发送数据消息响应函数

  6. afx_msg void OnBnClickedWrite();

  7. //读取数据消息响应

  8. afx_msg void OnBnClickedRead();

  9. //存储发送的内容(文本)

  10. CString m_strWrite;

  11. //存储接收的内容(文本)

  12. CString m_strRead;

  13. 消息映射:

  14. void CMFCClipboardProcTestDlg::DoDataExchange(CDataExchange* pDX)

  15. {

  16. CDialogEx::DoDataExchange(pDX);

  17. DDX_Text(pDX, IDC_EDIT_WRITE, m_strWrite);

  18. DDX_Text(pDX, IDC_EDIT_READ, m_strRead);

  19. }

  20.  
  21. BEGIN_MESSAGE_MAP(CMFCClipboardProcTestDlg, CDialogEx)

  22. ON_WM_SYSCOMMAND()

  23. ON_WM_PAINT()

  24. ON_WM_QUERYDRAGICON()

  25. ON_WM_DESTROYCLIPBOARD()

  26. ON_WM_RENDERALLFORMATS()

  27. ON_WM_RENDERFORMAT()

  28. ON_BN_CLICKED(IDC_BUTTON_WRITE, &CMFCClipboardProcTestDlg::OnBnClickedWrite)

  29. ON_BN_CLICKED(IDC_BUTTON_READ, &CMFCClipboardProcTestDlg::OnBnClickedRead)

  30. END_MESSAGE_MAP()

  31. 消息映射函数实现

  32. //WM_DESTROYCLIPBOARD 消息处理函数

  33. void CMFCClipboardProcTestDlg::OnDestroyClipboard()

  34. {

  35. CDialogEx::OnDestroyClipboard();

  36. //当有另外的程序调用 EmptyClipboard 时,//Windows 将向当前窗口过程发送 WM_DESTROYCLIPBOARD 消息

  37. MessageBox(TEXT("很抱歉 , 您已失去对剪贴板的拥有权 ..."),TEXT("提示"), MB_ICONINFORMATION);

  38. }

  39. //WM_RENDERALLFORMATS 消息处理函数

  40. void CMFCClipboardProcTestDlg::OnRenderAllFormats()

  41. {

  42. CDialogEx::OnRenderAllFormats();

  43. //当剪贴板中的数据句柄为当前程序所拥有,而当前程序又将被退出时,

  44. //Windows 给该程序窗口发送 WM_RENDERALLFORMATS 消息

  45. OpenClipboard();

  46. EmptyClipboard();

  47. CloseClipboard();

  48. }

  49. //WM_RENDERFORMAT 消息处理函数

  50. void CMFCClipboardProcTestDlg::OnRenderFormat(UINT nFormat)

  51. {

  52. //当有另外的程序访问剪贴板时

  53. //Windows 给该程序窗口过程发送 WM_RENDERFORMAT 消息

  54. int dataNum;

  55. int dataIndex;

  56. char * pDataBuf;

  57. HGLOBAL hGlobalClip;

  58. dataNum = this->m_strWrite.GetLength();

  59. hGlobalClip = GlobalAlloc(GHND, dataNum + 1);

  60. pDataBuf = (char *)GlobalLock(hGlobalClip);

  61. for(dataIndex=0;dataIndex<dataNum;dataIndex++)

  62. {

  63. pDataBuf[dataIndex] = this->m_strWrite.GetAt(dataIndex);

  64. }

  65. GlobalUnlock(hGlobalClip); //此时需要将有效数据写入到剪贴板中

  66. SetClipboardData(CF_TEXT, hGlobalClip);

  67.  
  68. CDialogEx::OnRenderFormat(nFormat);

  69. }

  70.  
  71. void CMFCClipboardProcTestDlg::OnBnClickedWrite()

  72. {

  73. UpdateData();

  74. if(this->m_strWrite.GetLength() > 0)

  75. {

  76. if(OpenClipboard())

  77. {

  78. EmptyClipboard();

  79. SetClipboardData(CF_TEXT, NULL);

  80. CloseClipboard();

  81. MessageBox(TEXT("恭喜您 , 设置剪贴板成功 ..."),TEXT("提示"), MB_ICONINFORMATION);

  82. }

  83. }

  84. }

  85.  
  86. void CMFCClipboardProcTestDlg::OnBnClickedRead()

  87. {

  88. if(OpenClipboard())

  89. {

  90. //判断剪贴板中的数据格式是否为 CF_TEXT

  91. if(IsClipboardFormatAvailable(CF_TEXT))

  92. {

  93. char * pDataBuf;

  94. HGLOBAL hGlobalClip;

  95. //从剪贴板中获取到指定格式的数据

  96. hGlobalClip = GetClipboardData(CF_TEXT);

  97. pDataBuf = (char *)GlobalLock(hGlobalClip);

  98. this->m_strRead = pDataBuf;

  99. GlobalUnlock(hGlobalClip);

  100. UpdateData(FALSE);

  101. }

  102. CloseClipboard();

  103. }

  104. }

运行结果:

首先在左侧的编辑框中编辑内容,点击“发送”,编辑框中的内容将会发送至剪贴板中(同样可以在文本文件中粘贴内容来验证)

 

读取剪贴板中的数据,并显示在右侧的编辑框中。 

 

测试当前进程失去剪贴板所有权:

首先单击当前程序设置好剪贴板中的数据(shufac),

然后打开一个记事本文件,在在其中输入一些数据(sasasa),然后选择这部分数据,按下复制,此时转至程序运行的界面就会发现 失去剪贴板所有权的提示,如下图所示: 

 

2.4结束语

上面介绍的是通过剪贴板来实现进程之间的通信,还是有很大的局限性,因为我们的剪贴板是位于本地机器上,所以,利用剪贴板还是无法实现本地进程与远程进程通信,当然要想实现本地进程和远程进程的通信,那也还是有办法的,在后续博文中再做介绍。

猜你喜欢

转载自blog.csdn.net/xueluowutong/article/details/81113666