现在的位置: 首页 > 综合 > 正文

孙鑫教学视频笔记(17)进程间通信

2013年10月10日 ⁄ 综合 ⁄ 共 12324字 ⁄ 字号 评论关闭

进程间通信的四种方式
剪贴板
匿名管道
命名管道
邮槽

Win32平台下,内存块在物理内存中从来不会被移动,但可以在缺省的堆中被移动

剪贴板
将数据存入剪贴板:
void CClipboardDlg::OnBnClickedBtnSend()
{
 // TODO: 在此添加控件通知处理程序代码
 if(OpenClipboard())
 {
  CString str;
  char * pBuf;
  HANDLE hClip;
  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();
 }
}
函数:
1.  BOOL OpenClipboard( HWND hWndNewOwner);
The
OpenClipboard function opens the clipboard for examination and prevents
other applications from modifying the clipboard content.

2.   HGLOBAL GlobalAlloc(
       UINT uFlags,
       SIZE_T dwBytes);
The
GlobalAlloc function allocates the specified number of bytes from the
heap. Windows memory management does not provide a separate local heap
and global heap.
Note  The global functions are slower than other
memory management functions and do not provide as many features.
Therefore, new applications should use the heap functions. However, the
global functions are still used with DDE, the clipboard functions, and
OLE data objects.

3.  LPVOID GlobalLock(HGLOBAL hMem);
The GlobalLock function locks a global memory object and returns a pointer to the first byte of the object's memory block.

Note  The global functions are slower than other memory management
functions and do not provide as many features. Therefore, new
applications should use the heap functions. However, the global
functions are still used with DDE and the clipboard functions.

4.  HANDLE SetClipboardData(         
 UINT uFormat,
       HANDLE hMem);
The
SetClipboardData function places data on the clipboard in a specified
clipboard format. The window must be the current clipboard owner, and
the application must have called the OpenClipboard function. (When
responding to the WM_RENDERFORMAT and WM_RENDERALLFORMATS messages, the
clipboard owner must not call OpenClipboard before calling
SetClipboardData.)
The WM_RENDERFORMAT message is sent to the
clipboard owner if it has delayed rendering a specific clipboard format
and if an application has requested data in that format. The clipboard
owner must render data in the specified format and place it on the
clipboard by calling the SetClipboardData function.

5  BOOL CloseClipboard( VOID );
The CloseClipboard function closes the clipboard.

6  BOOL EmptyClipboard( VOID );
The EmptyClipboard function
empties the clipboard and frees handles to data in the clipboard. The
function then assigns ownership of the clipboard to the window that
currently has the clipboard open.

检查数据是否符合要求:
BOOL IsClipboardFormatAvailable(UINT format);
The IsClipboardFormatAvailable function determines whether the clipboard contains data in the specified format.

从剪贴板读取数据:
void CClipboardDlg::OnBnClickedBtnRecv()
{
 // TODO: 在此添加控件通知处理程序代码
 if(OpenClipboard())
 {
  if(IsClipboardFormatAvailable)
  {
   char * pBuf;
   HANDLE hClip;
   hClip=GetClipboardData (CF_TEXT);
   pBuf=(char*)GlobalLock (hClip);  //把数据转换成字符型
   GlobalUnlock (hClip);
   SetDlgItemText (IDC_EDIT_RECV,pBuf);
   CloseClipboard ();
  }
 }
}

匿名管道
创建管道:
BOOL CreatePipe(
  PHANDLE hReadPipe,
  PHANDLE hWritePipe,
  LPSECURITY_ATTRIBUTES lpPipeAttributes,
  DWORD nSize
);

启动进程:
BOOL CreateProcess(
  LPCTSTR lpApplicationName,
  LPTSTR lpCommandLine,
  LPSECURITY_ATTRIBUTES lpProcessAttributes,
  LPSECURITY_ATTRIBUTES lpThreadAttributes,
  BOOL bInheritHandles,
  DWORD dwCreationFlags,
  LPVOID lpEnvironment,
  LPCTSTR lpCurrentDirectory,
  LPSTARTUPINFO lpStartupInfo,
  LPPROCESS_INFORMATION lpProcessInformation
);
The
CreateProcess function creates a new process and its primary thread.
The new process runs the specified executable file in the security
context of the calling process.
If the calling process is
impersonating another user, the new process uses the token for the
calling process, not the impersonation token. To run the new process in
the security context of the user represented by the impersonation
token, use the CreateProcessAsUser or CreateProcessWithLogonW function.
其中STARTUPINFO结构体定义如下:
typedef
struct _STARTUPINFO {  DWORD cb;  LPTSTR lpReserved;  LPTSTR
lpDesktop;  LPTSTR lpTitle;  DWORD dwX;  DWORD dwY;  DWORD dwXSize; 
DWORD dwYSize;  DWORD dwXCountChars;  DWORD dwYCountChars;  DWORD
dwFillAttribute;  DWORD dwFlags;  WORD wShowWindow;  WORD cbReserved2; 
LPBYTE lpReserved2;  HANDLE hStdInput;  HANDLE hStdOutput;  HANDLE
hStdError;
} STARTUPINFO, *LPSTARTUPINFO;

