Windows进程通信——剪贴板

1. 概述

1.1 介绍

剪贴板(Clipped Board)实质是Win32 API中一组用来传输数据的函数和消息,为Windows应用程序之间进行数据共享提供了一个中介,Windows已建立的剪切(复制)-粘贴的机制为不同应用程序之间共享不同格式数据提供了一条捷径。当用户在应用程序中执行剪切或复制操作时,应用程序把选取的数据用一种或多种格式放在剪贴板上。然后任何其它应用程序都可以从剪贴板上拾取数据,从给定格式中选择适合自己的格式。

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

剪贴板是一个非常松散的交换媒介,可以支持任何数据格式,每一格式由一无符号整数标识,对标准(预定义)剪贴板格式,该值是Win32 API定义的常量;对非标准格式可以使用Register Clipboard Format函数注册为新的剪贴板格式。利用剪贴板进行交换的数据只需在数据格式上一致或都可以转化为某种格式就行。但剪贴板只能在基于Windows的程序中使用,不能在网络上使用。

1.2 剪贴板操作API

要想在剪贴板中写入数据就需要在全局的内存空间中去分配空间,至于为甚不用常用的new、malloc函数原因是:

使用 malloc 或者 new 有一个问题,那就是,用这个两个东西来分配的内存空间都是在当前进程的私有地址空间上分配内存,也就是它们两个东东所分配的内存空间为进程私有地址空间所有,并不为所有进程所共享,而且任何进程之间都是不能访问对方的私有地址空间的,你把剪贴板中的内存分配到了你当前进程的私有地址空间上,而其他进程又不能访问你这个进程的私有地址空间,因而就不能访问到数据了。

至于进程访问这些内存空间的效率的话是new和malloc效率更高一些,这是因为
(1)malloc 或者 new 分配的内存,是在进程的私有地址空间,所以与进程隔得很近,程序要过去拿数据效率很高
(2)而 GlobalAlloc 函数分配的内存是在全局的内存空间中的,进程需要访问这些内存就需要经过内存映射之类的操作,因而和进程隔得很远,程序要过去拿数据效率就相对较低

1. 全局分配内存,使用GlobalAlloc()函数,它的函数原型为

  1. HGLOBAL WINAPI GlobalAlloc(
  2. _In_ UINT uFlags,
  3. _In_ SIZE_T dwBytes
  4. );

分配全局内存,这块内存的管理相对new和malloc函数得到的内存耗时,原因在前面已经做介绍。

参数 uFlags 
用来指定分配内存的方式,它可取下面的一些参数
GHND
即 GMEM_MOVEABLE 和 GMEM_ZEROINIT 的组合
GMEM_FIXED
分配一块固定内存,返回值是一个指针
GMEM_MOVEABLE
分配一块可移动内存
GMEM_ZEROINIT
初始化内存的内容为 0
GPTR
即 GMEM_FIXED 和 GMEM_ZEROINIT 的组合

参数dwBytes 
指定分配的字节数

2. 为了得到分配内存的指针,需要调用GlobalLock()函数,它的函数原型为

  1. LPVOID WINAPI GlobalLock(
  2. _In_ HGLOBAL hMem
  3. );
函数的作用是对全局内存对象加锁(加锁的意思就是你已经在使用这块全局内存了,别的程序就不能再使用这块全局内存了),然后返回该对象内存块第一个字节的指针。
参数hMem

由 GlobalAlloc 函数返回的数据对象句柄

3. 由于GlobalLock()函数对内存对象加锁了,需要解锁就需要调用GlobalUnlock()函数,函数原型为

  1. BOOL WINAPI GlobalUnlock(
  2. _In_ HGLOBAL hMem
  3. );

函数就是用来对全局内存对象解锁

参数hMem

由 GlobalAlloc 函数返回的数据对象句柄

4. 对于初始的内存大小,若是后来程序需要更大的内存的时候需要调用GlobalReAlloc()函数,他的函数原型为

  1. HGLOBAL WINAPI GlobalReAlloc(
  2. _In_ HGLOBAL hMem,
  3. _In_ SIZE_T dwBytes,
  4. _In_ UINT uFlags
  5. );

该函数为再分配函数,即在原有的数据对象 hMem 上,为其扩大内存空间
参数 hMem
代表由 GlobalAlloc 函数返回的数据对象句柄
参数 dwBytes
指定需要重新分配的内存的大小
参数 uFlags
指定分配的方式(可以参考 GlobalAlloc 函数)

