windows 进程间通讯方法
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);
}
windows 进程间通讯方法的更多相关文章
- Windows进程间通讯(IPC)----消息队列
消息队列 windows系统是通过消息驱动的,每移动一下鼠标,点击一下屏幕都会产生一个消息.这些消息会先被放在windows的一个系统消息队列(先进先出)中,windows系统会为每一个GUI线程创建 ...
- Windows进程间通讯(IPC)----共享内存
Windows中同一个EXE文件多次加载过程 Windows中EXE文件加载是基于内存映射文件的. 当EXE文件第一次被加载. 首先系统会先创建一个进程内核对象,并创建一个新的进程地址空间. 系统调用 ...
- Windows进程间通讯(IPC)----内存映射文件
内存映射文件原理 内存映射文件是通过在虚拟地址空间中预留一块区域,然后通过从磁盘中已存在的文件为其调度物理存储器,访问此虚拟内存空间就相当于访问此磁盘文件了. 内存映射文件实现过程 HANDLE hF ...
- Windows进程间通讯(IPC)----WM_COPYDATA
WM_COPYDATA通讯思路 通过向其他进程的窗口过程发送WM_COPYDATA消息可以实现进程间通讯. 只能通过SendMessage发送WM_COPYDATA消息,而不能通过PostMessag ...
- windows进程间通讯的方法
版权声明 请尊重原创作品.转载请保持文章完整性,并以超链接形式注明原始作者“tingsking18”和主站点地址,方便其他朋友提问和指正. 1.使用共享内存 代码如下: void FileMapp ...
- Windows进程间通讯(IPC)----管道
管道的分类 管道其实际就是一段共享内存,只不过Windows规定需要使用I/O的形式类访问这块共享内存,管道可以分为匿名管道和命名管道. 匿名管道就是没有名字的管道,其支持单向传输数据,如果需要双向传 ...
- Windows进程间通讯(IPC)----信号量
线程同步内核对象 操作系统进行进程间同步是利用信号量机制.对于windows系统而言,可以利用一些内核对象进行线程同步,因为这些内核对象可以命名并且属于系统内核,所以可以支持不同进程间的线程同步进而实 ...
- Windows进程间通讯(IPC)----套接字
Windows套接字 Windows套接字即socket,通过socket可以实现在不同的进程间通信,甚至这两个进程可以不在同一个计算机中. Winsock使用步骤 服务端 socket初始化 创建套 ...
- UNIX环境高级编程——进程间通讯方法整理
一.无名管道pipe #include <unistd.h> int pipe(int fd [2]) 二.fifo #include <sys/stat.h> int mkf ...
随机推荐
- Json 返回日期格式转换
//日期转换 function ChangeDateFormat(time) { if (time != null) { var date = new Date(parseInt(time.repla ...
- OA学习笔记-002-Sruts2.1配置
一.jar commons-fileupload-1.2.1.jarcommons-io-1.3.2.jarfreemarker-2.3.15.jarognl-2.7.3.jarstruts2-cor ...
- leetcode面试准备: CountPrimes
1 题目 Description: Count the number of prime numbers less than a non-negative number, n. 接口:public in ...
- 行为树实现AI逻辑
http://blog.csdn.net/kenkao/article/details/6099966 http://www.aisharing.com/archives/99 http://www. ...
- 转载:MyEclipse中防止代码格式化时出现换行的情况的设置
转载出处:http://www.cnblogs.com/yjhrem/articles/2310013.html 编辑完成代码,用MyEclipse的代码格式化后,本来不长的代码也被自动转成了多行. ...
- 【HDOJ】3726 Graph and Queries
Treap的基础题目,Treap是个挺不错的数据结构. /* */ #include <iostream> #include <string> #include <map ...
- bzoj2209 2329
括号序列的经典做法把(看成1,)看成-1匹配的括号序列即任意前缀和都非负我们先解决静态的问题,给定一段括号序列求最少修改次数我们先找出最大后缀和a和最小前缀和b之间一定可以不相交显然a+|b|个括号是 ...
- 学以致用:让visualstudio爱上sublime
前言: 经常在vs中工作,但是一些编辑工作却非常喜欢sublime的方式,如果你也是,那我们来当媒婆吧,哈哈. 准备: Visualstudio一枚 Sublime一枚 ...
- POJ_3579_Median_(二分,查找第k大的值)
描述 http://poj.org/problem?id=3579 给你一串数,共C(n,2)个差值(绝对值),求差值从大到小排序的中值,偶数向下取. Median Time Limit: 1000M ...
- JW Player 现在支持 Azure 媒体服务
Vishal Sood Azure媒体服务首席项目经理 此合作伙伴关系是关于什么内容? Azure媒体服务现已支持一些最常见的流媒体格式,其中包括 Microsoft SmoothStreaming ...