将机构体中所有成员置为零可以使用函数:
void ZeroMemory(PVOID Destination,SIZE_T Length);

要获得标准输入输出或标准错误句柄,使用函数:
HANDLE GetStdHandle(DWORD nStdHandle);
得到的是父进程的标准错误句柄。

关闭进程和线程对象的句柄
  CloseHandle (pi.hProcess);  //关闭所返回的子进程句柄
  CloseHandle (pi.hThread);  //关闭子进程中主线程句柄

创建新进程时,系统会建立一个主进程内核对象和一个线程内核对象,内核对象都有使用计数,系统为内核对象赋初始的使用计数1,当
CreateProgress在内部打开内核对象时,每个对象的使用计数变为2,我们在父进程中如果不需要使用这两个句柄,调用CloseHandle
关闭这两个句柄,系统会将子进程的进程内核对象和线程内核对象减1,当子进程中止运行时,系统会再将使用计数减一,此时内核对象实用计数为零,进程内核对
象和线程内核对象才能够被释放。我们应该在不需要使用内核对象句柄的时候使用CloseHandle关闭句柄。

建立匿名管道代码如下:
void CParentView::OnPipeCreate()
{
 // TODO: 在此添加命令处理程序代码
 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);  //关闭子进程中主线程句柄
 }
}

在父进程创建管道,返回管道的读写句柄,调用CreateProcess启动子进程,通过将子进程的标准输入输出句柄设置为管道的读写句柄,相当于对管道的读写句柄做上标记然后传递给子进程。在子进程中得到自己的标准输入输出句柄,相当于得到了管道的读写句柄。

匿名管道只能在父子进程间进行通信,因为匿名管道没有名字,所以我们只有在调用CreateProcess时将管道的读写句柄传递给子进程。

添加读写函数
void CParentView::OnPipeRead()
{
 // TODO: 在此添加命令处理程序代码
 char buf[100];
 DWORD dwRead;
 if(!ReadFile(hRead,buf,100,&dwRead,NULL))
 {
  MessageBox ("读取数据失败!");
  return ;
 }
 else MessageBox (buf);
}

void CParentView::OnPipeWrite()
{
 // TODO: 在此添加命令处理程序代码
 char buf[]="I love you!";
 DWORD dwWrite;
 if(!WriteFile(hWrite,buf,strlen(buf)+1,&dwWrite,NULL))
 {
  MessageBox ("写入数据失败!");
  return ;
 } 
}

记得前边建立子进程的函数吗:
if(CreateProcess("..//Child//Debug//Child.exe",NULL,NULL,NULL,TRUE,
        0,NULL,NULL,&sui,&pi))
接下来在同名目录下新建一个工程,名为Child。

大部分代码可移植刚才的程序。
为得到子进程的标准输入和标准输出句柄,需要在View类窗口完全创建成功之后去获取,增加一个虚函数OnInitialUpdate();此函数是当我们的窗口创建成功之后第一个调用的函数
代码如下:
void CChildView::OnInitialUpdate()
{
 CView::OnInitialUpdate();

 // TODO: 在此添加专用代码和/或调用基类
 hRead=GetStdHandle (STD_INPUT_HANDLE);
 hWrite=GetStdHandle (STD_OUTPUT_HANDLE);
}

HANDLE GetStdHandle(
  DWORD nStdHandle
);
STD_INPUT_HANDLE   Handle to the standard input device. Initially, this is a handle to the console input buffer, CONIN$.
STD_OUTPUT_HANDLE
  Handle to the standard output device. Initially, this is a handle to
the active console screen buffer, CONOUT$.
STD_ERROR_HANDLE
  Handle to the standard error device. Initially, this is a handle to
the active console screen buffer, CONOUT$.
If the function
succeeds, the return value is a handle to the specified device, or a
redirected handle set by a previous call to SetStdHandle. The handle
has GENERIC_READ and GENERIC_WRITE access rights, unless the
application has used SetStdHandle to set a standard handle with lesser
access.

