当一个进程启动后,操作系统为其分配了4GB的私有地址空间,位于同一个进程中的多个线程共享一个地址空间,因此线程之间的通信非常简单。然而,由于每个进程锁拥有的4GB地址空间都是私有的,一个进程不能访问另一个进程地址空间中的数据。
进程通信的四种方式:剪贴板、匿名管道、命令通道和邮槽。
剪贴板:
例如,在word文档中复制一份数据后,可以在其他文件中粘贴,这个过程就是在不同进程之间利用剪贴板实现的一次数据传输。剪贴板实际上是系统维护管理的一块内存区域,当在一个进程中复制数据时,是将这个数据放到该块内存区域中,当在另一个进程中粘贴数据时,是从该块内存区域中取出数据,然后显示在窗口上。
在把数据放置到剪贴板之前,需要先打开剪贴板,如果某个程序已经打开了剪贴板,其他应用程序将不能修改剪贴板,直到前者调用了CloseClipBoard函数,并且只有调用了EmptyClipBoard函数之后,打开剪贴板的当前窗口才拥有剪贴板。EmptyClipBoard函数将清空剪贴板,并释放剪贴板中数据的句柄,然后剪贴板的所有权分配给当前打开剪贴板的窗口。因为剪贴板是所有进程都可以访问的,所以我们在编写这个ClipBoard进程使用剪贴板之前,可能已经有其他进程把数据放置到了剪贴板上,那么在该进程打开剪贴板之后,需要调用EmptyClipBoard函数,清空剪贴板,释放剪贴板上的数据句柄,并将剪贴板的所有权分配给当前打开剪贴板的窗口,之后,就可以向剪贴板中放置数据了。
OpenClipBoard(); 打开剪贴板,将剪贴板中的窗口句柄设置为本窗口;
CloseClipBoard(); 清空剪贴板中的数据,即清空指向数据的句柄。
EmptyClipBoard();
SetClipBoardData();
剪贴板延迟提交技术:
在数据提供进程创建了剪贴板数据后,一直到有其他进程获取剪贴板数据前,这些数据都要占据内存空间。如在剪贴板放置的数据量过大,就会浪费内存空间,降低对资源的利用率。为避免这种浪费,可以采取延迟提交(Delayed rendering)技术。即由数据提供进程先创建一个指定数据格式为空(NULL)剪贴板数据块,直到有其他进程需要数据或自身进程要终止运行时才提交真正的数据。延迟提交的拥有者进程需要做的工作是对WM_RENDERFORMAT、WM_DESTROYCLIPBOARD和WM_RENDERALFROMAT等剪贴板延迟提交消息的处理。
当另一个进程调用GetClipBoardData函数时,系统将会向延迟提交数据的剪贴板拥有者进程(EmptyClipBoard会使进程拥有剪贴板,并在此时设置窗口的句柄,发送这些消息的时候,就知道了要发送的目的窗口的窗口句柄)发送WM_RENDERFORMAT消息,剪贴板拥有者进程在此消息的响应函数中应使用响应的格式和实际数据句柄来调用SetClipBoardData函数,而不必再调用OpenClipBoard和EmptyClipBoard去打开和清空剪贴板了。在设置完数据也不必调用CloseClipBoard关闭剪贴板。如果有其他进程打开剪贴板并且调用了EmptyClipBoard函数去清空剪贴板的内容,接管剪贴板的拥有权时,系统将向延迟提交的剪贴板拥有者进程发送WM_DESTROYCLIPBOARD消息,以通知该进程剪贴板拥有权的丧失。而失去剪贴板拥有权的进程在接收到该消息后,则不会再向剪贴板提交数据,另外,在延迟提交进程在提交完要提交的数据后,也会收到该消息,如果延迟提交剪贴板拥有者进程将要终止,系统将会为其发送一条WM_RENDERALLFORMATS,通知打开并清除剪贴板内容,在调用SetClipBoardData设置各数据句柄后关闭剪贴板。
剪切板:系统维护的一个全局公共内存区域.每次只允许一个进程对其进行访问。
1.打开剪切板
Bool OpenClipboard(HWND hWndNewOwner);
指定关联到打开的剪切板的窗口句柄,传入NULL表示关联到当前任务。每次只允许一个进程打开并访问。
每打开一次就要关闭,否则其他进程无法访问剪切板。
2.清空剪切板
Bool EmptyClipboard(void)
写入前必须先清空,得到剪切板占有权
3.分配内存
HGLOBAL GlobalAlloc(UINT uFlags, SIZE_T dwBytes);
在堆上动态分配以字节为单位的内存区域。成功则指向该内存,失败NULL。参数:1.分配内存属性, 2.分配的大小
Win32内存管理没有提供一个单独的本地堆和全局堆,也就是说,在Win32平台下,已经没有本地堆和全局堆了。和其他内存管理函数相比,全局内存函数的运行速度稍微慢些,而且他们没有提供更多的特性,所以新的应用程序应该使用堆函数,然而,全局函数仍然与动态数据交换,以及剪贴板函数一起使用。本程序是利用剪贴板在进程间进行通信,因此还是需要使用GlobalAlloc这个函数。
4.锁定内存
LPVOID GlobalLock(HGLOBAL hMem);
此函数的作用是对全局内存对象进行加锁,然后返回该内存块第一个字节的指针。
锁定由GlobalAlloc分配的内存,并将内存对象的锁定计数器+1,成功返回指向内存对象起始地址的指针。失败NULL
系统为每个全局内存对象维护一个锁定计数器,初始为0,GlobalLock使计数器+1,GlobalUnLock计数器-1.一旦计数器值大于0,
这块内存区域将不允许被移动或删除,只有当为0时,才解除对这块内存的锁定。如果分配时GMEM_FIXED属性,计数器一直为0
5.设置剪切板
HANDLE SetClipboardData(UINT uFormat, HANDLE hMem);
执行成功,返回数据句柄,否则返回NULL
6.解除锁定
BOOL GlobalUnlock(HGLOBAL hMem);
将GlobalAlloc分配的属性为GMEM_MOVEABLE的内存对象计数器-1.
7.关闭剪切板
Bool CloseClipboard(void);
必须关闭剪切板其他进程才能使用剪切板,且关闭后当前进程就不能写入数据。
8.判断剪贴板中的数据是否是指定格式的数据
IsClipboardFormatAvaiable
判断剪贴板中的数据是否是我们想要的格式的数据,是的话,在获取剪贴板中的数据。
9.获取剪切板数据
HANDLE GetClipboardData(UINT uFormat);
执行成功,返回数据句柄,否则返回NULL数据格式,指定格式的数据的句柄
一:UINT uFormate格式说明:标准剪贴簿数据格式
Windows支持不同的预先定义剪贴簿格式, 这些格式在WINUSER.H定义成以CF为前缀的标识符。
■三种能够储存在剪贴簿上的文字数据型态:
①CF_TEXT 以NULL结尾的ANSI字符集字符串。它在每行末尾包含一个carriage return和linefeed字符,这是最简单的剪贴簿数据格式。
②CF_OEMTEXT 含有文字数据(与CF_TEXT类似)的内存块。但是它使用的是OEM字符集。
③CF_UNICODETEXT 含有Unicode文字的内存块。与CF_TEXT类似,它在每一行的末尾包含一个carriage return和linefeed字符,以及一个NULL字符(两个0字节)以表示数据结束。CF_UNICODETEXT只支援Windows NT。
■两种附加的剪贴簿格式、但是它们不需要以NULL结尾,因为格式已经定义了数据的结尾。
①CF_SYLK 包含Microsoft 「符号连结」数据格式的整体内存块。这种格式用在Microsoft的Multiplan、Chart和Excel程序之间交换数据,它是一种ASCII码格式。
②CF_DIF 包含数据交换格式(DIF)之数据的整体内存块。用于把数据送到VisiCalc电子表格程序中。这也是一种ASCII码格式
■下面三种剪贴簿格式与位图有关。所谓位图就是数据位的矩形数组
①CF_BITMAP 与设备相关的位图格式。位图是通过位图句柄传送给剪贴簿的。
②CF_DIB 定义一个设备无关位图的内存块。
③CF_PALETTE 调色盘句柄。
■下面是两个metafile格式、metafile就是一个以二进制格式储存的画图命令集
①CF_METAFILEPICT 以旧的metafile格式存放的「图片」 。
②CF_ENHMETAFILE 增强型metafile(32位Windows支持的)句柄。
■最后介绍几个混合型的剪贴簿格式:
CF_PENDATA与Windows的笔式输入扩充功能联合使用。
CF_WAVE声音(波形)文件。
CF_RIFF使用资源交换文件格式(Resource Interchange File Format)的多媒体数据。
CF_HDROP与拖放服务相关的文件列表。
二:UINT uFlags格式说明:内存属性
GMEM_FIXED
分配一块固定的内存区域,不允许系统移动,这时返回值是一个指针。
GMEM_MOVEABLE
分配一块可移动的内存区域,实际上内存块在物理内存中是不可移动的,这里的可移动指的是在应用程序的默认逻辑堆内可以移动。返回值是内存对象的句柄。可以通过调研GlobalLock()函数将一个句柄转化为一个指针,这个标志不能喝GMEM_FIXED同时使用
GMEM_ZEROINT
初始化内存对象为全0,如果不用这个标志,内存对象将为不确定的内容
GHND
GMEM_MOVEABLE和GMEM_ZEROINT块标志联合使用,即可移动同时初始化为0
GPTR
GMEM_FIXED和GMEM_ZEROINT标志联合使用,即不可移动同时初始化为0
1 void CMFC_TabCtrlDlg::SetClipBoardData_(CString strText) 2 { 3 /* 4 OpenClipboard打开剪切板:指定关联到打开的剪切板的窗口句柄,传入NULL表示关联到当前任务。每次只允许一 5 个进程打开并访问。每打开一次就要关闭,否则其他进程无法访问剪切板。 6 EmptyClipboard清空剪切板:写入前必须先清空,得到占有权 7 */ 8 if (::OpenClipboard(m_hWnd) &&::EmptyClipboard()) 9 { 10 //根据环境变量获取数据长度 11 size_t cbStr = (strText.GetLength() + 1) * sizeof(TCHAR); 12 13 //在堆上动态分配以字节为单位的全局内存区域。成功则指向该内存,失败NULL。参数:1.分配内存属性,2.大小 14 HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, cbStr); 15 16 if (hMem == NULL) 17 { 18 //关闭剪切板,释放剪切板所有权,关闭后就不能写入数据 19 CloseClipboard(); 20 return; 21 } 22 23 //锁定由GlobalAlloc分配的内存,并将内存对象的锁定计数器+1;成功返回指向内存对象起始地址的指针。失败NULL 24 LPTSTR lpDest = (LPTSTR)GlobalLock(hMem); 25 /* 26 系统为每个全局内存对象维护一个锁定计数器,初始为0,GlobalLock使计数器+1, 27 */ 28 29 //拷贝数据到剪贴板内存。 30 memcpy_s(lpDest, cbStr, strText.LockBuffer(), cbStr); 31 strText.UnlockBuffer(); 32 33 //解除内存锁定,将属性为GMEM_MOVEABLE的内存对象计数器-1. 34 GlobalUnlock(hMem); 35 /* 36 GlobalUnLock计数器-1.一旦计数器值大于0,这块内存区域将不允许被移动或删除,只 37 有当为0时,才解除对这块内存的锁定。如果分配时GMEM_FIXED属性,计数器一直为0 38 39 */ 40 41 //根据环境变量设置数据格式 42 UINT uiFormat = (sizeof(TCHAR) == sizeof(WCHAR))?CF_UNICODETEXT:CF_TEXT; 43 44 //设置数据到剪贴板。执行成功,返回数据句柄,否则返回NULL 45 if(SetClipboardData(uiFormat, hMem) == NULL); 46 { 47 CloseClipboard(); 48 return; 49 } 50 51 CloseClipboard(); 52 } 53 }
2.从剪切板内存获取数据
1 void CMFC_TabCtrlDlg::GetClipBoardData_(void) 2 { 3 //if (IsClipboardFormatAvailable(CF_UNICODETEXT)) //判断某种格式的数据是否可用 4 if(::OpenClipboard(m_hWnd)) 5 { 6 UINT uiFormat = (sizeof(TCHAR) == sizeof(WCHAR))?CF_UNICODETEXT:CF_TEXT; 7 8 ////执行成功,返回数据句柄,否则返回NULL。参数:1.数据格式,2.指定格式的数据的句柄 9 HGLOBAL hMem = GetClipboardData(uiFormat); 10 11 if (hMem != NULL) 12 { 13 //获取UNICODE的字符串。 14 LPCTSTR lpStr = (LPCTSTR)GlobalLock(hMem); 15 if (lpStr != NULL) 16 { 17 SetDlgItemText(IDC_EDIT1, lpStr); 18 } 19 GlobalUnlock(hMem); 20 } 21 } 22 CloseClipboard(); 23 }