5. 为了知道全局分配的内存的大小,需要调用GlobalSize()函数,它的函数原型为

  1. SIZE_T WINAPI GlobalSize(
  2. _In_ HGLOBAL hMem
  3. );
该函数用来返回内存块的大小
参数 hMem
代表由 GlobalAlloc 函数返回的数据对象句柄

6. 在使用完成之后,要释放内存空间需要调用 GlobalFree()函数,它的函数原型为

  1. HGLOBAL WINAPI GlobalFree(
  2. _In_ HGLOBAL hMem
  3. );
函数释放全局内存块
参数 hMem 
代表由 GlobalAlloc 函数返回的数据对象句柄

在介绍完成内存分配函数之后,就涉及到对剪贴板的操作API,这部分的API涉及到剪贴板的开关函数OpenClipboard()和CloseClipboard()、剪贴板读写数据函数SetClipboardData()和GetClipboardData()、清空剪贴板操作EmptyClipboard()、判断剪贴板是否有数据 IsClipboardFormatAvaliable() 。下面是这些API函数的详细解释

1. 打开剪贴板函数OpenClipboard(),它的函数原型为

  1. BOOL WINAPI OpenClipboard(
  2. _In_opt_ HWND hWndNewOwner
  3. );
参数hWndNewOwner 
指向一个与之关联的窗口句柄,即代表是这个窗口打开剪贴板,如果这个参数设置为 NULL 的话,则以当前的任务或者说是进程来打开剪贴板。
返回值
如果打开剪贴板成功,则该函数返回非 0 值,如果其他程序已经打开了剪贴板,那么当前这个程序就无法再打开剪贴板了,所以会致使打开剪贴板失败(别的进程在使用),从而该函数返回 0 值。
需要说明的是:如果某个程序已经打开了剪贴板,那么其他应用程序将不能修改剪贴板,直到打开了剪贴板的这个程序调用了 CloseClipboard 函数,并且只有在调用了 EmptyClipboard 函数之后,打开剪贴板的当前窗口才能拥有剪贴板,注意是必须要在调用了 EmptyClipboard 函数之后才能拥有剪贴板

2. 关闭剪贴板函数CloseClipboard(),它的函数原型为

BOOL WINAPI CloseClipboard(void);
如果某个进程打开了剪贴板,则在这个进程没有调用 CloseClipboard 函数关闭剪贴板句柄之前,其他进程都是无法打开剪贴板的,所以我们每次使用完剪贴板之后都应该关闭剪贴板。
注意,这里的关闭剪贴板并不代表当前打开剪贴板的这个程序失去了对剪贴板的所有权,只有在别的程序调用了 EmptyClipboard 函数之后,当前的这个程序才会失去对剪贴板的所有权,那个调用 EmptyClipboard 函数的程序才能拥有剪贴板。

3. 剪贴板写数据函数SetClipboardData(),它的函数原型为

  1. HANDLE WINAPI SetClipboardData(
  2. _In_ UINT uFormat,
  3. _In_opt_ HANDLE hMem
  4. );
参数 uFormat
用来指定要放到剪贴板上的数据的格式,比如常见的有 CF_BITMAP ,CF_TEXT (其他格式可以参考 MSDN)
参数 hMem 
用来指定具有指定格式的数据的句柄,该参数可以是 NULL ,如果该参数为 NULL 则表明直到有程序对剪贴板中的数据进行请求时,该程序(也就是拥有剪贴板所有权的进程)才会将数据复制到剪贴板中,也就是提供指定剪贴板格式的数据,这也就是就是延迟提交技术,这种技术在拷贝大型文件时用处很大。

4. 剪贴板读数据函数GetClipboardData(),它的函数原型为

  1. HANDLE WINAPI GetClipboardData(
  2. _In_ UINT uFormat
  3. );
函数根据 uFormat 指定的格式,返回一个以指定格式存在于剪贴板中的剪贴板对象的句柄

5. 清空剪贴板操作EmptyClipboard(),它的函数原型为

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

6. 判断剪贴板是否有数据 IsClipboardFormatAvaliable() ,它的函数原型为

  1. BOOL WINAPI IsClipboardFormatAvailable(
  2. _In_ UINT format
  3. );
函数用来判断剪贴板上的数据格式是否为 format 指定的格式

