MFC(过程间的通信,孙鑫C++第十七讲笔记整理)
有四种方法
1.剪贴板
a.创建个ClipBoard的对话框应用程序,加两EditBox和两个Button发送接收。
b.具体代码:
发送端代码:
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();
}
}
2.匿名管道:只能在父子进程之间进行通信
a.先建一个Parent的单文档应用程序,增加“创建管道”“读取数据”“写入数据”三个菜单
b.增加成员变量HANDLE类型的hRead,hWrite,初始化变量,并在析构函数中释放句柄
c.响应菜单代码:
void CParentView::OnPipeCreate()菜单“创建管道”代码
{
// TOD Add your command handler code here
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));将数据清0!
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);关闭句柄,将内核对象的使用计数减少1,这样当操作系统发现内核对象的使用计数为0时,将清除内核对象。
hRead=NULL;
hWrite=NULL;
MessageBox("创建子进程失败!");
return;
}
else
{
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
}void CParentView::OnPipeRead()菜单“读取数据”代码
{
// TOD Add your command handler code here
char buf[100];
DWORD dwRead;
if(!ReadFile(hRead,buf,100,&dwRead,NULL))
{
MessageBox("读取数据失败!");
return;
}
MessageBox(buf);
}void CParentView::OnPipeWrite()菜单“写入数据”代码
{
// TOD Add your command handler code here
char buf[]="http://www.sunxin.org";
DWORD dwWrite;
if(!WriteFile(hWrite,buf,strlen(buf)+1,&dwWrite,NULL))
{
MessageBox("写入数据失败!");
return;
}
}
d.再建一个Child的单文档,在View中增加两个成员hRead和hWrite.在OnInitialUpdate()中得到句柄的值。
void CChildView::OnInitialUpdate()
{
CView::OnInitialUpdate();
// TOD Add your specialized code here and/or call the base class
hRead=GetStdHandle(STD_INPUT_HANDLE);注意这句代码!
hWrite=GetStdHandle(STD_OUTPUT_HANDLE);
} e.加菜单“读取数据”“写入数据”其代码如下:
void CChildView::OnPipeRead()
{
// TOD Add your command handler code here
char buf[100];
DWORD dwRead;
if(!ReadFile(hRead,buf,100,&dwRead,NULL))
{
MessageBox("读取数据失败!");
return;
}
MessageBox(buf);
}void CChildView::OnPipeWrite()
{
// TOD Add your command handler code here
char buf[]="匿名管道测试程序";
DWORD dwWrite;
if(!WriteFile(hWrite,buf,strlen(buf)+1,&dwWrite,NULL))
{
MessageBox("写入数据失败!");
return;
}
}
3.命名管道:还可以跨网络通信,服务器只能在win2000和NT下运行!而客户端可以在95下运行。关键函数CreateNamedPipe
a.先建一个NamedPipeSRV单文档应用程序,加菜单“创建管道”“读取数据”“写入数据”
b.在View中增加Handle变量hPipe,注意在析构函数中释放它!
c.响应菜单,创建命名管道
void CNamedPipeSrvView::OnPipeCreate()
{
// TOD Add your command handler code here
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);
}void CNamedPipeSrvView::OnPipeRead()
{
// TOD Add your command handler code here
char buf[100];
DWORD dwRead;
if(!ReadFile(hPipe,buf,100,&dwRead,NULL))
{
MessageBox("读取数据失败!");
return;
}
MessageBox(buf);
}void CNamedPipeSrvView::OnPipeWrite()
{
// TOD Add your command handler code here
char buf[]="http://www.sunxin.org";
DWORD dwWrite;
if(!WriteFile(hPipe,buf,strlen(buf)+1,&dwWrite,NULL))
{
MessageBox("写入数据失败!");
return;
}
} d.再建一个NamedPipeCLT单文档工程,加菜单“连接管道”“读取数据”“写入数据”,当然别忘记成员变量hPipe的定义和初始化
e.响应菜单代码
void CNamedPipeCltView::OnPipeConnect()连接管道
{
// TOD Add your command handler code here
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;
}
}void CNamedPipeCltView::OnPipeRead()读取数据
{
// TOD Add your command handler code here
char buf[100];
DWORD dwRead;
if(!ReadFile(hPipe,buf,100,&dwRead,NULL))
{
MessageBox("读取数据失败!");
return;
}
MessageBox(buf);
}void CNamedPipeCltView::OnPipeWrite()写入数据
{
// TOD Add your command handler code here
char buf[]="命名管道测试程序";
DWORD dwWrite;
if(!WriteFile(hPipe,buf,strlen(buf)+1,&dwWrite,NULL))
{
MessageBox("写入数据失败!");
return;
}
}
4.邮槽,使用时应将消息长度限制在424字节以下,关键函数CreateMailSlot()
a.先建一个MailSlotSRV工程,加菜单“接收数据”
b.消息响应代码:
void CMailslotSrvView::OnMailslotRecv()菜单“接收数据”的代码
{
// TOD Add your command handler code here
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);
}
c.加工程MailSlotCLT,加菜单“发送数据”
d.加消息响应,添加代码,客户端也比较简单。
void CMailslotCltView::OnMailslotSend()菜单“发送数据”的代码
{
// TOD Add your command handler code here
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[]="http://www.sunxin.org";
DWORD dwWrite;
if(!WriteFile(hMailslot,buf,strlen(buf)+1,&dwWrite,NULL))
{
MessageBox("写入数据失败!");
CloseHandle(hMailslot);
return;
}
CloseHandle(hMailslot);
}
5.以上4种方法各有优缺点:剪贴板比较简单。邮槽是基于广播的,可以一对多发送。但只能一个发送,一个接收,要想同时发送接收,须写两次代码。
命名管道和邮槽可以进行网络通信。
剪切板
void CJieQieBanDlg::OnSend() { // TODO: Add your control notification handler code here if(OpenClipboard())//首先要打开剪切板 { CString str; HANDLE hChip; char *pBuf; EmptyClipboard();//清空剪切板 GetDlgItemText(ID_FASONG,str);//获取发送数据的内容 hChip=GlobalAlloc(GMEM_MOVEABLE,str.GetLength()+1);//分配空间,返回一个句柄 pBuf=(char*)GlobalLock(hChip);//上锁,返回一个字符指针 strcpy(pBuf,str);//复制内容 GlobalUnlock(hChip);//解锁 SetClipboardData(CF_TEXT,hChip);//设置剪切板的内容 CloseClipboard(); } }
void CJieQieBanDlg::OnRecv() { // TODO: Add your control notification handler code here if(OpenClipboard())//打开剪切板 { if(IsClipboardFormatAvailable(CF_TEXT))//判断剪切板的内容格式是否是我们需要的 { HANDLE hChip; char *pBuf; hChip=GetClipboardData(CF_TEXT);//返回一个句柄 pBuf=(char*)GlobalLock(hChip);//获取内容 GlobalUnlock(hChip); SetDlgItemText(ID_JIESHOU,pBuf);//设置内容 CloseHandle(hChip); } } }
匿名管道:
1父进程:
a添加两个CXXView成员变量HANDLE hRead,HANDLE hWrite
b构造函数赋值hRead=NULL,hWrite=NULL,析构函数if(!hRead) CloseHandle(hRead)...
c
void CParentView::OnPipeCreate() { // TODO: Add your command handler code here SECURITY_ATTRIBUTES sa; sa.bInheritHandle=TRUE; sa.lpSecurityDescriptor=NULL; sa.nLength=sizeof(SECURITY_ATTRIBUTES); if(!CreatePipe(&hRead,&hWrite,&sa,0)) { MessageBox("创建管道失败"); return ; } PROCESS_INFORMATION pi; STARTUPINFO sui; 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(hWrite); CloseHandle(hRead); hRead=NULL; hWrite=NULL; MessageBox("子进程创建失败"); return ; } else { CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } }
d
void CParentView::OnXieru() { // TODO: Add your command handler code here char temp[]="hello,i am writing..."; unsigned long len; if(!WriteFile(hWrite,temp,strlen(temp)+1,&len,NULL)) { MessageBox("写入失败"); return ; } }
e
void CParentView::OnDuqu() { // TODO: Add your command handler code here char temp[100]; unsigned long len; if(!ReadFile(hRead,temp,100,&len,NULL)) { MessageBox("读取失败"); return ; } MessageBox(temp); }
子进程
a,CXXVIEW中添加成员变量HANDLE hRead,HANDLE hWrite,在构造函数和析构函数进行响应的操作
bCXXView中添加虚函数OnInitialUpdate
void CChildView::OnInitialUpdate() { CView::OnInitialUpdate(); // TODO: Add your specialized code here and/or call the base class hRead=GetStdHandle(STD_INPUT_HANDLE); hWrite=GetStdHandle(STD_OUTPUT_HANDLE); }
void CChildView::OnXieru() { // TODO: Add your command handler code here char temp[]="hello,wel to my blog,i am afei"; unsigned long len; if(!WriteFile(hWrite,temp,strlen(temp)+1,&len,NULL)) { MessageBox("写入数据失败"); return ; } }
void CChildView::OnDuqu() { // TODO: Add your command handler code here char temp[100]; unsigned long len; if(!ReadFile(hRead,temp,100,&len,NULL)) { MessageBox("读取数据失败"); return ; } MessageBox(temp); }
先启动父进程,然后通过父进程建立子进程,然后就能实现父子进程之间的通信了。
命名管道:(可跨网络)
1服务器端:
a,CXXView中添加成员变量HANDLE hPipe
b,
void CNamePipeSerView::OnPipeCreate() { // TODO: Add your command handler code here 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=CreateEvent(NULL,TRUE,FALSE,NULL); if(!hEvent) { MessageBox("对象事件创建失败"); return ; } OVERLAPPED oa; ZeroMemory(&oa,sizeof(OVERLAPPED)); oa.hEvent=hEvent; if(!ConnectNamedPipe(hPipe,&oa)) { if(ERROR_IO_PENDING!=GetLastError()) { MessageBox("命名管道连接失败"); CloseHandle(hEvent); CloseHandle(hPipe); hEvent=NULL; hPipe=NULL; return ; } } if(WAIT_FAILED==WaitForSingleObject(hEvent,INFINITE)) { MessageBox("对象等待失败"); CloseHandle(hEvent); CloseHandle(hPipe); hEvent=NULL; hPipe=NULL; return ; } }
void CNamePipeSerView::OnXieru() { // TODO: Add your command handler code here char temp[]="hello,i am afei"; unsigned long len; if(!WriteFile(hPipe,temp,strlen(temp)+1,&len,NULL)) { MessageBox("写入文件失败"); return ; } }
void CNamePipeSerView::OnDuqu() { // TODO: Add your command handler code here char temp[100]; unsigned long len; if(!ReadFile(hPipe,temp,100,&len,NULL)) { MessageBox("读取文件失败"); return ; } MessageBox(temp); }
客户端:
void CNamePipeCliView::OnLianjie() { // TODO: Add your command handler code here 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 ; } }
void CNamePipeCliView::OnXieru() { // TODO: Add your command handler code here char temp[]="i love C++,MFC..."; unsigned long len; if(!WriteFile(hPipe,temp,strlen(temp)+1,&len,NULL)) { MessageBox("写入文件失败"); return ; } }
void CNamePipeCliView::OnDuqu() { // TODO: Add your command handler code here char temp[100]; unsigned long len; if(!ReadFile(hPipe,temp,100,&len,NULL)) { MessageBox("读取文件失败"); return ; } MessageBox(temp); }
这样就可以实现两个进程之间的通信了,
邮槽:(一对多)
1服务器端(接收数据)
void CYouCaoSerView::OnJieshou() { // TODO: Add your command handler code here HANDLE hMail; hMail=CreateMailslot("\\\\.\\mailslot\\mymail",0,MAILSLOT_WAIT_FOREVER,NULL); if(INVALID_HANDLE_VALUE==hMail) { MessageBox("创建油槽失败"); return ; } char temp[200]; DWORD dwlen; if(!ReadFile(hMail,temp,200,&dwlen,NULL)) { CloseHandle(hMail); MessageBox("读取失败"); return ; } CloseHandle(hMail); MessageBox(temp); }
2客户端:(发送数据)
void CYouCaoCView::OnFasong() { // TODO: Add your command handler code here HANDLE hMail; hMail=CreateFile("\\\\.\\mailslot\\mymail",GENERIC_WRITE,FILE_SHARE_READ,NULL, OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if(INVALID_HANDLE_VALUE==hMail) { MessageBox("打开油槽失败"); return ; } char temp[]="hello,welcome to my blog,i am afei"; DWORD dwlen; if(!WriteFile(hMail,temp,strlen(temp)+1,&dwlen,NULL)) { CloseHandle(hMail); MessageBox("写入数据失败"); return ; } CloseHandle(hMail); }
要先打开服务器端的接收消息,这样客户端的发送消息才能打开油槽
要实现可以发送又可以接收的油槽运用程序,可以在同一个程序中,既编写客户端又编写服务器