win32进程之间通信的方式

一 剪切板方式

剪切板是由系统创建一块公共内存用于进程之间共享数据,从而达到进程之间相互通信的目的。但是这种通信方式具有一定的盲目性,也就是发送者并不知道接受者进程会是哪一个。
剪切板通信,首先利用OpenClipboard打开并且独占系统剪切板,由于传递给这个函数的参数是一个窗口句柄。所以使得实际上真正执行进程间通信的是两个与窗口相关联的线程。这一点可以通过程序片段进行验证。当属于同一个窗口句柄的线程在一次调用OpenClipboard之后,对剪切板进行操作,而其他的线程当中则必须再次调用OpenClipboard函数才可以。不过在调用OpenClipboard之后需要对应的调用CloseClipboard,不然整个系统都不能使用剪切板功能,直到程序结束或者调用CloseClipboard函数。
在OpenClipboard函数之后,就可以操作剪切板的数据了。不过为了避免和之前的数据发生冲突,需要调用EmptyClipboard将原有的剪切板数据清空。否则,其他的进程将不能收到我们的剪切板数据。
接下来就需要进行内存分配,由于剪切板只接受内存句柄,而我们的内存操作函数却是以指针的形式进行的,所以需要首先利用GlobalAlloc分配内存,当然也可以分配其他的内存,然后将GlobalAlloc函数返回的内存句柄传递给GlobalLock——将句柄转换为可以操作的指针,在进行内存操作之后利用GlobalUnlock取消指针操作。
然后利用SetClipboardData设置内存的数据格式,并且将内存句柄也作为参数传进去,最后不要忘了调用CloseClipboard函数
发送端:

if(OpenClipboard()) { CString str; HANDLE hClip; char *pBuf; EmptyClipboard(); GetDlgItemText(IDC_EDIT_SEND,str); hClip=GlobalAlloc(GMEM_MOVEABLE,str.GetLength()+1); pBuf=(char*)GlobalLock(hClip); strcpy(pBuf,str); GlobalUnlock(hClip); SetClipboardData(CF_TEXT,hClip); CloseClipboard(); }

接收端:
if(OpenClipboard()) { if(IsClipboardFormatAvailable(CF_TEXT)) { HANDLE hClip; char *pBuf; hClip=GetClipboardData(CF_TEXT); pBuf=(char*)GlobalLock(hClip); GlobalUnlock(hClip); SetDlgItemText(IDC_EDIT_RECV,pBuf); CloseClipboard(); } else { CloseClipboard(); } }

