一 剪切板方式
剪切板是由系统创建一块公共内存用于进程之间共享数据,从而达到进程之间相互通信的目的。但是这种通信方式具有一定的盲目性,也就是发送者并不知道接受者进程会是哪一个。
剪切板通信,首先利用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);
}