32位Windows采用虚拟内存技术使每个进程虚拟4G内存,在逻辑上实现了对进程之间数据代码的分离与保护。那么相应的进程之间的通信也就有必要整理掌握一下。

Windows进程间通讯的方法有很多:管道、邮件槽、剪切板、共享内存、消息、套接字、RPC、DDE等。

但是他们大部分拥有一个共同的本质:利用Windows操作系统高2GB内核共享空间进行数据传递的桥梁,所以他们都是内核对象!

所以他们大部分都要遵循:A创建对象-->A写入数据-->B打开A创建的对象-->B读入数据的规则

下面着重通过一些代码Demo来加深下对进程间通信的理解

0X01

命名管道

进程A代码

#define READ_PIPE   L"\\\\.\\pipe\\ReadPipe"
#define WRITE_PIPE L"\\\\.\\pipe\\WritePipe" // 管道命名 typedef struct _USER_CONTEXT_
{
HANDLE hPipe;
HANDLE hEvent;
}USER_CONTEXT,*PUSER_CONTEXT; USER_CONTEXT Context[2] = {0}; HANDLE hThread[2] = {0}; BOOL WritePipe();
BOOL ReadPipe(); BOOL bOk = FALSE; DWORD WINAPI WritePipeThread(LPVOID LPParam);
DWORD WINAPI ReadPipeThread(LPVOID LPParam);
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0;
HANDLE hPipe = NULL;
if (WritePipe()==FALSE)
{
return -1;
}
if (ReadPipe()==FALSE)
{ return -1;
} int iIndex = 0;
while (TRUE)
{
if (bOk==TRUE)
{
SetEvent(Context[0].hEvent);
SetEvent(Context[1].hEvent); Sleep(1);
} iIndex = WaitForMultipleObjects(2,hThread,TRUE,5000); if (iIndex==WAIT_TIMEOUT)
{
continue;
} else
{
break;
} } int i = 0;
for (i=0;i<2;i++)
{
CloseHandle(Context[i].hEvent);
CloseHandle(Context[i].hPipe);
}
CloseHandle(hThread[0]);
CloseHandle(hThread[1]);
cout<<"Exit"<<endl;
return nRetCode;
} BOOL WritePipe()
{
HANDLE hWritePipe = NULL; hWritePipe = CreateNamedPipe(
WRITE_PIPE,
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_MESSAGE |
PIPE_READMODE_MESSAGE |
PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
MAX_PATH,
MAX_PATH,
0,
NULL); if (hWritePipe==INVALID_HANDLE_VALUE)
{
return FALSE;
} HANDLE hEvent = CreateEvent(NULL,FALSE,FALSE,NULL); Context[0].hEvent = hEvent;
Context[0].hPipe = hWritePipe;
hThread[0] = CreateThread(NULL,0,WritePipeThread,NULL,0,NULL); return TRUE;
} BOOL ReadPipe()
{
HANDLE hReadPipe = NULL; hReadPipe = CreateNamedPipe(
READ_PIPE,
PIPE_ACCESS_DUPLEX,
PIPE_TYPE_MESSAGE |
PIPE_READMODE_MESSAGE |
PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
MAX_PATH,
MAX_PATH,
0,
NULL); if (hReadPipe==INVALID_HANDLE_VALUE)
{
return FALSE;
} HANDLE hEvent = CreateEvent(NULL,FALSE,FALSE,NULL); Context[1].hEvent = hEvent;
Context[1].hPipe = hReadPipe;
hThread[1] = CreateThread(NULL,0,ReadPipeThread,NULL,0,NULL); return TRUE; } DWORD WINAPI ReadPipeThread(LPVOID LPParam)
{ HANDLE hEvent = Context[1].hEvent;
HANDLE hReadPipe = Context[1].hPipe;
DWORD dwReturn = 0; char szBuffer[MAX_PATH] = {0};
int iIndex = 0;
while (TRUE)
{ iIndex = WaitForSingleObject(hEvent,30); iIndex = iIndex-WAIT_OBJECT_0; if (iIndex==WAIT_FAILED||iIndex==0)
{
break;
} if (ReadFile(hReadPipe,szBuffer,MAX_PATH,&dwReturn,NULL))
{
szBuffer[dwReturn] = '\0'; cout<<szBuffer<<endl;
}
else
{
if (GetLastError()==ERROR_INVALID_HANDLE)
{
break;
}
} } return 0;
} DWORD WINAPI WritePipeThread(LPVOID LPParam)
{
HANDLE hEvent = Context[0].hEvent;
HANDLE hWritePipe = Context[0].hPipe;
DWORD dwReturn = 0; char szBuffer[MAX_PATH] = {0};
int iIndex = 0;
while (TRUE)
{
iIndex = WaitForSingleObject(hEvent,30); iIndex = iIndex-WAIT_OBJECT_0; if (iIndex==WAIT_FAILED||iIndex==0)
{
break;
} cin>>szBuffer; if (WriteFile(hWritePipe,szBuffer,strlen(szBuffer),&dwReturn,NULL))
{ } else
{
if (GetLastError()==ERROR_INVALID_HANDLE)
{
break;
}
}
} return 0;
}