命名管道
命名管道是通过网络来完成进程间的通信,它屏蔽了地称的网络协议细节。我们在不了解网络协议的情况下,也可以利用命名管道来实现进程间的通信。
命名管道充分利用了Windows NT和Windows2000内建的安全机制。
将命名管道作为一种网络编程方案时,它实际上建立了一个客户机/服务器通信体系,并在其中可靠的传输数据。
命名管道是围绕Windows文件系统设计的一种机制,采用“命名管道文件系统(Named Pipe File System,NPFS)”接口,因此,客户机和服务器可利用标准的Win32文件系统内核(例如ReadFile和WriteFile)来进行数据的收发。
命名管道服务器和客户机的区别在于:服务器是唯一一个有权创建命名管道的进程,也只有它才能接受管道客户机的连接请求。而客户机只能同一个现成的命名管道服务器建立连接。

名管道服务器只能在Windows
NT或Windows2000上创建,所以,我们无法在两台Windows95或Windows98计算机之间利用管道进行通信。不过,客户机可以是
Windows95或Windows98计算机,与Windows NT或Windows2000计算机进行连接通信。
命名管道提供了两种基本通信模式:字节模式和消息模式。在字节模式中,数据以一个连续的字节流的形式,在客户机和服务器之间流动。而在消息模式中,客户机和服务器则通过一系列不连续的数据单位,进行数据的收发,每次在管道上发出一条消息后,它必须作为一条完整的消息读入。

服务器进程使用
HANDLE CreateNamedPipe(
  LPCTSTR lpName,
  DWORD dwOpenMode,
  DWORD dwPipeMode,
  DWORD nMaxInstances,
  DWORD nOutBufferSize,
  DWORD nInBufferSize,
  DWORD nDefaultTimeOut,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes
);
创建第制定命名管道的第一个实例,或现有命名管道的新的实例
The
CreateNamedPipe function creates an instance of a named pipe and
returns a handle for subsequent pipe operations. A named pipe server
process uses this function either to create the first instance of a
specific named pipe and establish its basic attributes or to create a
new instance of an existing named pipe。
Windows Me/98/95:  Named pipes cannot be created.

调用CreateEvent创建一个人工或自动重置的事件对象
HANDLE CreateEvent(
  LPSECURITY_ATTRIBUTES lpEventAttributes,
  BOOL bManualReset,
  BOOL bInitialState,
  LPCTSTR lpName
);

 

服务器等待连接请求到来,使用函数:
BOOL ConnectNamedPipe(
      HANDLE hNamedPipe,
      LPOVERLAPPED lpOverlapped);

如果ConnectNamedPipe返回一个零值而ERROR_IO_PENDING= =GetLastError()表示这个操作没有失败,可能在随后的时间内这个操作会完成

如果我们要在一个程序中调用另一个可执行程序,可使用CreateProcess,代码如下:
 STARTUPINFO sui;
 PROCESS_INFORMATION pi;
 ZeroMemory(&sui,sizeof(STARTUPINFO));
 sui.cb=sizeof(STARTUPINFO);
 sui.dwFlags=STARTF_USESTDHANDLES;
 sui.hStdInput=hPipe;
 sui.hStdOutput=hPipe;
 sui.hStdError=GetStdHandle (STD_ERROR_HANDLE);
 CreateProcess("..//NamedPipeClient//Debug//NamedPipeClient.exe",NULL,NULL,NULL,TRUE,0,NULL,NULL,&sui,&pi);

接下来创建命名管道的服务器端,代码如下:

void CNamePipeView::OnPipeCreate()
{
 // TODO: 在此添加命令处理程序代码
 hPipe=CreateNamedPipe("////.//pipe//MyTest",
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,0,1,1024,1024,0,NULL);
 if(INVALID_HANDLE_VALUE==hPipe)
 {
  MessageBox("创建命名管道失败!");
  hPipe=NULL;
  return ;
 }
 HANDLE hEvent;
 hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);
 if(!hEvent)
 {
  MessageBox("创建事件对象失败!");
  CloseHandle(hPipe);
  hPipe=NULL;
  return ;
 }
 OVERLAPPED lp;
 ZeroMemory(&lp,sizeof(OVERLAPPED));
 lp.hEvent=hEvent;

 if(!ConnectNamedPipe(hPipe,&lp))
 {
  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);
}

void CNamePipeView::OnPipeRead()
{
 // TODO: 在此添加命令处理程序代码
 char buf[100];
 DWORD dwRead;
 if(!ReadFile(hPipe,buf,100,&dwRead,NULL))
 {
  MessageBox ("读取数据失败!");
  return ;
 }
 else MessageBox (buf);
}

void CNamePipeView::OnPipeWreie()
{
 // TODO: 在此添加命令处理程序代码
 char buf[]="I love you!";
 DWORD dwWrite;
 if(!WriteFile(hPipe,buf,strlen(buf)+1,&dwWrite,NULL))
 {
  MessageBox ("写入数据失败!");
  return ;
 } 
}

