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等也可用來進行進程間通信,但總有種殺雞用牛刀的感覺。我並沒有再進行整理,有興趣的可以在進行了解了解。