进程B代码

#define WRITE_PIPE   L"\\\\.\\pipe\\ReadPipe"
#define READ_PIPE L"\\\\.\\pipe\\WritePipe" HANDLE hThread[2] = {0}; DWORD WINAPI ReadPipeThread(LPARAM LPParam);
DWORD WINAPI WritePipeThread(LPARAM LPParam);
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{ HANDLE hReadPipe = NULL;
HANDLE hWritePipe = NULL; hThread[0] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ReadPipeThread,NULL,0,NULL);
hThread[1] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)WritePipeThread,NULL,0,NULL); WaitForMultipleObjects(2,hThread,TRUE,INFINITE); CloseHandle(hReadPipe);
CloseHandle(hWritePipe); CloseHandle(hThread[0]);
CloseHandle(hThread[1]);
cout<<"Exit"<<endl; return -1;
} DWORD WINAPI WritePipeThread(LPARAM LPParam)
{
HANDLE hWritePipe = NULL;
char szBuffer[MAX_PATH] = {0};
DWORD dwReturn = 0; while(TRUE)
{
hWritePipe = CreateFile(WRITE_PIPE,GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,OPEN_EXISTING,0,NULL); if (hWritePipe==INVALID_HANDLE_VALUE)
{
continue;
} break;
}
while (TRUE)
{ cin>>szBuffer;
if (WriteFile(hWritePipe,szBuffer,MAX_PATH,&dwReturn,NULL))
{ } else
{
if (GetLastError()==ERROR_NO_DATA)
{
cout<<"Write Failed"<<endl;
break;
}
} }
return 0;
} DWORD WINAPI ReadPipeThread(LPARAM LPParam)
{ HANDLE hReadPipe = NULL;
char szBuffer[MAX_PATH] = {0};
DWORD dwReturn = 0; while(TRUE)
{
hReadPipe = CreateFile(READ_PIPE,GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,OPEN_EXISTING,0,NULL); if (hReadPipe==INVALID_HANDLE_VALUE)
{
continue;
} break;
}
while (TRUE)
{
if (ReadFile(hReadPipe,szBuffer,MAX_PATH,&dwReturn,NULL))
{
szBuffer[dwReturn] = '\0';
cout<<szBuffer;
}
else
{
cout<<"Read Failed"<<endl;
break;
} }
return 0;
}

*其中进程A创建了管道内核对象,以及用于读写管道的双线程。B进程通过对象名打开了A创建的内核对象,同时也创建了双线程进行命名管道的读与写。

对于管道需要多说的是有一种管道是匿名管道,也就是不需要创建对象管道的名字。那么其他进程又是如何知道这个管道对象,从而实现对信息的传递的呢?

原来它是通过内核对象的可继承性进行的,也就是说匿名管道只能作用于父子进程之间,在父进程创建子进程的时候通过对CreateProcess函数中传参,即可让子进程获得父进程的内核对象句柄。

具体实现细节,请参考《Windows核心编程》内核对象一章。

0X02

邮件槽

进程A代码

#define  MAIL_SLOT_NAME  L"\\\\.\\mailslot\\Name" 

