Windows平台为我们提供了多种进程间通信的机制,主要包括:注册表方式、共享文件方式、共享内存方式、共享数据段、映射文件方式、管道方式、 剪贴板方式、消息方式。其中注册表方式需要增加注册表表项,而注册表一般不应删改,所以此种方式不被推荐;共享数据段需要借助动态链接库,实现起来比较麻 烦,这种方式也不被推荐。下面重点介绍一下其它几种进程间通信的实现方式。
1.共享文件方式
(1)数据发送
数据发送进程为通过Cfile类创建一个共享文件,然后调用Write()方法想文件中写入数据,具体如下:
void CSendDlg::OnSend()
{
//TODO: 在此添加控件通知处理程序代码
UpdateData(TRUE); //更新数据
CFile file;
CString filename = _T("C:\test.txt");
if (file.Open(filename , CFile::modeCreate | CFile::modeWrite | CFile::shareDenyRead))
{
char * buf = (char*)(LPCTSTR)m_strsend;
file.Write(buf, strlen(buf));
file.Close();
}
else
{
MessageBox(_T("创建文件失败!"));
}
}
(2)数据接收
数据在接收进程中,通过Cfile类打开以上创建的共享文件,然后调用Read方法读取数据,具体代码如下:
void CRecieveDlg::Onrecieve()
{
//TODO: 在此添加控件通知处理程序代码
CFile file;
CString filename = _T("C:\test.txt");
if (file.Open(filename , CFile::modeRead|CFile::shareDenyWrite))
{
char Buf[100]={0};
file.Read(Buf,100);
m_strrecieve=Buf;
file.Close();
}
else
{
MessageBox(_T("打开文件失败!"));
}
UpdateData(FALSE); //更新数据
}
2.共享内存方式
通 过内存来传递数据,必须在内存中申请一定的空间。可以调用GlobalAlloc()或者VirtualAllocEx()来实现内存空间分配,使用内存 读写函数ReadProcessMemory()和WriteProcessMemory()来读写进程的内存。要使接收程序获得发送程序的内存地址,可 以通过发送消息方法来实现,即通过消息把内存地址从发送程序传递到接收程序。
(1)数据发送
首先要使用发送消息的方法来传递指针,就需要定义一个用户消息。可用如下的自定义消息来传递指针:
const UINT wm_nMemMsg=RegisterWindowMessage("mem_data");
寻 找接收数据的程序Recieve的窗口指针pWnd和进程句柄hProcess,用VirtualAllocEx()函数在这个进程中申请虚拟内存空间。 然后通过WriteProcessMemory()把字符串m_strsend存放入虚拟内存中,并且通过消息wm_nMemMsg把所申请的内存空间起 始地址发送给数据接收程序。最后,当数据接收程序接收到数据后,用VirtualFreeEx()释放所申请的虚拟内存。
数据发送函数具体代码如下:
void CSendDlg::OnSend()
{
//TODO: 在此添加控件通知处理程序代码
UpdateData(TRUE); //更新数据
CWnd *pWnd=CWnd::FindWindow(NULL,_T("Recieve")); //查找Recieve进程
if(pWnd==NULL){
MessageBox(_T("寻找接收消息窗口失败!"));
return;
}
DWORD PID; //获取进程号
GetWindowThreadProcessId(pWnd->m_hWnd, (DWORD*)&PID );
HANDLE hProcess = OpenProcess (PROCESS_ALL_ACCESS,FALSE,PID);
LPVOID lpBaseAddress; //分配虚拟内存
lpBaseAddress = VirtualAllocEx(hProcess, 0, BUFFER_SIZE,
MEM_COMMIT, PAGE_READWRITE);
char [BUFFER_SIZE];
strcpy(data,m_strsend);
//把字符串写入hProcess进程的内存
WriteProcessMemory(hProcess, lpBaseAddress, data, BUFFER_SIZE, NULL);
//发送基址给Recieve进程
pWnd->SendMessage(wm_nMemMsg,NULL,(LPARAM)lpBaseAddress);
Sleep(100); //等待接收程序接收数据
VirtualFreeEx(hProcess,lpBaseAddress, 0, MEM_RELEASE); //释放虚拟内存
}
(2)数据接收
首先需要定义一个用户消息,如下代码所示:
const UINT wm_nMemMsg=RegisterWindowMessage("mem_data");
然后在头文件中添加消息映射函数定义:
afx_msg void OnRegMemMsg(WPARAM wParam,LPARAM lParam);
接着需要定义wm_nMemMsg消息映射,它在消息映射表中的表示方法如下:
BEGIN_MESSAGE_MAP(CDataRecvDlg, CDialog)
ON_REGISTERED_MESSAGE(wm_nMemMsg,OnRegMemMsg)
END_MESSAGE_MAP()
最后在源文件中添加实现消息映射函数,具体代码如下:
LRESULT CRecieveDlg::OnRegMemMsg(WPARAM wParam,LPARAM lParam)
{
//TODO: 在此添加控件通知处理程序代码
LPVOID lpBaseAddress=(LPVOID)lParam;
HANDLE hProcess=GetCurrentProcess(); //把字符串写入hProcess进程的内存
char data[BUFFER_SIZE];
ReadProcessMemory(hProcess, lpBaseAddress, data,BUFFER_SIZE, NULL);
m_strrecieve=data;
UpdateData(FALSE); //更新数据
return 0;
}
3.映射文件方式
在Windows中,单个计算机上共享数据的底层机制是内存映射文件。如果互相通信的进程都在同一台计算机上,上面提到的所有机制均使用内存映射文件实现。如果想要达到较高的性能和较小的开销,内存映射文件将是最佳的实现机制。
内 存映射文件是通过两个或多个进程映射同一个文件映射对象的视图来实现的,这意味着它们将共享物理存储器的同一个页面。因此,当一个进程将数据写入一个共享 文件映射对象的视图时,其他进程可以立即看到它们视图中的数据变更情况。如果多个进程共享单个文件映射对象,那么所有进程必须使用相同的名字来表示该文件 映射对象。
(1)数据发送
在数据发送进程中,程序应首先调用CreateFileMapping()函数创建一个命名的内存 映射对象,得到相应内存起始位置指针lhShareMemory。如果打开成功,则调用MapViewOfFile()函数映射对象的一个视图,得到指向 映射到内存的第一个字节的指针lpBuffer并通过该指针读写共享的内存区域。最后使用UnmapViewOfFile()函数来解除视图映射,传入参 数为lpBuffer,具体代码如下:
void CSendDlg::OnSend()
{
UpdateData(TRUE);
HANDLE lhShareMemory;
char* lpBuffer = NULL;
//创建一个有名的共享内存
lhShareMemory = CreateFileMapping(
HANDLE(0xFFFFFFFF), //0xFFFFFFFF表示创建一个进程间共享的对象
NULL,
PAGE_READWRITE, //读写共享
0,
100, //共享区间大小
"mySharedMemory"); //映射文件名,即共享内存的名称
if (NULL == lhShareMemory)
{
if (ERROR_ALREADY_EXISTS == GetLastError())
{
MessageBox(_T("Already exists!"));
}
else
{
MessageBox(_T("Create Sheared Memory unsuccessfully!"));
}
return;
}
//映射到本进程的地址空间
lpBuffer = (char*)MapViewOfFile(lhShareMemory, FILE_MAP_WRITE, 0, 0, 100);
if (NULL == lpBuffer)
{
MessageBox(_T("Get Share memory unsuccessfully!"));
return;
}
strcpy(lpBuffer, (char*)(LPCTSTR)m_strsend);
UnmapViewOfFile(lpBuffer); //取消本进程地址空间的映射
lpBuffer = NULL;
}
(2)数据接收
在 数据接收进程中,首先调用OpenFileMapping()函数打开一个命名的内存映射文件对象,得到相应内存起始位置指针 lhShareMemory。如果打开成功,则调用MapViewOfFile()函数映射对象的一个视图,得到指向映射到内存的第一个字节的指针 lpcBuffer并通过该指针读写共享的内存区域。最后调用UnmapViewOfFile()函数来解除视图映射,传入参数为lpcBuffer,调 用CloseHandle()函数来关闭内存映射文件,传入参数为lhShareMemory,具体代码如下:
void CRecieveDlg::Onrecieve()
{
HANDLE lhShareMemory;
char* lpcBuffer; //获得共享内存句柄
lhShareMemory = OpenFileMapping(FILE_MAP_READ, false, "mySharedMemory");
if (NULL == lhShareMemory)
{
MessageBox(_T("Open share memory unsuccessfully!"));
return;
}
lpcBuffer = (char*)MapViewOfFile(lhShareMemory, FILE_MAP_READ, 0, 0, 100);
if (NULL == lpcBuffer)
{
MessageBox(_T("Open share memory unsuccessfully!"));
return;
}
m_strrecieve = lpcBuffer;
UnmapViewOfFile(lpcBuffer) //取消本进程地址空间的映射;
lpcBuffer=NULL;
CloseHandle(lhShareMemory)
UpdateData(FALSE);
}
4.管道方式
管 道的类型有两种:匿名管道和命名管道。匿名管道是不命名的,它最初用于本地系统中父进程与它启动的子进程之间的通信。命名管道则高级一些,通过一个名字进 行标识,使客户端和服务端应用程序可以通过该管道进行通信。Win32命名管道甚至可以在不同系统的进程间使用,这使它成为许多客户/服务器应用程序的理 想之选。
现在我们用命名管道实现进程间的通信,具体实现过程如下。
(1)创建命名管道,具体代码如下
void CSendDlg::OnPipeCreate()
{
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);
}
(2)读取数据,对于命名管道的数据读取操作,与上面匿名管道的读取操作是一样的,代码如下
void CSendDlg::Onrecieve()
{
char buf[100];
DWORD dwRead;
if(!ReadFile(hPipe,buf,100,&dwRead,NULL))
{
MessageBox(_T("读取数据失败!"));
return;
}
m_strrecieve=buf;
UpdateData(FALSE);
}
(3)写入数据,对于命名管道的数据写入操作,与上面匿名管道的写入操作也是相同的,代码如下
void CSendDlg::OnSend()
{
UpdateData(TRUE);
char * buf = (char*)(LPCTSTR)m_strsend;
DWORD dwWrite;
if(!WriteFile(hPipe,buf,strlen(buf)+1,&dwWrite,NULL))
{
MessageBox("写入数据失败!");
return;
}
}
(4) 连接命名管道。客户端在连接服务器端程序创建的命名管道之前,首先应判断一下,是否有可以利用的命名管道,这可以通过调用WaitNamedPipe() 函数实现,该函数会一直等待,直到指定的超时间隔已过,或者指定了命名管道的实例可以用来连接了,也就是说该管道的服务器进程有了一个未决的 ConnectNamedPipe操作。如果当前命名管道的实例可以使用,那么客户端就可以调用CreateFile函数打开这个命名管道,与服务端进程 进行通信了。客户端的连接命名管道的代码如下
void CRecieveDlg::OnPipeConnect()
{
if(!WaitNamedPipe(_T("\\.\pipe\MyPipe"),NMPWAIT_WAIT_FOREVER))
{
MessageBox(_T("当前没有可利用的命名管道实例!"));
return;
}
hPipe=CreateFile(_T("\\.\pipe\MyPipe"),GENERIC_READ | GENERIC_WRITE,
0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(INVALID_HANDLE_VALUE==hPipe)
{
MessageBox(_T("打开命名管道失败!"));
hPipe=NULL;
return;
}
}
(5)读取数据。如果客户端成功打开了指定的命名管道,那么就可以进行读取和写入操作了,具体代码如下
void CRecieveDlg::Onrecieve()
{
char buf[100];
DWORD dwRead;
if(!ReadFile(hPipe,buf,100,&dwRead,NULL))
{
MessageBox(_T("读取数据失败!"));
return;
}
m_strrecieve=buf;
UpdateData(FALSE);
}
(6)写入数据。客户端向命名管道写入数据与上面服务器端向命名管道写入数据一样,具体代码如下
void CRecieveDlg::OnSend()
{
UpdateData(TRUE);
char * buf = (char*)(LPCTSTR)m_strsend;
DWORD dwWrite;
if(!WriteFile(hPipe,buf,strlen(buf)+1,&dwWrite,NULL))
{
MessageBox("写入数据失败!");
return;
}
}
5.剪贴板方式
Windows 剪贴板是一种比较简单同时也是开销比较小的IPC(进程间通信)机制。Windows系统支持剪贴板IPC的基本机制是由系统预留的一块全局共享内存,用 来暂存各个进程间进行交换的数据。提供数据的进程创建一个全局内存块中,并将要传送的数据移到或复制到该内存块;而接受数据的进程(也可以是提供数据的进 程本身)获取此内存块的句柄,并完成对该内存块数据的读取。
(1)数据发送
在数据放到剪贴板前,首先需要打开剪切板,这里可 以用CWnd类的OpenClipboard()成员函数实现。打开剪切板后,调用EmptyClipboard()函数让打开剪切板的当前窗口拥有剪切 板。调用SetClipboardData()函数向剪切板中放置数据。调用SetClipboardData()函数之后,系统就拥有了hMen参数所 标识的数据对象,该应用程序可以读取这个数据对象。如果hMen参数标识了一个内存对象,那么这个对象必须是利用GMEM_MOVEABLE标志调用 GlobalAlloc函数为其分配内存的。向剪切板发送数据的具体代码如下
void CSendDlg::OnSend()
{
UpdateData(TRUE);
if(OpenClipboard())
{
HANDLE hClip;
char *pBuf;
EmptyClipboard();
hClip=GlobalAlloc(GMEM_MOVEABLE,m_strsend.GetLength()+1);
pBuf=(char*)GlobalLock(hClip); //将句柄转换为指针
strcpy(pBuf,m_strsend);
GlobalUnlock(hClip);
SetClipboardData(CF_TEXT,hClip);
CloseClipboard();
}
else
{
MessageBox(_T("打开剪切板失败!"));
}
}
(2)数据接收
从剪切板中接收数据的具体代码如下
void CRecieveDlg::Onrecieve()
{
if(OpenClipboard())
{
if(IsClipboardFormatAvailable(CF_TEXT))
{
HANDLE hClip;
char *pBuf;
hClip=GetClipboardData(CF_TEXT);
pBuf=(char*)GlobalLock(hClip);
GlobalUnlock(hClip);
m_strrecieve = pBuf;
CloseClipboard();
}
}
else
{
MessageBox(_T("打开剪切板失败!"));
}
UpdateData(FALSE);
}
6.消息方式
消息是Windows操作系统提供的一种驱动机制。利用消息进行进程通信,就是使用消息激活某种操作的过程。对于进程间的通信,一般采用用户自定义的消息来完成;如果要实现的是Windows定义的消息功能,则可以使用已定义的消息。
(1)数据发送
在数据发送进程中,调用FindWindow()函数根据窗口的标题或者接收窗体的类名搜索窗口,然后给COPYDATASTRUCT结构赋值,最后调用SendMessage()函数发送消息,具体代码如下
void CSendDlg::OnSend()
{
UpdateData(TRUE);
CWnd *pWnd=CWnd::FindWindow(NULL,_T("Recieve")); //查找DataRecv进程
if(pWnd==NULL)
{
MessageBox(_T("寻找接收窗口失败!"));
return;
}
COPYDATASTRUCT cpd; //给COPYDATASTRUCT结构赋值
cpd.dwData = 0;
cpd.cbData = m_strsend.GetLength();
cpd.lpData = (void*)m_strsend.GetBuffer(cpd.cbData);
pWnd->SendMessage(WM_COPYDATA,NULL,(LPARAM)&cpd); //发送
}
(2)数据接收
实现数据接收的代码如下
BOOL CRecieveDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
m_strrecieve=(LPSTR)pCopyDataStruct->lpData;
m_strrecieve=m_strrecieve.Left(pCopyDataStruct->cbData); //获得实际长度的字符串
UpdateData(FALSE); //更新数据
return CDialog::OnCopyData(pWnd, pCopyDataStruct);
}