應對32位程序在64位系統上訪問註冊表和文件自動轉向問題

 1 簡介

當前計算機系統已經逐漸地從32位轉到64位,XP,2003,VISTA都有64位的版本。從目前而言,32位應用程序還是佔了絕大多數,但是也有部分應用程序既有32位版本,又有64位版本。爲了保證32位程序可以順利運行在64位系統上,微軟提供了一套叫WOW64的模擬機制。通常把這套系統稱爲WOW64。從總體上來說,WOW64是一套基於用戶模式的動態鏈接庫,它可以把32位應用程序的發出的命令翻譯成64位系統可以接受的格式。從下圖中可以大概地看出32位應用程序運行在64位系統上的方式。

 

當32位應用程序運行的時候,首先會去啓動本地庫加載器(Native Library Loader)。加載器會識別出應用程序是32位的並且用特殊的方式來處理它。加載器會爲32位應用程序建立起一個WOW64的模擬環境並把控制權交給32位的Ntdll.dll。運行在32位應用程序和64位Ntdll.dll之間的WOW64模擬環境會將32位應用程序的指令翻譯成64位Ntdll.dll可以接受的方式,並且它也可以把系統的指令翻譯成32位應用程序可以接受的方式。

2 如何判斷系統是64位

判斷系統是否是64位的方法有很多。對於C#來說,調用WMI是一種簡單易行的方式。我們可以用Win32_Processor類裏面的AddressWidth屬性來表示系統的位寬。AddressWidth的值受CPU和操作系統的雙重影響。具體的值如下面的表格所示:

  32bit OS 64bit OS
32bit CPU AddressWidth = 32 N/A
64bit CPU AddressWidth = 32 AddressWidth = 64

可以用下面的C#代碼得到AddressWidth的值

public static string Detect3264()
{
            ConnectionOptions oConn 
= new ConnectionOptions();
            System.Management.ManagementScope oMs 
= new System.Management.ManagementScope("\\\\localhost", oConn);
            System.Management.ObjectQuery oQuery 
= new System.Management.ObjectQuery("select AddressWidth from Win32_Processor");
            ManagementObjectSearcher oSearcher 
= new ManagementObjectSearcher(oMs, oQuery);
            ManagementObjectCollection oReturnCollection 
= oSearcher.Get();
            
string addressWidth = null;

            
foreach (ManagementObject oReturn in oReturnCollection)
            {
                addressWidth 
= oReturn["AddressWidth"].ToString();
            }

            
return addressWidth;
}

3 文件系統的轉向

32位進程不能加載64位Dll,64位進程也不可以加載32位Dll。Windows的系統目錄包含了所有安裝的應用程序和它們的Dll文件,根據我們所述的規則,它應該被分爲給64位應用程序的目錄和給32位應用程序的目錄。如果不這樣,我們就無法區分32位和64位的Dll文件。對於64位應用程序,其文件通常被放在%windir%\system32和%programfiles%(比如:c:\program files)。對於32位應用程序,其文件通常在%windir%\syswow64和C:\program files (x86)下面。如果我們用32位程序去訪問%windir%\system32,不管我們用硬編碼還是其它的方式,系統都會自動地給我們轉向到%windir%\syswow64下面。這種轉向對於每個32位應用程序默認都是打開的。但是這種轉向對於我們來說並不總是需要的。那麼我們可以在C#裏面調用相關的API來關閉和打開這種轉向。常用的函數有3個,Wow64DisableWow64FsRedirection(關閉系統轉向),Wow64RevertWow64FsRedirection(打開系統轉向),Wow64EnableWow64FsRedirection(打開系統轉向)。但是Wow64EnableWow64FsRedirection在嵌套使用的時候不可靠,所以通常用上面的Wow64RevertWow64FsRedirection來打開文件系統轉向功能。在C#中,我們可以利用DllImport直接調用這兩個函數。但是要注意到的是,在32位的Kernel.dll中是沒有這兩個函數的。那麼在C++中應該使用LoadLibrary來動態加載這兩個函數。否則會因爲找不到這兩個函數而無法通過編譯。而且在目前的使用中,發現這兩個函數有一個小小的問題。如果我們在調用了Wow64DisableWow64FsRedirection後去調用Comdlg32.dll的GetOpenFileName函數,是無法調用成功的。但是也得不到Error的值。在C#中可以用如下的代碼關閉和打開文件的轉向。

聲明調用規則
[DllImport( "Kernel32.dll", CharSet=CharSet.Auto, SetLastError = true)] 
public static extern bool Wow64DisableWow64FsRedirection(ref IntPtr ptr);
[DllImport( 
"Kernel32.dll", CharSet=CharSet.Auto, SetLastError = true)] 
public static extern bool Wow64RevertWow64FsRedirection(IntPtr ptr);

