1.当一个进程启动后,操作系统为其分配4GB的私有地址空间,位于同一进程中的多个线程共享同一个地址空间,因此线程间的通信非常简单,但因为进程地址空间都是私有的,所以进程间通信比较困难
(1)共享内存(剪贴板)
(2)匿名管道
(3)命名管道
(4)邮槽
(5)消息
2.剪贴板
(1)剪贴板实际上是系统维护管理的一块内存区域,当在一个进程中复制数据时,是将这个数据放到该块内存区域中,当在另一个进程中粘贴数据时,是从该块内存区域中取出数据,然后显示在窗口上
(2)数据发送
1.打开剪贴板:CWnd类的OpenClipboard成员函数
函数声明:BOOL OpenClipboard();//如果某个程序打开了剪贴板,则其他应用程序将不能修改剪贴板,直到前者关闭剪贴板(CloseClipboard)并且只有调用了EmptyClipboard()函数后,打开剪贴板的当前窗口才拥有剪贴板
2.向剪贴板上放置内容:SetClipboardData函数(这个函数是以指定的剪贴板格式向剪贴板上放置数据)
函数原型:HANDLE SetClipboardData(UINT uFormat,HANDLE hMem);
// uFormat指定剪贴板格式,这个格式可以是已注册的格式,或者是任一种标准的剪贴板格式(eg:CF_TEXT)
// hMem:具有指定格式的数据的句柄,该参数若为NULL,指示调用窗口直到有对剪贴板数据的请求时,才提供指定剪贴板格式的数据,如果窗口采用延迟提交技术,则该窗口必须处理WM_RENDERFORMAT和WM_RENDERALLFORMATS消息
3.如果hMenu参数标识了一个内存对象,那么这个对象必须是利用GMEM_MOVEABLE标识调用GlobalAlloc函数为其分配内存
HGLOBAL GlobalAlloc(UINT uFlags,SIZE_T dwBytes);
// dwBytes指定分配的字节数
// uFlags用来指定分配内存的方式
GMEM_MOVEABLE:分配一块可移动的内存(返回值是一块内存对象句柄,若要转换为一个指针需要调用GlobalLock函数将内存锁定,每一次调用GlobalLock函数后,最后一定要调用GlobalUnlock函数(GlobalRealloc函数将重新分配)) LPVOID GlobalLock(HGLOBAL hMem);
GMEC_FIXED:分配一块固定的内存(返回值是一个指针)
4.示例:
void CClipboardDlg::OnBtnSend()
{
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();//关闭剪贴板(一定要记得关闭,否则其他程序无法使用剪贴板)
}
}
(3)数据接收
1.示例:
void CClipboardDlg::OnBtnRecv()
{
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.在获取数据之前,应该查看一下剪贴板中是否有我们想要的特定格式的数据,这可以通过调用IsClipboardFormatAvailable函数实现
函数原型:BOOL IsClipboardFormatAvailable(UINT format);
3.从剪贴板上获得数据:GetClipboardData函数
函数原型:HANDLE GetClipboardData(UINT uFormat);
(要获取数据还要把句柄转化为指针)
3.匿名管道
(1)匿名管道是一个未命名的、单向的管道,通常用来在一个父进程和一个子进程之间传输数据,匿名管道只能实现本地机器上两个进程间的通信
(2)创建匿名管道:CreatePipe函数
函数原型:BOOL CreatePipe(
PHANDLE hReadPipe,//out型,作为返回值使用,返回管道的读取句柄
PHANDLE hWritePipe,//out型,作为返回值使用,返回管道的写入句柄
LPSECURITY_ATTRIBUTES lpPipeAttributes,//安全属性,在此处不能使用默认的安全描述符(NULL),因为子进程要或的匿名管道的句柄只能从父进程继承而来,所以必须构造一个SECURITY_ATTRIBUTES结构体变量
DWORD nSize//指定管道的缓冲区大小,若为0系统提供默认值
);
typedef struct _SECURITY_ATTRIBUTES{
DWORD nLength;//该结构体大小
LPVOID lpSecurityDescriptor;//指向安全描述符的指针(NULL)
BOOL bInheritHandle;//该成员指定所返回的句柄能否被一个新的进程所继承,TRUE:能被继承
}SECURITY_ATTRIBUTES,*PSECURITY_ATTRIBUTES;
(3)进程的创建:CreateProcess函数
函数原型:BOOL CreateProcess(
LPCTSTR lpApplicationName,//指向字符串,用来指定可执行程序的名称,该名称可以是该程序的完整路径和文件名,也可以是部分名称(当前路径搜索)可以为NULL注意:一定要加上扩展名,系统不会自动加.exe
LPTSTR lpCommandLine,//指向字符串,用来指定传递给新进程的命令行字符串,(可以将文件名和命令行参数构造成一个字符串,一并传给这个参数)可以为空
LPSECURITY_ATTRIBUTES lpProcessAttributes,//设置新进程的进程对象安全性
LPSECURITY_ATTRIBUTES lpThreadAttributes,//设置新进程的线程对象安全性
BOOL bInheritHandles,//用来指定该进程创建的子进程能够继承父进程的对象句柄,TRUE:父进程的每个可继承的打开句柄都能被子进程继承
DWORD dwCreationFlags,//指定控件优先级类和进程创建的附加标记(0)(标识可以利用位运算组合)
LPVOID lpEnvironment,//一个指向环境块的指针,若为NULL,则新进程使用调用进程的环境,通常设为NULL
LPCTSTR lpCurrentDirectory,//指向字符串,用来规定子进程当前的路径,必须是一个完整的路径名,包括驱动器的标识符,若为NULL,则和父进程相同
LPSTARTUPINFO lpStartupInfo,//指向STARTUPINFO结构体的指针,用来指定新进程的主窗口如何显示,该结构体中的一些数据成员需要赋值
LPPROCESS_INFORMATION lpProcessInformation//这个参数作为返回值使用,用来接收有关于新进程的标识信息,PROCESS_INFORMATION结构体有四个成员:新建进程句柄;新建进程的主线程句柄;全局进程标识符;全局线程标识符
);
(4)父进程的实现
1.增加成员变量 HANDLE hWrite; HANDLE hRead;
2.构造函数中初始化 hRead = NULL; hWrite = NULL;
3.析构函数中关闭这两个变量
If(hRead) CloseHandle(hRead);
If(hWrite) CloseHandle(hWrite);
4.创建匿名管道
void CParentView::On1()
{
// 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("ERROR!");
return;
}
STARTUPINFO sui; //指定新进程的主窗口如何显示
ZeroMemory(&sui,sizeof(STARTUPINFO));
sui.cb = sizeof(STARTUPINFO);
sui.dwFlags = STARTF_USESTDHANDLES;
sui.hStdInput = hRead; //设置子进程的标准输入句柄,子进程调用GetStdHandle函数将得到该句柄
sui.hStdOutput = hWrite; //设置子进程的标准输出句柄
sui.hStdError = GetStdHandle(STD_ERROR_HANSLE);
PROCESS_INFORMATION pi; //接收有关于新进程的标识信息
if(!CreateProcess("..\\Child\\Debug\\Chile.exe",NULL,NULL,NULL,TRUE,0.NULL,NULL,&sui,&pi)
{
CloseHandle(hRead);
CloseHandle(HWrite);
hRead = NULL;
hWrite = NULL;
MessageBox("Fail");
return;
}
else
{
CloseHandle(pi.hProcess);
CloseHandle(pi.dwThreadId);
}
}
//ZeroMemory(&sui,sizeof(STARTUPINFO));将sui中所有成员全设置为0
//GetStdHandle函数:获得标准输入,标准输出,或者一个标准错误输出句柄
5. 管道的读取和写入:ReadFile和WriteFile
读取:void CParentView::On2()
{
// TODO: Add your command handler code here
char buf[100];
DWORD dwRead;
if(!ReadFile(hRead,buf,100,&dwRead,NULL))
{
MessageBox("FAIL");
return;
}
MessageBox(buf);
}
写入:void CParentView::On3()
{
// TODO: Add your command handler code here
char buf[] = "Pipe";
DWORD dwWrite;
if(!WriteFile(hWrite,buf,strlen(buf) + 1,&dwWrite,NULL))
{
MessageBox("Fail");
return;
}
}
(5)子进程的实现
1.增加成员变量 HANDLE hWrite; HANDLE hRead;
2.构造函数中初始化 hRead = NULL; hWrite = NULL;
3.析构函数中关闭这两个变量
if(hRead) CloseHandle(hRead);
if(hWrite) CloseHandle(hWrite);
4.获得管道的读取和写入句柄(即子进程的标准输入、输出句柄)
要在CChild类窗口完全创建成功后去获取,因此,我们可以为CChildView类增加虚函数OnInitialUpdate,这个函数是窗口创建成功后第一个调用的函数
void CChildView::OnInitialUpdate()
{
CView::OnInitialUpdate();
hRead = GetStdHandle(STD_INPUT_HANDLE);
hWrite = GetStdHandle(STD_OUTPUT_HANDLE);
}
5. 读取和写入:ReadFile和WriteFile
读取:
void CChildView::On1()
{
char buf[100];
DWORD dwRead;
if(!ReadFile(hRead,buf,100,&dwRead,NULL))
{
MessageBox("FAIL");
return;
}
MessageBox(buf);
}
写入:
void CChildView::On2()
{
char buf[] = "Pipe";
DWORD dwWrite;
if(!WriteFile(hWrite,buf,strlen(buf) + 1,&dwWrite,NULL))
{
MessageBox("Fail");
return;
}
}
(6)因为匿名管道没有名称,所以只能在父进程中创建子进程时,将管道的读、写句柄传递给子进程
4.命名管道
(1)命名管道通过网络来完成进程间的通信,它屏蔽了底层的网络协议细节。命名管道不仅可以在本机上实现两个进程间的通信,还可以跨网络实现两个进程间的通信
(2)在创建管道时,可以指定具有访问权限的用户
(3)将命名管道作为一种网络编程方案时,它实际上建立了一个客户机/服务器通信体系,并在其中可靠地传输数据。命名管道是围绕Windows文件系统设计的一种机制,采用“命名管道文件系统(NPFS)”接口。命名管道服务器和客户端的区别是:服务器是唯一一个有权创建命名管道的进程,也只有它才能接受管道客户机的连接请求,而客户机只能同一个现成的命名管道服务器建立连接
(4)命名管道提供了两种基本通信模式:字节模式和消息模式
字节模式:数据以一个连续的字节流的形式在客户机和服务器之间流动
消息模式:客户机和服务器通过一系列不连续的数据单位,进行数据的收发
(5)创建命名管道:CreateNamedPipe函数
函数原型:
HANDLE CreateNamedPipe(
LPCTSTR lpName,//指向字符串,格式必须是:\\.\pipe\pipename
其中:开始是两个连续的反斜杠,其后的圆点表示是本地机器,如果想与远程的服务器建立连接,那么在这个圆点位置处应指定远程服务器名,接下来是”pipe”这个固定的字符串,最后是所创建的命名管道的名称
DWORD dwOpenMode,//指定管道的访问方式、重叠方式、写直通方式、还有管道句柄的安全访问方式
DWORD dwPipeMode,//指定管道句柄的类型、读取和等待方式
管道句柄的类型:PIPE_TYPE_BYTE 和PIPE_TYPE_MESSAGE,同一命名管道的每一个实例必须具有相同的类型 默认字节型
读取方式:PIPE_READMODE_BYTE和PIPE_READMODE_MESSAGE
等待方式:PIPR_WAIT(阻塞)和PIPE_NOWAIT(非阻塞)
DWORD nMaxInstances,//指定管道能够创建的实例的最大数目,最大为:
PIPE_UNLIMITED_INSTANCES;对同一个命名管道的实例来说,在某一时刻,它只能和一个客户端进行通信
DWORD nOutBufferSize,//指定输出缓冲区所保留的字节数
DWORD nInBufferSize,//指定输入缓冲区所保留的字节数
DWORD nDefaultTimeOut,//指定默认的超时值,同一管道的不同实例必须指定同样的超时值
LPSECURITY_ATTRIBUTES lpSecurityAttributes//安全属性
);
(6)服务器端程序
1.为CNamedPipeSrv类增加一个句柄变量,保存命名管道实例句柄:HANDLE hPipe;
2.在构造函数中将其初始化为NULL hPipe = NULL;
3.在析构函数中关闭该句柄 if(hPipe) CloseHandle(hPipe);
4.创建命名管道
void CNamedPipeSrvView::OnPipeCreate()
{
//创建命名管道PIPE_ACCESS_DUPLEX 双向模式 FILE_FLAG_OVERLAPPED 允许重叠
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);
}
5.等待客户端请求的到来:ConnectNamedPipe,这个函数的作用是让服务器等待客户端的连接请求的到来
函数原型:BOOL ConnectNamedPipe(HANDLE hNamedPipe,LPOVERLAPPED lpOverlapped);
//第一个参数指向一个命名管道实例的服务器的句柄,该句柄由CreateNamedPipe返回
//第二个参数是一个指向OVERLAPPED结构的指针,如果hNamedPipe参数所标识的管道是用FILE_FLAG_OVERLAPPED标记打开的,则这个参数不能是NULL,必须是一个有效的指针,若这个参数不是NULL,则必须包含人工重置对象的句柄
6.读取数据:同匿名管道读取操作
7.写入数据:同匿名管道写入操作
(7)客户端程序
1.为CNamedPipeClt类增加一个句柄变量,保存命名管道实例句柄:HANDLE hPipe;
2.在构造函数中将其初始化为NULL hPipe = NULL;
3.在析构函数中关闭该句柄 if(hPipe) CloseHandle(hPipe);
4.连接命名管道
(1)判断是否由可用的命名管道:WaitNamedPipe函数,该函数会一直等待,知道指定的事件间隔已过,或者指定的命名管道的实例可以用来连接了
函数原型:BOOL WaitNamedPipe(LPCTSTR lpNamedPipeName,DWORD nTimeout);
//第一个参数指定命名管道的名称,这个名称必须包括创建该命名管道的服务器进程所在的机器名,格式为\\.\pipe\pipename,若跨网通信,则圆点位置应指定服务器程序所在主机名
//第二个参数指定超时间隔 NMPWAIT_WAIT_FOREVER 一直等待
(2)打开命名管道:CreateFile函数
void CNamedPipeCltView::OnPipeConnect()
{
//判断是否有可以利用的命名管道
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;
}
}
5. 读取数据:同匿名管道读取操作
6.写入数据:同匿名管道写入操作
5.邮槽
(1)邮槽是基于广播(一对多)通信体系设计出来的,它采用无连接的不可靠的数据传输,邮槽是一种单向通信机制,创建邮槽的服务器进程读取数据,打开邮槽的客户机进程写入数据(在Windows平台下,传输消息时,长度要在424字节一下)
(2)创建邮槽:CreateMailslot函数,该函数利用指定的名称创建一个邮槽,然后返回所创建的邮槽的句柄
函数原型:HANDLE CreateMailslot(
LPCTSTR lpName,//指定邮槽的名称,格式:”\\.\mailslot\[path]name”
DWORD nMaxMessageSize,//指定可以被写入邮槽的单一消息的最大尺寸,设为0可以发送任意大小的消息
DWORD lReadTimeout,//读取操作的超时时间间隔,设为0,则若无消息,立即返回;设为MAILSLOT_WAIT_FOREVER,则函数一直等待
LPSECURITY_ATTRIBUTES lpSecurityAttributes//安全属性
);
(3)服务器端程序
void CMailslotSrcView::OnMailslotRecv()
{
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);
}
(4)客户端程序
void CMailslotCltView::OnMailslotSend()
{
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[] = "hello";
DWORD dwWrite;
if(!WriteFile(hMailslot,buf,strlen(buf)+1,&dwWrite,NULL))
{
MessageBox("写入数据失败!");
CloseHandle(hMailslot);
return;
}
CloseHandle(hMailslot);
}
(5)邮槽可以实现一对多的单向通信
6.比较:邮槽容量小,管道容量较大
邮槽可以一对多,管道只能一对一
邮槽只能是单项的,管道可以双向
邮槽不可靠数据传输,管道可靠
剪贴板和匿名通道只能在本机通信,邮槽和命名管道可以跨网络