Windows 下有很多方法實(shí)現(xiàn)進(jìn)程間通訊,比如用 socket,管道(Pipe),信箱(Mailslot),等等。但最基本最直接的還是使用內(nèi)存共享。其他方法最終還是會(huì)繞道這里。
可想而知,如果物理內(nèi)存只有一份,讓這份內(nèi)存在不同的進(jìn)程中,映射到各自的虛擬地址空間上,每個(gè)進(jìn)程都可以讀取同一份數(shù)據(jù),是一種最高效的數(shù)據(jù)交換方法。下面我們就討論如何實(shí)現(xiàn)它。
共享內(nèi)存在 Windows 中是用 FileMapping 實(shí)現(xiàn)的。我們可以用 CreateFileMapping 創(chuàng)建一個(gè)內(nèi)存文件映射對(duì)象, CreateFileMapping 這個(gè) API 將創(chuàng)建一個(gè)內(nèi)核對(duì)象,用于映射文件到內(nèi)存。這里,我們并不需要一個(gè)實(shí)際的文件,所以,就不需要調(diào)用 CreateFile 創(chuàng)建一個(gè)文件, hFile 這個(gè)參數(shù)可以填寫 INVALID_HANDLE_VALUE 。但是,文件長度是需要填的。Windows 支持長達(dá) 64bit 的文件,但是這里,我們的需求一定不會(huì)超過 4G , dwMaximumSizeHigh 一定是 0 ,長度填在 dwMaximumSizeLow 即可。然后調(diào)用 MapViewOfFile 映射到當(dāng)前進(jìn)程的虛擬地址上即可。一旦用完共享內(nèi)存,再調(diào)用 UnmapViewOfFile 回收內(nèi)存地址空間。
Windows 把 CreateFileMapping 和 MapViewOfFile 兩個(gè) API 分開做是有它的道理的。這是因?yàn)樵试S映射一個(gè)超過 4G 的文件,而地址空間最大只有 4G (實(shí)際上,一般用戶的程序只能用到 2G) , MapViewOfFile 就可以指定文件的 Offset 而只映射一部分。
在 CreateFileMapping 的最后一個(gè)參數(shù) pszName 填寫一個(gè)名字,那么別的進(jìn)程就可以用這個(gè)名字去調(diào)用 OpenFileMapping 來打開這個(gè) FileMapping 對(duì)象,在新的進(jìn)程內(nèi)作映射。 不過,通過約定字符串的方法似乎不太優(yōu)雅。
一個(gè)優(yōu)雅的方法是,用 DuplicateHandle 在新進(jìn)程中復(fù)制一份 FileMapping 對(duì)象出來,然后想辦法把 Handle 通知新進(jìn)程,比如用消息的方式傳遞過去。
如果需要共享內(nèi)存的兩個(gè)進(jìn)程是父子關(guān)系,那么我們可以不用消息傳遞的方式來通知 FileMapping 的 Handle 。父進(jìn)程可以用繼承 Handle 的方式直接把 FileMapping 的 Handle 傳遞到子進(jìn)程中。當(dāng)然,在 CreateFileMapping 時(shí)就應(yīng)該設(shè)置可以被繼承的屬性。
大約是這樣:
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 的時(shí)候,如果 bInheritHandles 參數(shù)為 TRUE ,所有有可被繼承屬性的內(nèi)核對(duì)象都會(huì)被復(fù)制到子進(jìn)程中。
注:內(nèi)核對(duì)象的繼承就是在 CreateProcess 創(chuàng)建子進(jìn)程,但是子進(jìn)程的主線程尚未活動(dòng)之前,內(nèi)核掃描當(dāng)前進(jìn)程中所有內(nèi)核對(duì)象,檢查出有可繼承屬性的那些,再用 DuplicateHandle 復(fù)制一份到子進(jìn)程。由于是內(nèi)核對(duì)象,在內(nèi)核中實(shí)質(zhì)只有一份,所有只是引用記數(shù)加一,父進(jìn)程和子進(jìn)程對(duì)同一內(nèi)核對(duì)象的 Handle 一定是相同的。
復(fù)制內(nèi)核對(duì)象的過程是由 CreateProcess 內(nèi)部完成的,我們可以放心的把對(duì)象 Handle (和子進(jìn)程相同) 通過命令行傳遞給子進(jìn)程。或者,用環(huán)境變量傳遞也可以。
值得注意的是,子進(jìn)程用完了這個(gè) FileMapping 對(duì)象后一樣需要 CloseHandle 減去引用計(jì)數(shù)。
備注:
CreateProcess 調(diào)用時(shí),pszCommandLine 不能直接填上一個(gè)不可修改的字符串。例如:
CreateProcess("test.exe","test argument",...);
這樣就是錯(cuò)誤的,因?yàn)?"test argument" 會(huì)被編譯器編譯放到不可修改的數(shù)據(jù)段中。正確的方法是:
char cmdline[]="test argument";
CreateProcess("test.exe",cmdline,...);
這樣,命令行的字符串就被放在堆棧上,是可以被讀寫的。
CreateProcess 的倒數(shù)第二個(gè)參數(shù)需要填寫一個(gè) STARTUPINFOW 結(jié)構(gòu),這個(gè)結(jié)構(gòu)很復(fù)雜,通常填起來很麻煩。我們可以復(fù)制一份父進(jìn)程的結(jié)構(gòu),再酌情修改。方法是:
STARTUPINFO si={sizeof(si)};
PROCESS_INFORMATION pi;
GetStartupInfo(&si);
CreateProcess(...,&si,& pi);
這里, STARTUPINFO 結(jié)構(gòu)的第一個(gè)長度信息通常應(yīng)該填上,保證 GetStartupInfo(&si); 的正確執(zhí)行。