void CNamePipeView::OnPipeOpen()
{
 // TODO: 在此添加命令处理程序代码
 STARTUPINFO sui;
 PROCESS_INFORMATION pi;
 ZeroMemory(&sui,sizeof(STARTUPINFO));
 sui.cb=sizeof(STARTUPINFO);
 sui.dwFlags=STARTF_USESTDHANDLES;
 sui.hStdInput=hPipe;
 sui.hStdOutput=hPipe;
 sui.hStdError=GetStdHandle (STD_ERROR_HANDLE);
 CreateProcess("..//NamedPipeClient//Debug//NamedPipeClient.exe",NULL,NULL,NULL,TRUE,
  0,NULL,NULL,&sui,&pi);
}  
//OnPipeOpen()这段代码是我自己加的,刚装了VC++2003.NET,不知道怎样同时//启动第二个程序,只好用这段代码在主程序里调用客户端程序。

客户端程序要相对简单,只需要等待网络上命名管道实例,如果连上就打开命名空间
代码如下:
void CNamedPipeClientView::OnPipeConnect()
{
 // TODO: 在此添加命令处理程序代码
 if(!WaitNamedPipe("////.//pipe//MyTest",NMPWAIT_WAIT_FOREVER))
 {
  MessageBox("当前没有可利用的命名管道实例!");
  return ;
 }
 hPipe=CreateFile("////.//pipe//MyTest",FILE_SHARE_READ | FILE_SHARE_WRITE,
  0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
//点那部分表示服务器名字,用“.”表示本地主机,如果要连接其他主机就要把点//换成主机名。注意双引号中的反斜杠,四个表示两个
 if(INVALID_HANDLE_VALUE==hPipe)
 {
  MessageBox("打开命名空间失败!");
  hPipe=NULL;
  return ;
 }
}

void CNamedPipeClientView::OnPipeRead()
{
 // TODO: 在此添加命令处理程序代码
 char buf[100];
 DWORD dwRead;
 if(!ReadFile(hPipe,buf,100,&dwRead,NULL))
 {
  MessageBox ("读取数据失败!");
  return ;
 }
 else MessageBox (buf);
}

void CNamedPipeClientView::OnPipeWrite()
{
 // TODO: 在此添加命令处理程序代码
 char buf[]="命名管道测试程序!!";
 DWORD dwWrite;
 if(!WriteFile(hPipe,buf,strlen(buf)+1,&dwWrite,NULL))
 {
  MessageBox ("写入数据失败!");
  return ;
 }
}

邮槽
邮槽是基于广播通信体系设计出来的,它采用无连接的不可靠的数据传输。
邮槽是一种单向通信机制,创建邮槽的服务器进程读取数据,打开邮槽的客户机进程写入数据,为保证邮槽在各种Windows平台下都能正常工作,我们传输消息的时候,应将消息的长度限制在424字节以下。
要创建邮槽,可以使用函数
HANDLE CreateMailslot(
  LPCTSTR lpName,
  DWORD nMaxMessageSize,
  DWORD lReadTimeout,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes
); 
服务器端代码:
void CMailsoltSrvView::OnMailRecv()
{
 // TODO: 在此添加命令处理程序代码
// HANDLE hMail;

 hMail=CreateMailslot("////.//mailslot//MyMail",0,MAILSLOT_WAIT_FOREVER,NULL);
 if(INVALID_HANDLE_VALUE==hMail)
 {
  MessageBox("创建邮槽失败!");
  return;
 }
 char buf[100]="0";
 DWORD dwRead;
 if(!ReadFile(hMail,buf,100,&dwRead,NULL))
 {
  MessageBox ("读取数据失败!");
  CloseHandle(hMail); 
  return ;
 }
 MessageBox (buf);
 CloseHandle(hMail);
}

客户端代码:
void CMailsoltCltView::OnMailSend()
{
 // TODO: 在此添加命令处理程序代码
 HANDLE hMail;
 hMail=CreateFile("////.//mailslot//MyMail",GENERIC_READ,FILE_SHARE_READ,
  NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
 if(INVALID_HANDLE_VALUE==hMail)
 {
  MessageBox("打开邮槽失败!");
  return; 
 }
 
 char buf[]="邮槽测试程序!";
 DWORD dwWrite;
 if(!WriteFile(hMail,buf,strlen(buf)+1,&dwWrite,NULL))
 {
  MessageBox ("写入数据失败!");
  CloseHandle(hMail);
  return ;
 }
 CloseHandle(hMail);
}

抱歉!评论已关闭.