1 进程与进程通信
进程是装入内存并准备执行的程序,每个进程都有私有的虚拟地址空间,由代码、数据以及它可利用的系统资源(如文件、管道等)组成。多进程/多线程是Windows操作系统的一个基本特征。Microsoft Win32应用编程接口(Application Programming Interface, API)提供了大量支持应用程序间数据共享和交换的机制,这些机制行使的活动称为进程间通信(InterProcess Communication, IPC),进程通信就是指不同进程间进行数据共享和数据交换。
正因为使用Win32 API进行进程通信方式有多种,如何选择恰当的通信方式就成为应用开发中的一个重要问题,下面本文将对Win32中进程通信的几种方法加以分析和比较。
2 进程通信方法
2.1 文件映射
文件映射(Memory-Mapped Files)能使进程把文件内容当作进程地址区间一块内存那样来对待。因此,进程不必使用文件I/O操作,只需简单的指针操作就可读取和修改文件的内容。
Win32 API允许多个进程访问同一文件映射对象,各个进程在它自己的地址空间里接收内存的指针。通过使用这些指针,不同进程就可以读或修改文件的内容,实现了对文件中数据的共享。
应用程序有三种方法来使多个进程共享一个文件映射对象。
(1)继承:第一个进程建立文件映射对象,它的子进程继承该对象的句柄。
(2)命名文件映射:第一个进程在建立文件映射对象时可以给该对象指定一个名字(可与文件名不同)。第二个进程可通过这个名字打开此文件映射对象。另外,第一个进程也可以通过一些其它IPC机制(有名管道、邮件槽等)把名字传给第二个进程。
(3)句柄复制:第一个进程建立文件映射对象,然后通过其它IPC机制(有名管道、邮件槽等)把对象句柄传递给第二个进程。第二个进程复制该句柄就取得对该文件映射对象的访问权限。
文件映射是在多个进程间共享数据的非常有效方法,有较好的安全性。但文件映射只能用于本地机器的进程之间,不能用于网络中,而开发者还必须控制进程间的同步。
#include <windows.h> #include <iostream> using namespace std; int main(int argc, TCHAR* argv[], TCHAR* envp[]) { int nRetCode = 0; HANDLE hFile = CreateFile("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); system("pause"); return nRetCode; }
2.2 共享内存
Win32 API中共享内存(Shared Memory)实际就是文件映射的一种特殊情况。进程在创建文件映射对象时用0xFFFFFFFF来代替文件句柄(HANDLE),就表示了对应的文件映射对象是从操作系统页面文件访问内存,其它进程打开该文件映射对象就可以访问该内存块。由于共享内存是用文件映射实现的,所以它也有较好的安全性,也只能运行于同一计算机上的进程之间。
#define _CRT_SECURE_NO_WARNINGS #include <windows.h> #include <iostream> using namespace std; int main(int argc, TCHAR* argv[], TCHAR* envp[]) { int nRetCode = 0; char szBuffer[] = "ONDragon"; HANDLE hMapping = CreateFileMapping(NULL, NULL, PAGE_READWRITE, 0, 4096, "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; }
#define _CRT_SECURE_NO_WARNINGS #include <windows.h> #include <iostream> int main(int argc, TCHAR* argv[], TCHAR* envp[]) { int nRetCode = 0; HANDLE hMapping = OpenFileMapping(FILE_MAP_ALL_ACCESS, NULL, "ShareMemory"); if (hMapping) { wprintf(L"%s ", "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", "OpenMapping Error"); } return nRetCode; }
2.3 匿名管道
管道(Pipe)是一种具有两个端点的通信通道:有一端句柄的进程可以和有另一端句柄的进程通信。管道可以是单向-一端是只读的,另一端点是只写的;也可以是双向的一管道的两端点既可读也可写。
匿名管道(Anonymous Pipe)是 在父进程和子进程之间,或同一父进程的两个子进程之间传输数据的无名字的单向管道。通常由父进程创建管道,然后由要通信的子进程继承通道的读端点句柄或写 端点句柄,然后实现通信。父进程还可以建立两个或更多个继承匿名管道读和写句柄的子进程。这些子进程可以使用管道直接通信,不需要通过父进程。
匿名管道是单机上实现子进程标准I/O重定向的有效方法,它不能在网上使用,也不能用于两个不相关的进程之间。
2.4 命名管道
命名管道(Named Pipe)是服务器进程和一个或多个客户进程之间通信的单向或双向管道。不同于匿名管道的是命名管道可以在不相关的进程之间和不同计算机之间使用,服务器建立命名管道时给它指定一个名字,任何进程都可以通过该名字打开管道的另一端,根据给定的权限和服务器进程通信。
命名管道提供了相对简单的编程接口,使通过网络传输数据并不比同一计算机上两进程之间通信更困难,不过如果要同时和多个进程通信它就力不从心了。
#include <windows.h> #include <iostream> using namespace std; #define READ_PIPE "\\.\pipe\ReadPipe" #define WRITE_PIPE "\\.\pipe\WritePipe" // 管道命名 #define Log(str) {{printf("%s ",str);}} 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 main(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; //Create A Named Pipe 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] = '