標籤:
http://blog.codingnow.com/2005/10/interprocess_communications.html
Windows 下有很多方法實現進程間通訊,比如用 socket,管道(Pipe),信箱(Mailslot),等等。但最基本最直接的還是使用記憶體共用。其他方法最終還是會繞道這裡。
可想而知,如果實體記憶體只有一份,讓這份記憶體在不同的進程中,映射到各自的虛擬位址空間上,每個進程都可以讀取同一份資料,是一種最高效的資料交換方法。下面我們就討論如何?它。
共用記憶體在 Windows 中是用 FileMapping 實現的。我們可以用 CreateFileMapping 建立一個記憶體檔案對應物件, CreateFileMapping 這個 API 將建立一個核心對象,用於對應檔到記憶體。這裡,我們並不需要一個實際的檔案,所以,就不需要調用 CreateFile 建立一個檔案, hFile 這個參數可以填寫 INVALID_HANDLE_VALUE 。但是,檔案長度是需要填的。Windows 支援長達 64bit 的檔案,但是這裡,我們的需求一定不會超過 4G , dwMaximumSizeHigh 一定是 0 ,長度填在 dwMaximumSizeLow 即可。然後調用 MapViewOfFile 映射到當前進程的虛擬位址上即可。一旦用完共用記憶體,再調用 UnmapViewOfFile 回收記憶體位址空間。
Windows 把 CreateFileMapping 和 MapViewOfFile 兩個 API 分開做是有它的道理的。這是因為允許映射一個超過 4G 的檔案,而地址空間最大隻有 4G (實際上,一般使用者的程式只能用到 2G) , MapViewOfFile 就可以指定檔案的 Offset 而只映射一部分。
在 CreateFileMapping 的最後一個參數 pszName 填寫一個名字,那麼別的進程就可以用這個名字去調用 OpenFileMapping 來開啟這個 FileMapping 對象,在新的進程內作映射。 不過,通過約定字串的方法似乎不太優雅。
一個優雅的方法是,用 DuplicateHandle 在新進程中複製一份 FileMapping 對象出來,然後想辦法把 Handle 通知新進程,比如用訊息的方式傳遞過去。
如果需要共用記憶體的兩個進程是父子關係,那麼我們可以不用訊息傳遞的方式來通知 FileMapping 的 Handle 。父進程可以用繼承 Handle 的方式直接把 FileMapping 的 Handle 傳遞到子進程中。當然,在 CreateFileMapping 時就應該設定可以被繼承的屬性。
大約是這樣:
SECURITY_ATTRIBUTES sa;
sa.nLength=sizeof(sa);
sa.lpSecurityDescriptor=NULL;
sa.bInheritHandle=TRUE;
handle=CreateFileMapping(INVALID_HANDLE_VALUE,&sa,PAGE_READWRITE,0,size,NULL);
這樣,在 CreateProcess 的時候,如果 bInheritHandles 參數為 TRUE ,所有有可被繼承屬性的核心對象都會被複製到子進程中。
註:核心對象的繼承就是在 CreateProcess 建立子進程,但是子進程的主線程尚未活動之前,核心掃描當前進程中所有核心對象,檢查出有可繼承屬性的那些,再用 DuplicateHandle 複製一份到子進程。由於是核心對象,在核心中實質只有一份,所有只是引用記數加一,父進程和子進程對同一核心對象的 Handle 一定是相同的。
複製核心對象的過程是由 CreateProcess 內部完成的,我們可以放心的把對象 Handle (和子進程相同) 通過命令列傳遞給子進程。或者,用環境變數傳遞也可以。
值得注意的是,子進程用完了這個 FileMapping 對象後一樣需要 CloseHandle 減去引用計數。
備忘:
CreateProcess 調用時,pszCommandLine 不能直接填上一個不可修改的字串。例如:
CreateProcess("test.exe","test argument",...);
這樣就是錯誤的,因為 "test argument" 會被編譯器編譯放到不可修改的資料區段中。正確的方法是:
char cmdline[]="test argument";
CreateProcess("test.exe",cmdline,...);
這樣,命令列的字串就被放在堆棧上,是可以被讀寫的。
CreateProcess 的倒數第二個參數需要填寫一個 STARTUPINFOW 結構,這個結構很複雜,通常填起來很麻煩。我們可以複製一份父進程的結構,再酌情修改。方法是:
STARTUPINFO si={sizeof(si)};
PROCESS_INFORMATION pi;
GetStartupInfo(&si);
CreateProcess(...,&si,& pi);
這裡, STARTUPINFO 結構的第一個長度資訊通常應該填上,保證 GetStartupInfo(&si); 的正確執行。
[轉]Windows 下的進程間通訊及資料共用