關閉轉向
Wow64DisableWow64FsRedirection(ref Ptr);

打開轉向
Wow64RevertWow64FsRedirection(Ptr);

要注意的是,關閉和打開要成對出現。以免出現混亂的行爲。

4 註冊表的轉向

爲了防止註冊表鍵衝突,註冊表在某些鍵也分成了兩個部分。一部分是專門給64位系統訪問的,另一部分是專門給32位系統訪問的,放在Wow6432Node下面。當32位程序去訪問某些鍵值的時候,和文件轉向類似,系統也會自動地把程序的訪問轉向到Wow6432Node下面。Wow6432Node這個節點存在於HKEY_LOCAL_MACHINE和HKEY_CURRENT_USER下面。如果我們希望關閉這個轉向的話,可以通過上面的Wow64DisableWow64FsRedirection和RegOpenKeyEx方法辦到。RegOpenEx方法在C#中調用聲明方法如下:

[DllImport("Advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public
static extern uint RegOpenKeyEx( UIntPtr hKey,string lpSubKey, uint ulOptions,int samDesired,out IntPtr phkResult);

其中需要注意的是samDesired這個參數。這個參數可以取 KEY_ALL_ACCESS, KEY_QUERY_VALUE,  KEY_WOW64_64KEY等值(詳情可以查閱MSDN)。當我們已經關閉了文件系統的轉向,那麼就可以利用:(KEY_QUERY_VALUE | KEY_WOW64_64KEY)來得到註冊表的完全訪問權限。這個地方需要注意的是,在Vista下面,有一些註冊表項是隻讀的,如果用了KEY_ALL_ACCESS這個參數,就會出現“Access is denied” 這個錯誤(ErrorCode = 5)。因此,如果不是要寫入註冊表的話,最好不要使用KEY_ALL_ACCESS。我們可以用如下代碼來完全訪問註冊表。

        //use this function transfer the string key name to HKEY handle
        private static UIntPtr TransferKeyName(string keyName) 
        {
            UIntPtr HKEY_CLASSES_ROOT        
= (UIntPtr)0x80000000;
            UIntPtr HKEY_CURRENT_USER        
= (UIntPtr)0x80000001;
            UIntPtr HKEY_LOCAL_MACHINE    
= (UIntPtr)0x80000002;
            UIntPtr HKEY_USERS                    
= (UIntPtr)0x80000003;
            UIntPtr HKEY_CURRENT_CONFIG 
= (UIntPtr)0x80000005;

            
switch(keyName)
            {
                
case "HKEY_CLASSES_ROOT":
                    
return HKEY_CLASSES_ROOT;
                
case "HKEY_CURRENT_USER":
                    
return HKEY_CURRENT_USER;
                
case "HKEY_LOCAL_MACHINE":
                    
return HKEY_LOCAL_MACHINE;
                
case "HKEY_USERS":
                    
return HKEY_USERS;
                
case "HKEY_CURRENT_CONFIG":
                    
return HKEY_CURRENT_CONFIG;
            }

            
return HKEY_CLASSES_ROOT;
        }


        
public static bool OpenRegKey(string keyName, string subKeyName)
        {
            UIntPtr hKey 
= TransferKeyName(keyName);
            UIntPtr phkResult 
= UIntPtr.Zero;
            
int KEY_QUERY_VALUE    = (0x0001); 
            
int KEY_WOW64_64KEY    = (0x0100);
            
int KEY_ALL_WOW64        = (KEY_QUERY_VALUE | KEY_WOW64_64KEY);

            
if(Is64bitOS && !IsWow64RedirectionEnabled) //The os is 64 bit and the FileRedirection is closed
            {                
                samDesired 
= KEY_ALL_WOW64;
            }

            
if(RegOpenKeyEx(hKey, subKeyName,0,samDesired,out phkResult) == 0//ERROR_SUCCESS=0
            {
                
return true;
            }

            
int errCode = System.Runtime.InteropServices.Marshal.GetLastWin32Error();

            
return false;
        }


5 進一步閱讀

需要更多的64位編程的資料,可以查閱下面的鏈接

http://www.microsoft.com/whdc/system/platform/64bit/WoW64_bestprac.mspx

http://msdn2.microsoft.com/en-us/library/aa384187.aspx

http://msdn2.microsoft.com/en-us/library/aa384235.aspx

*【Author】:flyingbread
 *【Date】:2007年1月21日
 *【Notice】:


出處:http://tech.it168.com/KnowledgeBase/Articles/7/4/a/74a6990cf7ded7797cf258b60a59aac1.htm

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章