天天看点

《VC++深入详解》学习笔记[14]——第17章 进程间通信

第17章 进程间通信

1.剪贴板

剪贴板实际上是系统维护管理的一块内存区域。如果某个程序已经打开了剪贴板,则其他应用程序将不能修改剪贴板,知道前者调用了CloseClipboard函数。并且,只有在调用了EmptyClipboard函数之后,打开剪贴板的当前窗口才拥有剪贴板。

数据发送:

void CClipboardDlg::OnBtnSend()

{

       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();//记住要关闭剪贴板

       }

}

注:一般情况下在编程的时候,给应用程序分配的内存都是可以移动的或者是可以丢弃的,这样能使有限的内存资源充分利用,所以,在某一个时候我们分配的那块内存的地址是不确定的,因为他是可以移动的,所以得先锁定那块内存块,这样应用程序才能存取这块内存。使用GlobalLock的目的是为了保证内存管理时真的是用内存而不是“虚拟内存的磁盘镜像”,否则效率会降低。

数据接收:

void CClipboardDlg::OnBtnRecv()

{

       if(OpenClipboard())

       {//因为在接收端只需从剪贴板中得到数据,而不用向剪贴板中写入数据,所以不要调用EmptyClipboard;

              if(IsClipboardFormatAvailable(CF_TEXT))//检查剪贴板中是否有想要的特定格式的数据

              {

                     HANDLE hClip;

                     hClip = GetClipboardData(CF_TEXT);

                     char *pBuf;

                     pBuf = (char*)GlobalLock(hClip);

                     GlobalUnlock(hClip);//将内存解锁

                     SetDlgItemText(IDC_EDIT_RECV,pBuf);

                     CloseClipboard();

              }

       }

}

2.匿名管道

匿名管道是一个未命名的单向管道,通常用来在一个父进程和一个子进程之间传输数据。匿名管道只能实现本地机器上两个进程间的通信,而不能实现跨网络的通信。因为匿名管道只能在父子进程之间进行通信,子进程如果想要获得匿名管道的句柄,只能从父进程继承而来。当一个子进程从其父进程继承了匿名管道的句柄之后,这两个进程就可以通过该句柄进行通信了。

父进程的实现:

定义两个HANDLE变量:m_hRead, m_hWrite

1.创建匿名管道

void CParentView::OnPipeCreate()

{

       SECURITY_ATTRIBUTES sa;//定义安全属性结构体

       sa.bInheritHandle = TRUE; //子进程可以继承父进程创建的匿名管道的读写句柄

       sa.lpSecurityDescriptor = NULL;

       sa.nLength = sizeof(SECURITY_ATTRIBUTES);

       if(!CreatePipe(&m_hRead,&m_hWrite,&sa,0))

       {

              MessageBox ("创建匿名管道失败!");

              return ;

       }

//匿名管道创建成功则启动子进程并将其读、写句柄传递给子进程

       STARTUPINFO sui;//用来指定新进程主窗口如何出现的结构体

       PROCESS_INFORMATION pi;//进程信息结构体

       ZeroMemory(&sui,sizeof(STARTUPINFO));//结构体所有成员置为0,防止未设值的属性拥有随机值

       sui.cb = sizeof(STARTUPINFO);//指定结构体大小

       sui.dwFlags = STARTF_USESTDHANDLES;

       sui.hStdInput = m_hRead;//将标准读取句柄设置为管道读取句柄

       sui.hStdOutput =m_hWrite;//标准写入句柄设置为管道写入句柄

       sui.hStdError = GetStdHandle(STD_ERROR_HANDLE);

              //通过GetStdHandle返回一个父进程的标准错误句柄

       if(!CreateProcess("..\\Child\\Debug\\Child.exe", //启动子进程,并将子进程的标准输入输出句柄设置为匿名管道的读、写句柄

              NULL,//传递命令行参数

              NULL,//进程安全属性

              NULL,//线程安全属性

              TRUE,// handle inheritance flag

              0,    //创建标记

              NULL,//环境块

              NULL,//当前路径,NULL让子进程与父进程有相同路径

              &sui,//指定新进程主窗口如何出现

              &pi))//用来接收关于新的进程的标识信息

       {//如果创建子进程失败

              CloseHandle (m_hRead);

              CloseHandle (m_hWrite);

              m_hRead=NULL;

              m_hWrite=NULL;

              MessageBox ("创建子进程失败!");

              return;

       }///...endof if

       else

       {

              CloseHandle (pi.hProcess);  //关闭所返回的子进程句柄

              CloseHandle (pi.hThread);  //关闭子进程中主线程句柄

       }

}

