进程间通信的四种方式(孙鑫VC17集)

来源:互联网 发布:linux串口命令 编辑:程序博客网 时间:2024/06/16 03:41

进程间通信的四种方式(孙鑫VC17集)

(2011-01-17 15:32:45)
转载
标签:

杂谈

 

个人理解:

(1)剪切板:是系统内部维护的一个内存区域(只适用本机中进程)。是系统提供的,所以所有的进程都可以访问(如记事本,WORD)等,复制是把数据放到剪切板中,粘贴则是从剪切板中取出数据。

 

1.OpenClipboard()打开剪切板

2.EmptyClipboard(),清空剪切板,并让发送数据端的对象能拥有剪切板的拥有权

5.SetClipboardData(CF_TEXT,hand),在剪切板中放置句柄hand指定内存对象中的数据,并指定格式(CF_TEXT)

6.CloseClipboard(),关闭剪切板,让其它的窗口对象能够访问该剪切板

因为SetClipboardData()函数中要需要一个句柄指定的内存对象,那中间就还需要下面这些函数了:

3.hand=GlobalAlloc(GMEM_MOVEABLE,str.GetLength()+1),得到一个str.GetLength()+1长度大的内存对象,一般用GMEM_MOVEABLE,返回一个句柄,并且其索引为2

4.buf=(char*)GlobalLock(hand),让该对象的系统内部的技术索引减1,(在消毁内存对象,系统再减1 ,即为0了),然后往该内存对象中复制数据:strcpy(buf,W2A(str.GetBuffer()))

 

具体例子:这是发送数据端(一个对话框项目)进程剪切板操作

void CClipboardDlg::OnBnClickedBtnSend()
{
 // TODO: Add your control notification handler code here
 if(OpenClipboard())//判断能否打得开剪切板
 {
  CString str;
  GetDlgItemText(IDC_EDIT_SEND,str);
  char*buf;
  HANDLE hand;
  EmptyClipboard();//清空剪切板,让本对象拥有剪切板拥有权
  hand=GlobalAlloc(GMEM_MOVEABLE,str.GetLength()+1);//创建一个全局内存对象
  buf=(char*)GlobalLock(hand);//转换句柄成指针,其索引默认为2了
  GlobalUnlock(hand);//让其索引技术减1
  USES_CONVERSION;
  strcpy(buf,W2A(str.GetBuffer()));//将数据拷贝到创建的内存中,并宽窄字符转换
//  GlobalUnlock(hand);//让其索引技术减1
  SetClipboardData(CF_TEXT,hand);//指定在剪切板中放置全局内存对象中的数据,并指定其格式
  CloseClipboard();//关闭剪切板,让其它的窗口对象能够访问该剪切板
 }

}

 

而另一进程(另一个对话框项目)则接收端接收数据,剪切板操作如下:

void CClipboard1Dlg::OnBnClickedBtnRecv()
{
 // TODO: Add your control notification handler code here
 if(OpenClipboard())//打开剪切板
 {
//  CString str;
  HANDLE hand;
  char*buf;
  if(IsClipboardFormatAvailable(CF_TEXT))//判断下剪切板中是否有自己所需要格式的数据类型
  {
   hand=GetClipboardData(CF_TEXT);//获取剪切板的句柄
   buf=(char*)GlobalLock(hand);//句柄转换
   CString str(buf);// 获取剪切板内的数据
   SetDlgItemText(IDC_EDIT_RECV,str);
  }
  CloseClipboard();//关闭剪切板,让其它的窗口对象能够访问该剪切板

 }
}

 

(2)匿名管道(读写管道),匿名管道只能是用在本地机器上(即一台电脑)的父子进程间通信

1.CreatePipe()//创建一个匿名管道

2.CreateProcess()//创建一个子(新)进程

BOOL CreateProcess(
  LPCTSTR lpApplicationName,                 // name of executable module
  LPTSTR lpCommandLine,                      // command line string
  LPSECURITY_ATTRIBUTES lpProcessAttributes, // SD
  LPSECURITY_ATTRIBUTES lpThreadAttributes,  // SD
  BOOL bInheritHandles,                      // handle inheritance option
  DWORD dwCreationFlags,                     // creation flags
  LPVOID lpEnvironment,                      // new environment block
  LPCTSTR lpCurrentDirectory,                // current directory name
  LPSTARTUPINFO lpStartupInfo,               // startup information
  LPPROCESS_INFORMATION lpProcessInformation // process information
);

