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==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==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==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==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,其中必须设置标准输入输出文件句柄以及错误输出句柄,同时还要设置属性为使用标准句柄。然后在子进程当中获得标准的输入输出就可以和父进程通信了。

程序代码段如下:

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

你可能感兴趣的:(win32)