注:为了让子进程从众多继承的句柄中区分出管道的读写句柄,就必须将子进程的特殊句柄设置为管道的读写句柄。这里将子进程的标准输入输出句柄分别设置为管道的读、写句柄,这样在子进程中,只要得到了标准输入和标准输出句柄,就相当于得到了这个管道的读写句柄。

2.读取数据:

void CParentView::OnPipeRead()

{

       char buf[100];

       DWORD dwRead;

       if(!ReadFile(m_hRead,buf,100,&dwRead,NULL))

       {

              MessageBox ("读取数据失败!");

              return ;

       }

       else MessageBox (buf);

}

3.写入数据:

void CParentView::OnPipeWrite()

{

       char buf[]="http://blog.csdn.net/teshorse";

       DWORD dwWrite;

       if(!WriteFile(m_hWrite,buf,strlen(buf)+1,

              &dwWrite,NULL))

       {

              MessageBox ("写入数据失败");

              return ;

       }

}

对于管道的读取和写入实际上是通过调用ReadFile和WriteFile这两个函数完成的。

子进程的实现:

定义两个HANDLE变量:hRead, hWrite

1.获得管道的读取和写入句柄

void CChildView::OnInitialUpdate() //当窗口成功调用之后第一个创造的函数

{

       CView::OnInitialUpdate();

       //获取子进程的标准输入输出句柄.

       m_hRead =GetStdHandle(STD_INPUT_HANDLE);

       m_hWrite = GetStdHandle(STD_OUTPUT_HANDLE);

}

2.读取数据

void CChildView::OnPipeRead()

{

       char buf[100];

       DWORD dwRead;

       if(!ReadFile(m_hRead,buf,100,&dwRead,NULL))

       {

              MessageBox ("读取数据失败!");

              return ;

       }

       else MessageBox (buf);

}

3.写入数据

void CChildView::OnPipeWrite()

{

       char buf[]="匿名管道测试程序";

       DWORD dwWrite;

       if(!WriteFile(m_hWrite,buf,strlen(buf)+1,

              &dwWrite,NULL))

       {

              MessageBox ("写入数据失败");

              return ;

       }

}

注:匿名管道只能在父子进程之间通信。两个进程如果想要具有父子关系,必须由父进程通过调用CreateProcess函数去启动子进程。因为匿名管道没有名称,所有只能在父进程中调用CreateProcess函数创建子进程时将管道的读、写句柄传递给子进程。

       利用匿名管道也可以实现在同一个进程内读取和写入数据。

3.命名管道

基础知识:

       ①命名管道通过网络来完成进程间的通信,它屏蔽了底层的网络协议细节。

       ②命名管道充分利用了Windows内建的安全机制,可以指定用户权限。所以区别于Sockets编写网络应用,使用命名管道无需编写验证用户身份的代码。

       ③命名管道实际上是建立了一个CS通信体系,并在其中可靠地传输数据。命名管道是围绕Windows文件系统设计的一种机制,采用“命名管道文件系统”接口,因此,客户机和服务器可利用标准Win32文件系统函数来进行数据的收发。

       ④命名管道服务器和客户机的区别在于:服务器是唯一一个有权创建命名管道的进程,也只有它才能接受管道客户机的连接请求。而客户机只能同一个现成的命名管道服务器建立连接。命名管道服务器只能在WindowsNT、2000等系统上创建。

       ⑤命名管道提供了两种基本通信模式:字节模式和消息模式。在消息模式下通过一系列不连续的数据单位进行数据发送。

       ⑥对同一个命名管道的实例来说,在某一时刻,它只能和一个客户端进行通信。

实现过程:在服务器端调用CreateNamePipe创建命名管道之后,调用ConnectNamedPipe函数让服务器端进程等待客户端进程连接到该命名管道的实例上。在客户端首先调用WwaitNamePipe函数判断当前是否有可以利用的命名管道实例,如果有,就调用CreateFile函数打开该命名管道的实例,并建立一个连接。

4.邮槽

       邮槽是基于广播通信体系设计出来的,它采用无连接的不可靠的数据传输。邮槽是一种单向通信机制,创建邮槽的服务器进程读取数据,打开邮槽的客户机进程写入数据。邮槽适用于开发一对多的广播通信系统。

继续阅读