在Windows上經常操作文件或注冊表的同學可能知道,有“文件系統/注冊表重定向”這麼一回事。大致來說就是32位程序在64位的Windows上運行時,操作系統會把對System32文件夾的訪問重定向到SysWow64下,把對HKEY_LOCAL_MACHINE\SOFTWARE的訪問重定向到HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node下。當然不止這些路徑和注冊表。詳情請查看MSDN:https://msdn.microsoft.com/en-us/library/aa384187.aspx 和https://msdn.microsoft.com/en-us/library/aa384232(v=vs.85).aspx 。
我們通常為了方便發布,都只會編譯一份32位的程序,不會編譯64的程序。如果代碼中涉及到了訪問文件和注冊表,那就要考慮這個問題了。
Windows提供了兩個API(准確的說是3個,但其中一個已不推薦使用了)來禁用文件系統/注冊表重定向:Wow64DisableWow64FsRedirection,Wow64RevertWow64FsRedirection。顧名思義,前者用於禁用,後者用於恢復。
有的同學可能就說了:我一直禁用就行了,干嘛還要恢復?這你就想少了,有的代碼段可能並不關心是否有重定向,所以並沒有考慮這個問題,假若這個代碼段的結果會影響多個線程,而你剛好又把包含這個代碼段的某一線程的重定向禁用了,那就成了有的線程禁用了重定向,有的線程沒有禁用,獲取的結果就不一致了。
一般我們在會有重定向問題的函數調用前禁用重定向,調用完畢後,恢復重定向。這很容易讓人想到使用RAII手法:在類構造函數中禁用,析構函數中恢復:
class scoped_disable_wow64_fsredirection : public boost::noncopyable { public: scoped_disable_wow64_fsredirection(); ~scoped_disable_wow64_fsredirection(); private: static bool disable(void **ppOldValue); static bool revert(void *pOldValue); private: void *_pOldValue; };
構造函數的實現中調用了disable,析構函數的實現中調用了revert。
其中disable就是調用了Wow64DisableWow64FsRedirection,revert就是調用了Wow64RevertWow64FsRedirection。
注:類名字這麼長是為了能夠達到“顧名思義”的程度。還沒想到什麼更好的名字。哎,起名真是個頭疼的事。
但是我們不能直接調用這兩個Windows API,為什麼呢?
static boost::once_flag once_; typedef int (__stdcall *fnWow64DisableWow64FsRedirection)(void *); typedef int (__stdcall *fnWow64RevertWow64FsRedirection)(void *); static fnWow64DisableWow64FsRedirection g_fnWow64DisableWow64FsRedirection = NULL; static fnWow64RevertWow64FsRedirection g_fnWow64RevertWow64FsRedirection = NULL; static void load_wow64_funcs() { g_fnWow64DisableWow64FsRedirection = reinterpret_cast<fnWow64DisableWow64FsRedirection> (WindowsUtil::load_function("Kernel32.dll", "Wow64DisableWow64FsRedirection")); g_fnWow64RevertWow64FsRedirection = reinterpret_cast<fnWow64RevertWow64FsRedirection> (WindowsUtil::load_function("Kernel32.dll", "Wow64RevertWow64FsRedirection")); } scoped_disable_wow64_fsredirection::scoped_disable_wow64_fsredirection() : _pOldValue(NULL) { boost::call_once(once_, load_wow64_funcs); disable(&_pOldValue); } scoped_disable_wow64_fsredirection::~scoped_disable_wow64_fsredirection() { revert(_pOldValue); } bool scoped_disable_wow64_fsredirection::disable(void **ppOldValue) { bool ret = true; if (g_fnWow64DisableWow64FsRedirection) { if (!g_fnWow64DisableWow64FsRedirection(ppOldValue)) { ErrorLogLastErr("Wow64DisableWow64FsRedirection fail"); ret = false; } } return ret; } bool scoped_disable_wow64_fsredirection::revert(void *pOldValue) { bool ret = true; if (g_fnWow64RevertWow64FsRedirection) { if (!g_fnWow64RevertWow64FsRedirection(pOldValue)) { ErrorLogLastErr("Wow64RevertWow64FsRedirection fail"); ret = false; } } return ret; }
這裡用了前面文章講到的call_once去加載這兩個函數。
load_function封裝了GetModuleHandleA- GetProcAddress兩個函數的調用,詳情請參看源碼。
使用時,僅需定義一個類實例就可以了。切記,要盡量縮小作用域,以免影響其他代碼段。
源碼:https://git.oschina.net/mkdym/DaemonSvc.git (主)&& https://github.com/mkdym/DaemonSvc.git (提升逼格用的)。
2015年11月1日星期日