2. 编码实现

  1. #include <iostream>
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <windows.h>
  5. #include <string>
  6. using std:: cout;
  7. using std:: endl;
  8. //将数据放入到剪贴板中
  9. bool my_SetClipBoardData(std::string);
  10. //将数据从剪贴板中读取出来
  11. bool my_GetClipBoardData();
  12. int _tmain( int argc, _TCHAR* argv[])
  13. {
  14. system( "color f0");
  15. //设置数据
  16. if (!my_SetClipBoardData( "my clipboard test")) return -1;
  17. Sleep( 1000); //睡眠1s
  18. //读取数据
  19. my_GetClipBoardData();
  20. system( "pause");
  21. return 0;
  22. }
  23. //************************************************************************
  24. // 函数名称: SetClipBoardData
  25. // 访问权限: public
  26. // 创建日期: 2017/06/07
  27. // 创 建 人:
  28. // 函数说明: 将数据放入到剪贴板中
  29. // 函数参数: std::string 需要放入剪贴板中数据
  30. // 返 回 值: bool
  31. //************************************************************************
  32. bool my_SetClipBoardData(std::string str)
  33. {
  34. if (OpenClipboard( NULL)) //传递的参数为NULL,表示用当前进程的窗口句柄
  35. {
  36. //下面是全局内存空间操作,在全局空间上分配内存空间
  37. HGLOBAL m_hglobal = ::GlobalAlloc(GHND, str.length()+ 1); //分配全局内存,设置内存类型,大小为字符串长度+1
  38. if (INVALID_HANDLE_VALUE == m_hglobal)
  39. {
  40. cout << "分配全局空间错误" << endl;
  41. return false;
  42. }
  43. char* m_pBase = nullptr;
  44. m_pBase = ( char*)::GlobalLock(m_hglobal); //得到内存空间的首地址,加锁
  45. if (m_pBase == nullptr)
  46. {
  47. cout << "获得全局空间首地址,失败" << endl;
  48. return false;
  49. }
  50. strcpy(m_pBase, str.c_str()); //复制字符串到地址空间中去,类似于内存映射中的操作
  51. ::GlobalUnlock(m_hglobal); //解锁
  52. //下面是剪贴板操作
  53. if (!::EmptyClipboard()) //清空剪贴板操作,也代表当前进程拥有剪贴板了
  54. {
  55. cout << "清空剪贴板操作,失败" << endl;
  56. return false;
  57. }
  58. HANDLE data_handle = ::SetClipboardData(CF_TEXT, m_hglobal); //将数据放置到剪贴板里面去
  59. if (INVALID_HANDLE_VALUE == data_handle)
  60. {
  61. cout << "将数据放置到剪贴板里面去,错误" << endl;
  62. return false;
  63. }
  64. ::CloseClipboard(); //关闭剪贴板
  65. cout << "写入到剪贴板中的数据:" << str << endl;
  66. }
  67. return true;
  68. }
  69. //************************************************************************
  70. // 函数名称: GetClipBoardData
  71. // 访问权限: public
  72. // 创建日期: 2017/06/07
  73. // 创 建 人:
  74. // 函数说明: 将数据从剪贴板中读取出来
  75. // 返 回 值: bool
  76. //************************************************************************
  77. bool my_GetClipBoardData()
  78. {
  79. if (OpenClipboard( NULL)) //传递的参数为NULL,表示用当前进程的窗口句柄
  80. {
  81. //判断当前剪贴板中是否有值,且为规定的CF_TEXT类型
  82. if (::IsClipboardFormatAvailable(CF_TEXT))
  83. {
  84. char* m_pBase = nullptr;
  85. //从剪贴板中获取格式为 CF_TEXT 的数据
  86. HGLOBAL hGlobalClip = ::GetClipboardData(CF_TEXT);
  87. m_pBase = ( char *)::GlobalLock(hGlobalClip); //得到内存空间的首地址,加锁
  88. GlobalUnlock(hGlobalClip); //解锁
  89. cout << "从剪贴板中获取到数据:" << m_pBase << endl << endl;
  90. }
  91. else
  92. return false;
  93. }
  94. else
  95. return false;
  96. return true;
  97. }

另外,在这篇文章中给出了剪贴板的延迟提交技术,在复制大型文件的时候很有用

猜你喜欢

转载自blog.csdn.net/bruce135lee/article/details/80922497
今日推荐