HANDLE  hReadMailSlot = INVALID_HANDLE_VALUE;
DWORD WINAPI ReadMail();
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0; HANDLE hReadThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ReadMail,NULL,0,NULL); Sleep(INFINITE); if (hReadMailSlot!=INVALID_HANDLE_VALUE)
{
CloseHandle(hReadMailSlot);
} Sleep(10); return nRetCode;
} DWORD WINAPI ReadMail()
{ hReadMailSlot = CreateMailslot(MAIL_SLOT_NAME,0,0,NULL); if (hReadMailSlot==INVALID_HANDLE_VALUE)
{
return -1;
} //查看油槽的信息 DWORD cbMessage = 0;
DWORD cMessage = 0;
BOOL bOk = FALSE;
char* szBuffer = NULL;
DWORD dwReturn = 0; while (TRUE)
{ bOk = GetMailslotInfo(hReadMailSlot,NULL,&cbMessage,&cMessage,NULL); if (bOk==FALSE)
{
break;
} if (cMessage==0)
{
continue;
} else
{
if (szBuffer!=NULL)
{
free(szBuffer); szBuffer = NULL;
} szBuffer = (char*)malloc(sizeof(char)*cbMessage+1); if (ReadFile(hReadMailSlot,
szBuffer,
cbMessage,
&dwReturn,
NULL)==TRUE)
{ szBuffer[dwReturn] = '\0';
if (strcmp(szBuffer,"Exit")==0)
{
break;
} cout<<szBuffer<<endl;
} }
} cout<<"ReadThread Exit"<<endl;
}

进程B代码

#define  MAIL_SLOT_NAME  L"\\\\.\\mailslot\\Name"
int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0; HANDLE hWriteMailSlot = NULL; while(TRUE)
{
hWriteMailSlot = CreateFile(MAIL_SLOT_NAME,GENERIC_READ|GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,NULL); if (hWriteMailSlot==INVALID_HANDLE_VALUE)
{
continue;
}
else
{
break;
} } DWORD dwReturn = 0;
char szBuffer[1024] = {0};
while (TRUE)
{ cin>>szBuffer;
if (strcmp(szBuffer,"Exit")==0)
{
break;
} WriteFile(hWriteMailSlot,szBuffer,strlen(szBuffer),&dwReturn,NULL); } WriteFile(hWriteMailSlot,szBuffer,strlen(szBuffer),&dwReturn,NULL);
CloseHandle(hWriteMailSlot); return nRetCode;
}

*邮件槽的实现和命名管道大同小异,都是A创建对象-->A写入数据-->B打开A创建的对象-->B读入数据。以前一直认为邮件槽是Windows与Linux共有的机制,自从某次上Liunx课和老师讨论了一会进程间通信的问题,

才愚蠢的知道Linux并没有邮件槽这个机制。

0X03

共享内存

进程A代码

using namespace std;

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0; char szBuffer[] = "Shine"; HANDLE hMapping = CreateFileMapping(NULL,NULL,PAGE_READWRITE,0,4096,L"ShareMemory"); LPVOID lpBase = MapViewOfFile(hMapping,FILE_MAP_WRITE|FILE_MAP_READ,0,0,0); strcpy((char*)lpBase,szBuffer); Sleep(20000); UnmapViewOfFile(lpBase); CloseHandle(hMapping); return nRetCode;
}

进程B代码

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0; HANDLE hMapping = OpenFileMapping(FILE_MAP_ALL_ACCESS,NULL,L"ShareMemory"); if (hMapping)
{
wprintf(L"%s\r\n",L"Success"); LPVOID lpBase = MapViewOfFile(hMapping,FILE_MAP_READ|FILE_MAP_WRITE,0,0,0); char szBuffer[20] = {0}; strcpy(szBuffer,(char*)lpBase); printf("%s",szBuffer); UnmapViewOfFile(lpBase); CloseHandle(hMapping); } else
{
wprintf(L"%s",L"OpenMapping Error");
} return nRetCode;
}

说道共享内存不得不说下内存映射:如何将一个文件映射到自己的缓冲区中。

打开文件-->计算文件大小-->创建内存映射对象Mapping-->mapofviewfile映射到自己的缓冲区中

通过文件映射来进行读写文件操作较为方便。