二 MailSlot通信
MailSlot是windows特有的进程之间通信的过程。起点是由某一个进程创建的MailSlot开始,这个进程担当的是接受者身份。首先它调用CreateMailslot函数创建一个有名的MailSlot。这个函数原型如下所示:
CreateMailslot ( _In_LPCWSTR lpName,//特定字符串组成的MailSlot名称,\\.\mailslot\自定义的MailSlot名称 _In_DWORD nMaxMessageSize,//消息的长度限制,当这个参数为0的时候表示消息可以无限长 _In_DWORD lReadTimeout,//消息等待读取超时的时间 _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes //MailSlot的属性 );

调用这个函数之后,MailSlot就可以和文件一样读写了,程序小片段如下:
接收方:

HANDLE hMailslot; hMailslot=CreateMailslot("\\\\.\\mailslot\\MyMailslot",0, MAILSLOT_WAIT_FOREVER,NULL); if(INVALID_HANDLE_VALUE=https://www.it610.com/article/=hMailslot) { MessageBox("创建油槽失败!"); return; } char buf[100]; DWORD dwRead; if(!ReadFile(hMailslot,buf,100,&dwRead,NULL)) { MessageBox("读取数据失败!"); CloseHandle(hMailslot); return; } MessageBox(buf); CloseHandle(hMailslot);

发送方:
HANDLE hMailslot; hMailslot=CreateFile("\\\\.\\mailslot\\MyMailslot",GENERIC_WRITE, FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if(INVALID_HANDLE_VALUE=https://www.it610.com/article/=hMailslot) { MessageBox("打开油槽失败!"); return; } char buf[MAX_PATH]; DWORD dwWrite; if(!WriteFile(hMailslot,buf,strlen(buf)+1,&dwWrite,NULL)) { MessageBox("写入数据失败!"); CloseHandle(hMailslot); return; } CloseHandle(hMailslot);


三 命名管道
为了使用命名管道,和MailSlot类似。第一步需要调用CreateNamePipe函数创建一个命名管道。唯一需要注意的是这个函数的第一参数名称类似于MailSlot,也需要特定的名称组织方式。必须是\\.\pipe\自定义管道名称。当创建命名管道设置管道的属性为异步访问的时候,那么在不需要设置等待时间。创建命名管道之后,就需要等到客户端进程来连接。这个等待由ConnectNamedPipe函数完成。不过在这之前需要创建一个OVERLAPPED结构体,并且设置这个结构体的hEvent成员变量,以等待连接事件的激发。然后可以通过WaitForSingleObject函数等待事件被激发。到此,服务端的命名管道创建基本结束。
客户端只需要调用函数WaitNamedPipe等待服务端就绪就可以了。这个函数类似于网络编程里面的connect函数。经过上面的处理之后,整个命名管道就可以像文件一样进行读写。
服务端:

hPipe=CreateNamedPipe("\\\\.\\pipe\\MyPipe", PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, 0,1,1024,1024,0,NULL); if(INVALID_HANDLE_VALUE=https://www.it610.com/article/=hPipe) { MessageBox("创建命名管道失败!"); hPipe=NULL; return; } HANDLE hEvent; hEvent=CreateEvent(NULL,TRUE,FALSE,NULL); if(!hEvent) { MessageBox("创建事件对象失败!"); CloseHandle(hPipe); hPipe=NULL; return; } OVERLAPPED ovlap; ZeroMemory(&ovlap,sizeof(OVERLAPPED)); ovlap.hEvent=hEvent; if(!ConnectNamedPipe(hPipe,&ovlap)) { if(ERROR_IO_PENDING!=GetLastError()) { MessageBox("等待客户端连接失败!"); CloseHandle(hPipe); CloseHandle(hEvent); hPipe=NULL; return; } } if(WAIT_FAILED==WaitForSingleObject(hEvent,INFINITE)) { MessageBox("等待对象失败!"); CloseHandle(hPipe); CloseHandle(hEvent); hPipe=NULL; return; } CloseHandle(hEvent);

客户端:
if(!WaitNamedPipe("\\\\.\\pipe\\MyPipe",NMPWAIT_WAIT_FOREVER)) { MessageBox("当前没有可利用的命名管道实例!"); return; } hPipe=CreateFile("\\\\.\\pipe\\MyPipe",GENERIC_READ | GENERIC_WRITE, 0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if(INVALID_HANDLE_VALUE=https://www.it610.com/article/=hPipe) { MessageBox("打开命名管道失败!"); hPipe=NULL; return; } 读写过程是一样的,如下所示: if(!ReadFile(hPipe,buf,100,&dwRead,NULL)) { MessageBox("读取数据失败!"); return; } if(!WriteFile(hPipe,buf,strlen(buf)+1,&dwWrite,NULL)) { MessageBox("写入数据失败!"); return; }

四 匿名管道
匿名管道的创建时调用CreatePipe函数实现的,不过在调用这个函数之前需要设置SECURITY_ATTRIBUTES结构体的成员变量bInheritHandle为真,这样在创建子线程的时候才能够将创建的匿名管道传递给子进程。然后设置子进程创建的属性STARTUPINFO,其中必须设置标准输入输出文件句柄以及错误输出句柄,同时还要设置属性为使用标准句柄。然后在子进程当中获得标准的输入输出就可以和父进程通信了。
程序代码段如下:
【win32进程之间通信的方式】
SECURITY_ATTRIBUTES sa; sa.bInheritHandle=TRUE; sa.lpSecurityDescriptor=NULL; sa.nLength=sizeof(SECURITY_ATTRIBUTES); if(!CreatePipe(&hRead,&hWrite,&sa,0)) { MessageBox("创建匿名管道失败!"); return; } STARTUPINFO sui; PROCESS_INFORMATION pi; ZeroMemory(&sui,sizeof(STARTUPINFO)); sui.cb=sizeof(STARTUPINFO); sui.dwFlags=STARTF_USESTDHANDLES; sui.hStdInput=hRead; sui.hStdOutput=hWrite; sui.hStdError=GetStdHandle(STD_ERROR_HANDLE); if(!CreateProcess("..\\Child\\Debug\\Child.exe",NULL,NULL,NULL, TRUE,0,NULL,NULL,&sui,&pi)) { CloseHandle(hRead); CloseHandle(hWrite); hRead=NULL; hWrite=NULL; MessageBox("创建子进程失败!"); return; } else { CloseHandle(pi.hProcess); CloseHandle(pi.hThread); }


    推荐阅读