参数解释:1.指定(另一个项目)可执行文件名的话,它只是在当前目录(父进程)下查找可执行文件名,如果没有找到,就失败返回,还有就是它不会自动为可执行文件名加上扩展名(。EXE),若是用这个的话,要注意与父进程的目录平级(如“..\\Child\\Debug\\Child.exe")。2.若是第一个参数设NULL,而在这指定可执行文件名的话,系统则会按几种方法,逐一搜索可执行文件名,且会自动为可执行文件名加上扩展名(。EXE),所以在创建子进程这函数时,一般是在这个参数中指定可执行文件名。3与4.两个参数都是用来设定创建新进程的该新进程内核对象与其主线程内核对象的安全属性,要是该新进程,之后还有自己的子进程,而子进程不用到这两个内核对象句柄,则可设为NULL,让系统设为默认的安全属性。5.表示新(子)进程能否继承父进程中打开的句柄(如这匿名管道打开的读写管道句柄),TRUE表示能。6.指示控制优先级类与创建进程的附加标志,不需要考虑这些时,可以设为0 7.进程环境,一般设为NULL,表示与父进程一样环境。8.为NULL,表示新(子)进程与调用进程(父)有相同驱动口器与目录。9.用来指定新进程(子)的窗口如何出现(STARTUPINFO结构中指标dwFlags设置为STARTF_USESTDHANDLES,让新进程(子)能正常的工作,这样只要考虑这指标里面提到的三个参数标准的输入,输出句柄,错误句柄(GetStdHandle()函数获得错误句柄),而标准输入,输出句柄则要赋值父进程的匿名管道的读写管道句柄用的,这样明确的指出哪个是输入,输出句柄,这样新进程才能通过GetStdHandle()获取父进程中的读写管道句柄的,然后对数据进行访问操作了,另外一个就是一般一个结构体中有成员cb都要对其赋值,因为其它函数用它是,可能对这个参数有所要求,其它的则都置为0,用函数zeromemory()。10.[OUT]一个结构接收创建新进程时,其新进程句柄及这新进程的主线程句柄,新进程与新进程主线程的标识,若是有函数依赖与这两个标识时,那就一定要确定这新进程还在运行状态,要是它停止的话,那那函数结果将是不可预料的,因为停止了,系统会把它们分配给其它进程。

 

具体例子:

void CParentpipeView::OnPipeCreate()
{
 // TODO: Add your command handler code here
 //创建匿名管道
 SECURITY_ATTRIBUTES at;
 at.lpSecurityDescriptor=NULL;
 at.bInheritHandle=TRUE;//表示返回的[OUT]的读写句柄,子(新)进程可以继承(使用)
 at.nLength=sizeof(SECURITY_ATTRIBUTES);
 if(!CreatePipe(&m_hread,&m_hwrite,&at,0))//创建匿名管道
 {
  MessageBox(_T("创建匿名管道失败!"));
 }
 //创建一个子(新)进程
 STARTUPINFO  stainfo;
 ZeroMemory(&stainfo,sizeof(STARTUPINFO));
 stainfo.dwFlags=STARTF_USESTDHANDLES;//使用标准的输入,输出,错误句柄
 stainfo.cb=sizeof(STARTUPINFO);
 stainfo.hStdInput=m_hread;//*对读写管道句柄设为标准的,这样在子进程获取时,才能分清哪个是读写
 stainfo.hStdOutput=m_hwrite;
 stainfo.hStdError=GetStdHandle(STD_ERROR_HANDLE);//传的是父进程的错误句柄
 PROCESS_INFORMATION mation;
 int a=CreateProcess(_T("F:\\编写的程序\\Parentpipe\\Debug\\Child.exe"),NULL,NULL,NULL,TRUE,0,
  NULL,NULL,&stainfo,&mation);//***路径有问题:在一个工程中有建两个项目时)后面建的那个项目.EXE文件是放在第一项目(父进程里面的)里面的,所以路径要看准了,可从编译时OUTPUT窗口内看出。还有一个就是要父子进程能联系一起,就要子进程必定是通过父进程起动的(如父进程窗口中的一个菜单项单击,创建子进程即是起动),这个才能在匿名管道上进行数据交流访问。这函数并把父进程中的读写句柄传给子进程。
 if(!a)//一开始创建就默认了子进程及子进程内核对象索引为2了
 {
  MessageBox(_T("创建子进程失败!"));
  return;
 }
 else
 {
  CloseHandle(mation.hProcess);//让子进程内核对象索引减1
  CloseHandle(mation.hThread);//让子进程主线程对象索引减1,在它它们结束的时候,系统再对它们减1,即为0了,最后
 }
}

而读写管道中的数据还是可以用ReadFile(),WriteFile()函数,这两函数不仅是可以是读写文件也可以是匿名管道中的数据。

具体例子:

void CParentpipeView::OnPipeRead()
{
 // TODO: Add your command handler code here
 char buf[100];
 DWORD dwRead;
 if(!ReadFile(m_hread,buf,100,&dwRead,NULL))
 {
  MessageBox(_T("读取数据失败!"));
  return;
 }
 else
 {
  CString str(buf);
  MessageBox(str);
 }

}

***另外注意:内核对象引用计数,是指系统内部有多少句柄在标识这内核对象(线程内核对象,进程内核对象),开始创建线程(进程)时,这个线程(进程)内核对象内部的引用计数默认为2了,一般在创建线程(进程)后,在不需要或者是使用完后要紧写上语句:CloseHandle()关闭线程(进程)句柄,让线程(进程)内核对象引用技术减1,最后在线程(进程)结束时,系统自动再为它们减1,就归0了,不浪费系统句柄资源。对于是类成员的句柄要在这类的析构函数中关闭。

 

***1.VIEW中的重载函数OnInitialUpdate()运行时机是在VIEW窗口创建完成后,第一个开始调用的函数(即VIEW窗口首次更新时调用的),在VIEW类中需要对一些数据要先处理时,就可以调用了。

 2.如何表示上级目录
../表示源文件所在目录的上一级目录,../../表示源文件所在目录的上上级目录,以此类推。
 3.还有一点是,要想知道MSDN中一个函数调用失败在哪,可以用这函数DWORD DW=GetLastError()返回一个值DW,再在Tools中点Error Lockup,然后有个窗口,再里面输入DW,即可查找失败的原因在哪了!!!

 

(3)命名管道,它可以网络间进程通信,也可以本机内进程通信。服务器端是唯一一个有权创建命名管道的进程,而客户机只能是对它连接请求,命名管道服务器只能是Windows NT或Windows2000上创建,而客户机没有要求。命名管道提供两种基本通信模式:字节模式与消息模式(下面例子是字节模式)

 

具体例子:

服务器端:

void CNamePipeSrvView::OnPipeCreate()
{
 // TODO: Add your command handler code here
 //创建命名管道
 m_hPipe=CreateNamedPipe(_T("\\\\.\\pipe\\pipename"),PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
  0,1,1024,1024,0,NULL);//要注意的是_T(\\\\.\\pipe\\pipename)要是网络间通信时,里面的点"."必需是服务端主机名字,而pipename这名字可以随便,其它都是定的,不能改
 if(INVALID_HANDLE_VALUE==m_hPipe)
 {
  MessageBox(_T("创建命名管道失败!"));
//  return;
 }
 //创建一个事件对象,这是命名管道用到重叠函数,所以需要建这个事件对象
 HANDLE hevent;
 hevent=CreateEvent(NULL,FALSE,TRUE,NULL);
 if(!hevent)
 {
  MessageBox(_T("创建事件对象失败!"));
 }
 CloseHandle(hevent);
 OVERLAPPED old;
 ZeroMemory(&old,sizeof(OVERLAPPED));//这个结构其它成员不用到的都赋值0
 old.hEvent=hevent;
 //等待客户连接
 if(!ConnectNamedPipe(m_hPipe,&old))
 {
  if( ERROR_PIPE_CONNECTED==GetLastError())
   MessageBox(_T("等待客户请求失败!"));
 }
 

}

客户端:

 void CClientPipeView::OnPipeConnect()
{
 // TODO: Add your command handler code here
 //等待(判断服务器端是否有可以利用的命名管道实例
 if(!WaitNamedPipe(_T("\\\\.\\pipe\\pipename"),NMPWAIT_USE_DEFAULT_WAIT))
 {
  MessageBox(_T("服务器端没有可用的实例命名管道"));
  return;
 }
 //打开服务器端的命名管道,即可获得它的句柄,之后就可以读写,数据交流了
 m_hPipe=CreateFile(_T("\\\\.\\pipe\\pipename"),GENERIC_READ | GENERIC_WRITE,0,
  NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
 if(INVALID_HANDLE_VALUE==m_hPipe)
 {
  MessageBox(_T("打开服务器端的命名管道失败!"));
  return;
 }


}

 服务端写操作:

void CNamePipeSrvView::OnPipeWrite()
{
 // TODO: Add your command handler code here
 CString str=_T("this is good");
 DWORD dwWrite;
 USES_CONVERSION;//在读写过程中,宽窄字符要一致
 if(!WriteFile(m_hPipe,W2A(str.GetBuffer()),str.GetLength()+1,&dwWrite,NULL))
 {
  MessageBox(_T("写入数据失败"));
  return;
 }
}

客户端读操作:

void CClientPipeView::OnPipeRead()
{
 // TODO: Add your command handler code here
 char buf[100];
 DWORD dwRead;
 if(!ReadFile(m_hPipe,buf,100,&dwRead,NULL))
 {
  MessageBox(_T("读取数据失败!"));
  return;
 }
 else
 {
  CString str(buf);
  MessageBox(str);
 }
}

 

 (4)邮槽,基于广播通信体系的(网络间通信),它采用无连接的不可靠的数据传输,是一种意向的通信机制,服务器进程读取数据,客户机端进程写入数据,为保证其正常工作,传输消息长度要地424字节。这是一对多的概念:如要开会,领导要发布一个消息,他只要在他电脑(客户机)装个客户端,写入(发送)数据,而下面的成员,他们电脑(服务端)都装有服务器端,读取(接收)数据消息即可,要开会了!在这时候用这个邮槽编程比套接字要简便!还有一个是要是想一个进程中,即可发送,又可以接收,只要在这进程中,即写服务端,也写客户端即可。

具体例子:

服务端:

 

void CMailslotSrvView::OnRecv()
{
 // TODO: Add your command handler code here
 //创建邮槽
 HANDLE hmailslot;
 hmailslot=CreateMailslot(_T("\\\\.\\mailslot\\name"),0,MAILSLOT_WAIT_FOREVER,NULL);
 if(INVALID_HANDLE_VALUE==hmailslot)
 {
  MessageBox(_T("创建邮槽失败"));
  CloseHandle(hmailslot);
 }
 //读取数据
 char buf[100];
 DWORD dwRead;
 if(!ReadFile(hmailslot,buf,100,&dwRead,NULL))
 {
  MessageBox(_T("读取数据失败!"));
  return;
 }
 else
 {
  CString str(buf);
  MessageBox(str);
 }

}

客户端:

void CMailslotCltView::OnWrite()
{
 // TODO: Add your command handler code here
 HANDLE hand;
 hand=CreateFile(_T("\\\\.\\mailslot\\name"),GENERIC_WRITE,FILE_SHARE_READ,NULL,
  OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);//服务端读共享SHARE_READ
 if(INVALID_HANDLE_VALUE==hand)
 {
  MessageBox(_T("打开邮槽失败"));
  
 }
 //写入数据
 CString str=_T("you are good");
 DWORD dwWrite;
 USES_CONVERSION;//读写字符的宽窄要一致
 if(!WriteFile(hand,W2A(str.GetBuffer()),str.GetLength()+1,&dwWrite,NULL))
 {
  MessageBox(_T("写入数据失败"));
  CloseHandle(hand);
  return;
 }
 CloseHandle(hand);
}

 

0

阅读(1008)评论 (0)收藏(0)转载(1)喜欢打印举报
已投稿到:
排行榜

转载列表:

    转载

    转载是分享博文的一种常用方式...

    前一篇:2011年01月17日
    后一篇:2011年01月19日


    0 0
    原创粉丝点击