談談如何使用ShellExecute的返回值
之前的一篇文章中,我們講到了在 16 位 Windows 中,實例句柄(HINSTANCE)唯一標識了一個進程。到了 32 位 Windows,內核得到了完全的重新設計,其中之一是:它引入了 “內核對象” 和 “安全描述符”。
在 16 位 Windows 中,實際上是沒有進程 ID 的,它使用了實例句柄作為標識進程的手段。這就是為什么 WinExec 和 ShellExecute 會返回 HINSTANCE 的原因。
但在 32 位世界中,HINSTANCE 不再唯一標識正在運行的程序,因為它只是可執行文件的基址(Base Address)。由于每個程序都在自己的地址空間中運行,因此該值在整個系統中幾乎不是唯一的。
那么,你可以用 ShellExecute 函數返回的 HINSTANCE 做什么呢?你可以檢查它是否大于 32,如果是,則表示調用成功。如果該值小于 32,則為錯誤代碼。在大于 32 的情況下,HINSTANCE 的精確值毫無意義。為什么我費心告訴你 MSDN 中已經涵蓋的內容?因為人們仍然很難舉一反三。
我一直看到有人采用 ShellExecute 函數返回的 HINSTANCE,并在系統中的所有窗口中尋找具有匹配 GWLP_HINSTANCE 的窗口(如果你仍然生活在未開明的非 64 位兼容世界中,則為 GWL_HINSTANCE)。
由于我上面描述的兩個原因,這是行不通的。首先,你得到的 HINSTANCE 的精確值是沒有意義的,即使它是有意義的,它也不會對你有任何好處,因為 HINSTANCE 不是唯一的。(事實上,進程的 HINSTANCE 幾乎總是0x00400000,因為這是大多數鏈接器分配給程序可執行文件的默認地址。)
人們想要首先使用這種技巧的最常見原因是他們想對剛剛啟動的程序做一些事情,通常是等待它退出,表明用戶已關閉文檔。
不幸的是,這個計劃有其自身的陷阱。首先,正如我們所指出的,你從 ShellExecute 函數獲得的 HINSTANCE 是無用的。你必須使用 ShellExecuteEx 函數并在 SHELLEXECUTEINFO 結構中設置 SEE_MASK_NOCLOSEPROCESS 標志,此時將在 hProcess 成員中返回要處理的句柄。
但這仍然行不通。
可以在不創建新進程的情況下打開用戶的文檔。你會遇到這種情況的最常見情況(但不是唯一的這種情況)是文檔類型的注冊處理程序請求 DDE 對話。在這種情況下,程序的現有實例已接管對文檔的所有處理。等待進程退出與等待用戶關閉文檔不同,因為關閉文檔不會退出進程。
大多數程序可以讓你從 “文件” 菜單中打開一個新文檔。打開新文檔后,用戶可以關閉舊文檔。(打開新文檔時,單文檔程序會隱式關閉舊文檔。更重要的是,關閉與文檔關聯的所有打開的窗口不會導致程序退出。)
有些程序甚至在你關閉所有窗口后也在后臺運行,要么是為了提供某種持續服務,要么只是因為它們只是預期用戶很快就會再次運行該程序,所以他們會延遲最終退出幾分鐘以查看是否需要它們。僅僅因為進程退出并不意味著文檔已關閉。
某些程序檢測到以前的實例并將文檔移交給該實例。其他程序是啟動另一個進程來完成實際工作的存根。無論哪種情況,新創建的進程都會快速退出,但文檔仍處于打開狀態,因為文檔的責任已移交給另一個進程。
沒有統一的方法來檢測文檔是否已關閉。每個程序處理它的方式不同。如果幸運的話,該程序會公開允許你監視打開文檔狀態的屬性。如前所述,Internet Explorer 通過 ShellWindows 對象公開其打開窗口的屬性。我知道 Microsoft Office 還為其組件程序公開了一組相當復雜的自動化接口。
總結
進入到 32 位 Windows 的世界后,實例句柄我已經很少用到了。