文件映射代码

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])
{
int nRetCode = 0; HANDLE hFile = CreateFile(L"D:\\Demo.txt",GENERIC_READ|GENERIC_WRITE,FILE_SHARE_WRITE|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); DWORD dwHigh = 0;
DWORD dwLow = 0;
dwLow = GetFileSize(hFile,&dwHigh); dwLow = ((dwLow + 4095)/4096)*4096; if (hFile==INVALID_HANDLE_VALUE)
{ return -1;
} HANDLE hMapping = CreateFileMapping(hFile,NULL,PAGE_READWRITE,dwHigh,dwLow,NULL); if (hMapping==NULL)
{
CloseHandle(hFile);
} char* szBuffer = NULL; szBuffer = (char*)MapViewOfFile(hMapping,FILE_MAP_ALL_ACCESS,0,0,0); if (szBuffer!=NULL)
{
cout<<szBuffer<<endl;
}
*(szBuffer+1) = 'w'; UnmapViewOfFile(szBuffer); CloseHandle(hMapping);
CloseHandle(hFile); return nRetCode;
}

0X04

消息

进程A代码

void CServerDlg::OnBnClickedOk()
{ CString strBuffer;
m_Edit.GetWindowText(strBuffer); if (strBuffer.GetLength()==0)
{
return;
} COPYDATASTRUCT Temp; Temp.dwData = 0;
Temp.cbData = strBuffer.GetLength()*sizeof(WCHAR); // sizeof 没有算 '\0'
Temp.lpData = strBuffer.GetBuffer(); HWND hFindWindow = ::FindWindow(NULL,L"Client"); if (hFindWindow==NULL)
{
return;
}
::SendMessage(hFindWindow,WM_COPYDATA,NULL,(LPARAM)&Temp); }

进程B代码

进程B需要添加WM_COPYDATA消息

BOOL CClientDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值 if (pCopyDataStruct->lpData==NULL||pCopyDataStruct->cbData==0)
{
return FALSE;
} int nSize = 0; //字节20
int nLen = pCopyDataStruct->cbData+sizeof(WCHAR); //字符HelloWorld10 加了个'\0' WCHAR* szBuffer = new WCHAR[nLen>>1]; // 右移一位 除以二 申请 同样大的内存 if (szBuffer==NULL)
{
return FALSE;
}
memset(szBuffer,0,sizeof(WCHAR)*(nLen>>1)); memcpy(szBuffer,pCopyDataStruct->lpData,pCopyDataStruct->cbData); m_Edit.SetWindowText(szBuffer); delete szBuffer; szBuffer = NULL; return CDialogEx::OnCopyData(pWnd, pCopyDataStruct);
}

这种方式是由操作系统负责给目标窗口传递 ,所以目标进程必须需要窗口,不然A得不到窗口句柄就无法传递。这种方式是通过Windows消息队列传递,看起来与之前的内核对象传递消息有悖,

那是因为操作系统把相关细节都屏蔽掉了,如果深究起来还是通过Ring0的操作系统空间内核对象进行传递。

剩下的套接字,RPC,DDE等也可用来进行进程间通信,但总有种杀鸡用牛刀的感觉。我并没有再进行整理,有兴趣的可以在进行了解了解。

几种Windows进程通信的更多相关文章

  1. Windows进程通信 -- 共享内存(1)

    共享内存的方式原理就是将一份物理内存映射到不同进程各自的虚拟地址空间上,这样每个进程都可以读取同一份数据,从而实现进程通信.因为是通过内存操作实现通信,因此是一种最高效的数据交换方法. 共享内存在 W ...

  2. Windows进程通信 -- 共享内存

    享内存的方式原理就是将一份物理内存映射到不同进程各自的虚拟地址空间上,这样每个进程都可以读取同一份数据,从而实现进程通信.因为是通过内存操作实现通信,因此是一种最高效的数据交换方法. 共享内存在 Wi ...

  3. Windows进程通信之一看就懂的匿名管道通信

    目录 进程通信之一看就懂的匿名管道通信 一丶匿名管道 1.1何为匿名管道 1.2创建匿名管道需要注意的事项 1.3 创建匿名管道需要的步骤 1.4代码例子 1.5代码运行截图 进程通信之一看就懂的匿名 ...

  4. Windows进程通信-共享内存空间

    三个模块 1,game.exe,三个方法,控制台输入指令('A','B','R')分别控制三个方法的调用: 2,WGDll.dll,要注入到game进程中的dll文件: 3,myconsole.exe ...

  5. Windows 进程通信 之 DDE技术

    DDE (Dynamic Data Exchange,DDE)动态数据交换,是一种进程间通信机制,它最早是随着Windows由微软提出的.当前大部分软件仍旧支持DDE,但最近十年里微软已经停止发展DD ...

  6. Android四种跨进程通信

    由于android系统中应用程序之间不能共享内存.因此,在不同应用程序之间交互数据(跨进程通讯)就稍微麻烦一些.在android SDK中提供了4种用于跨进程通讯的方式.这4种方式正好对应于andro ...

  7. windows进程通信 -- WM_COPYDATA消息

    WM_COPYDATA消息,在win32中用来进行进程间的数据传输. typedef struct tagCOPYDATASTRUCT { // cds DWORD dwData; DWORD cbD ...

  8. [转]WINDOW进程通信的几种方式

    windows进程通信的几种方式 1 文件映射 文件映射(Memory-Mapped Files)能使进程把文件内容当作进程地址区间一块内存那样来对待.因此,进程不必使用文件I/O操作,只需简单的指针 ...

  9. Windows线程+进程通信

    一 Windows线程进程 1)定义 按照MS的定义, Windows中的进程简单地说就是一个内存中的可执行程序, 提供程序运行的各种资源. 进程拥有虚拟的地址空间, 可执行代码, 数据, 对象句柄集 ...

