进程间通信——剪贴板和匿名管道

    本地主机的不同进程之间以及网络间不同进程之间是如何通信的呢?下面将详细介绍进程间的通信。

    进程间通信常用的有以下几种方法:(1)剪贴板;(2)匿名管道;(3)命名管道;(4)邮槽。这四种方法又可以分为两大类,剪贴板和匿名管道只能在本地主机的各进程间通信,而命名管道和邮槽不仅可以在本地主机的各进程间通信也可以在网络中不同进程之间进行通信。本篇只介绍剪贴板和匿名管道。

一、剪贴板

    剪贴板是操作系统系统维护的一块内存区域,本地主机的任何进程都可以访问剪贴板,因此通过剪贴板这个中介本地主机的各进程之间就可以方便的通信。通过剪贴板进行进程间的通信,可用过下面的步骤实现:

(1)打开剪贴板

    打开剪贴板的操作可通过调用函数OpneClipboard()

(2)获得剪贴板的拥有权

    打开剪贴板之后,必须调用EmptyClipboard()函数,清空剪贴板的数据并获得剪贴板的所有权,然后就可以往剪贴板中写入数据和其他进程进行通信了。

(3)往剪贴板中写入数据

    往剪贴板中写入数据,通过调用SetClipboardData()实现,该函数可以通过参数写入指定格式的数据。如果写入的数据需要一个内存对象的话,需要调用GlobalAlloc()分配一个内存对象,但该函数的返回值是一个HGLOBAL类型的句柄,如果需要一个指针类型的话,可以调用GlobalLock()函数将句柄转换为指针类型,但是调用该函数之后,要调用GlobalUnlock()函数,为创建的饿内存对象解锁。

示例代码如下:

if (OpenClipboard())
 {
  EmptyClipboard();
  CString str;
  HGLOBAL hClip;
  char* pBuf;
  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();
 }

(4)进程从剪贴板获取数据

    从剪贴板获取数据可以调用GetClipboardData()获取指定格式的数据,在调用该函数之前可以通过调用IsClipboardFormatAvailable()函数判断剪贴板中是否有指定格式的数据。

示例代码如下:

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();
  }
 }

运行结果如下:

二、匿名管道

    通过匿名管道进行通信的进程必须是本地主机上有父子关系的进程。也就是说匿名管道只支持父子进程之间的通信。所以可以在父进程中调用子进程完成通信。这样的模式类似于服务器/客户机模式。通过匿名管道进行通信,也可以通过下面的步骤进行:

(一)父进程

(1)创建匿名管道

    利用匿名管道进行通信之前,必须先创建匿名管道。创建匿名管道可以调用CreatePipe()函数,该函数会返回该管道的读写句柄以供新进程继承以完成进程间的通信。该函数的第三个参数是一个指向SECURITY_ATTRIBUTES 结构体的指针,该结构体指定管道的读写句柄是否可以被子进程继承。

(2)创建子进程

    通过匿名管道进程的通信必须是父子进程之间的通信,所以必须在父进程中创建子进程或打开一个已有的进程。创建子进程的函数为CreateProcess(),该函数可以指定新进程的路径名和命令行参数来启动一个新进程。该函数的第五个参数指定该子进程是否继承调用进程(父进程)的读写句柄。该函数的第九个参数是一个指向STARTUPINFO结构体的指针,该结构体指明子进程的显示方式。由于该结构体的变量比较多,我们只需要其中某些我们感兴趣的参数,我们可以调用ZeroMemory()函数将其他参数设置为零,以免发生不可预知的错误。我们可以指定在该结构体中指定子进程的标准输入输出和错误句柄。标准输入输出句柄就是父进程创建管道的读写句柄。标准错误句柄可以通过GetStdHandle(STD_ERROR_HANDLE)获得。该函数的返回值是一个指向PROCESS_INFORMATION结构体的指针,该结构体保存了子进程的信息。

示例代码如下:

SECURITY_ATTRIBUTES sua;
 sua.bInheritHandle=TRUE;
 sua.lpSecurityDescriptor=NULL;
 sua.nLength=sizeof(SECURITY_ATTRIBUTES);
 if(!CreatePipe(&hRead,&hWrite,&sua,0))
 {
  MessageBox("创建匿名管道失败!");
  return;
 }
 STARTUPINFO sui;
 ZeroMemory(&sui,sizeof(STARTUPINFO));
 sui.cb=sizeof(STARTUPINFO);
 sui.dwFlags=STARTF_USESTDHANDLES;
 sui.hStdInput=hRead;
 sui.hStdOutput=hWrite;
 sui.hStdError=GetStdHandle(STD_ERROR_HANDLE);
 PROCESS_INFORMATION pi;
 if(!CreateProcess("E:\\studio\\Parent\\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);
 }

(3)往管道写入数据和从管道读取数据

读取数据和写入数据可以用ReadFile()WriteFile()

示例代码如下:

oid CParentView::OnPipRead()
{
 // TODO: Add your command handler code here
 char buf[200];
 DWORD dwRead;
 if(!ReadFile(hRead,buf,200,&dwRead,NULL))
 {
  MessageBox("读取匿名管道失败!");
  return;
 }
 MessageBox(buf);
}


void CParentView::OnPipWrite()
{
 // TODO: Add your command handler code here
 char buf[]="匿名管道测试程序!";
 DWORD dwWrite;
 if(!WriteFile(hWrite,buf,strlen(buf)+1,&dwWrite,NULL))
 {
  MessageBox("写匿名管道失败!");
  return;
 }
}

(二)子进程

在子进程中先要获得父进程的读写句柄,然后就可以进行读写操作完成进程通信了。

(1)获取读写句柄:

hRead=GetStdHandle(STD_INPUT_HANDLE);
 hWrite=GetStdHandle(STD_OUTPUT_HANDLE);

(2)从匿名管道进行读写操作

示例代码如下:

oid CChildView::OnPipRead()
{
 // TODO: Add your command handler code here
 char buf[200];
 DWORD dwRead;
 if(!ReadFile(hRead,buf,200,&dwRead,NULL))
 {
  MessageBox("读取匿名管道失败!");
  return;
 }
 MessageBox(buf);
}


void CChildView::OnPipWrite()
{
 // TODO: Add your command handler code here
 char buf[]="乘风736博客园 http://www.cnblogs.com/chengfeng736/";
 DWORD dwWrite;
 if(!WriteFile(hWrite,buf,strlen(buf)+1,&dwWrite,NULL))
 {
  MessageBox("写匿名管道失败!");
  return;
 }
}
运行结果如下:

posted @ 2011-10-18 23:43  乘风736  阅读(2311)  评论(1编辑  收藏  举报