如何傳遞大型數據給子進程
昨天的一篇文章中,我們說到如果想向一個子進程傳輸多于32767個字符的數據,我們需要尋找其他的方法(而不是命令行參數)來實現。
我們能想到的第一個方法是:WM_COPYDATA。當子進程創建并進入消息循環后,我們使用FindWindow來尋找進程的窗口并發送WM_COPYDATA消息。但是,這種方法有幾個問題:
> 你需要尋找一種方法確定子進程確實已經創建了目標窗口并進入消息循環,這樣才可以進行窗口查找。(提示:可以使用WaitForInputIdle這個方法)
> 你需要確保我們使用FindWindow查找到的窗口確實是你希望得到的那個,因為如果其他進程的窗口碰巧也有相同的標題或者窗口類,則你可能得到的不是目標窗口。又或者,有多個相同的子進程實例,也會創建同樣的窗口實例。所以,你需要確定查找到正確的目標窗口。(提示:可以使用GetWindowThreadProcessId這個方法)
> 你需要確保其他人不會查找到你的窗口并在你之前發送WM_COPYDATA,如果他們這樣做了,那他們就得到了你的子進程的控制權了。
> 子進程需要設計一種機制,來對抗一些惡意進程發送偽造的WM_COPYDATA消息,并盡可能正確處理它們(丟棄這種請求)。
我所推薦的方法是:匿名共享內存
基本想法是,創建一塊共享內存并填充你需要傳輸的數據。然后將它的句柄設置為可繼承的,然后創建子進程,傳輸句柄的數字表示到子進程的命令行。
子進程會從命令行中得到這個句柄,并映射這塊共享內存到它的進程空間,這樣就能訪問到你傳遞給它的數據了。
關于這種方法的幾點說明:
> 子進程需要判斷句柄的有效性,防止某些人傳遞一些無效的或者偽造的數據。
> 如果惡意進程想搞亂你的子進程命令行,則它們必須擁有PROCESS_VM_WRITE權限。如果它們想訪問進程句柄表,則還需要有PROCESS_DUP_HANDLE權限。這些都是安全的訪問掩碼,所有配置合適的ACL可以起到很好的保護作用。(默認的ACL就可以起到保護,所以使用默認的ACL就行了)
> 在這種方法中,沒有名字和數字可以被惡意進程監聽或者偽造。前提是你對子進程實施了PROCESS_VM_WRITE和PROCESS_DUP_HANDLE保護。
> 因為我們使用的是共享內存,所以共享的數據不會真正地在兩個進程之間拷貝,它們只是被操作系統重新映射而已。對于傳輸大型數據來說,這種方法十分高效。
在下面的例子代碼中,我們演示了如何使用共享內存技術來傳輸數據。

在實際的項目中,上面的STARTUPPARAMS結構體的成員可能會十分復雜,但是出于演示目的,我在這里僅僅定義了一個整數成員。

CreateStartupParams創建了一個在共享內存區中的STARTUPPARAMS的結構體。首先,我們填充SECURITY_ATTRIBUTES結構體并將它的可繼承屬性設置為TRUE,這樣子進程就可以訪問到這塊共享內存。將lpSecurityDescriptor設置為NULL表明我們希望使用默認的安全描述符,我們使用這個默認的值就夠了。然后,我們創建了一個指定大小的共享內存對象,然后將它映射到內存,最后,我們返回了一個共享內存的句柄和映射后的內存地址。

GetStartupParams函數是與CreateStartupParams對應的函數。它從命令行上解析出句柄的值并嘗試進行內存映射。如果句柄不是一個合法的文件映射句柄,則MapViewOfFile調用會失敗,我們可以通過這種方法進行參數的有效性校驗。然后,我們使用了VirtualQuery來查詢內存映射區的大小。(這里,我們沒有使用更加嚴格的測試方法,因為它的返回值可能被舍入到最近的頁邊界)

我們還需要釋放共享內存來防止可能出現的內存泄露,所以我們定義了上面的FreeStartupParams函數實現這一點。

上面的函數中,我們主要是構建子進程的命令行參數。我們使用了GetModuleFileName(NULL)來獲取執行程序的全路徑,然后傳輸句柄的數字表示,并在調用CreateProcess時設置了參數TRUE來表明我們希望子進程的句柄是可以繼承的。
有一個小地方需要注意的:我們使用了引號來處理程序路徑中含有空格的情況。

最后,我們將上面所有的程序片段組合在一起。
如果命令行上已經有一個參數了,表明這次運行的是子進程,所以我們將這個參數轉換為STARTUPPARAMS并獲取其中的數據并顯示出來。
如果命令行上沒有傳遞參數,則表明我們運行的是父進程,在這種情況下,我們創建了一個STARTUPPARAMS,并設置我們需要傳輸的數據(這里使用了42作為例子),然后傳輸給子進程。
總結
今天我們演示了如何向子進程傳輸大量數據的方法,雖然在上面的例子中,我們傳遞的數據量很小,但是如果稍加改進,完全可以實現傳遞大型數據。
所以各位老哥,可以將這種方法,穩穩地放到你的技能工具箱了。