随机推荐

  1. Asp.Net Core中简单使用日志组件log4net

    本文将简单介绍在.NET 6中使用log4net的方法,具体见下文范例. 1.首先新建一个ASP.NET Core空项目 2.通过Nuget包管理器安装下面两个包 log4net Microsoft. ...

  2. [luogu4718]Pollard-Rho算法

    模板题 题解主要分为两部分,即Miller-Robin判素数以及关于Pollard-Rho算法 1.Miller-Robin判素数 对于一个数$n$,判定其是否为素数,依次执行以下几步-- (1)若$ ...

  3. [atAGC007E]Shik and Travel

    二分枚举答案,判定答案是否合法 贪心:每一个叶子只能经过一遍,因此叶子的顺序一定是一个dfs序,即走完一棵子树中的所有叶子才会到子树外 根据这个贪心可以dp,设$f[k][l][r]$表示仅考虑$k$ ...

  4. 2017Java前景怎么样?

    当今社会互联网软件行业属于高薪技术行业,伴随着互联网的发展Java在Web领域的优势也日渐凸显,并且java语言本身就应用最广泛,最高效.据说,全球有25亿Java器件运行着Java,450多万Jav ...

  5. 洛谷 P3783 - [SDOI2017]天才黑客(前后缀优化建图)

    题面传送门 神仙题一道. 首先注意到这里的贡献涉及到边的顺序,并且只与相邻的边是什么有关,因此不难想到一个做法--边转点,点转边,具体来说对于每条边 \(e\),我们将其拆成两个点 \(in_e,ou ...

  6. P5896 [IOI2016]aliens

    *IX. P5896 [IOI2016]aliens DP 优化方法大杂烩,详解 wqs 二分及其注意事项,斜率优化等其它 DP 优化方法. **** 团队赛 T6,没想到是 IOI 原题.当时看出来 ...

  7. 系统发育树邻接法(NJ)和非加权组平均法(UPGMA)之比较

    目录 1.原理的区别 2.实操比较 UPGMA NJ法 保存树文件 更深理解 1.原理的区别 主要区别在于,非加权组平均法(UPGMA)是基于平均链接方法的聚集层次聚类方法,而邻接法(NJ)是基于最小 ...

  8. 生产调优2 HDFS-集群压测

    目录 2 HDFS-集群压测 2.1 测试HDFS写性能 测试1 限制网络 1 向HDFS集群写10个128M的文件 测试结果分析 测试2 不限制网络 1 向HDFS集群写10个128M的文件 2 测 ...

  9. Angular Service设计理念及使用

    官方认为组件不应该直接获取或保存数据, 它们应该聚焦于展示数据,而把数据访问的职责委托给某个服务. 而服务就充当着数据访问,逻辑处理的功能.把组件和服务区分开,以提高模块性和复用性. 1.依赖注入 注 ...

  10. 【STM32】使用SDIO进行SD卡读写,包含文件管理FatFs(七)-准备移植FatFs

    [STM32]使用SDIO进行SD卡读写,包含文件管理FatFs(一)-初步认识SD卡 [STM32]使用SDIO进行SD卡读写,包含文件管理FatFs(二)-了解SD总线,命令的相关介绍 [STM3 ...