应对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和操作系统的双重影响。具体的值如下面的表格所示:
可以用下面的C#代码得到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#中可以用如下的代码关闭和打开文件的转向。
声明调用规则
关闭转向
打开转向
要注意的是,关闭和打开要成对出现。以免出现混乱的行为。
4 注册表的转向
为了防止注册表键冲突,注册表在某些键也分成了两个部分。一部分是专门给64位系统访问的,另一部分是专门给32位系统访问的,放在Wow6432Node下面。当32位程序去访问某些键值的时候,和文件转向类似,系统也会自动地把程序的访问转向到Wow6432Node下面。Wow6432Node这个节点存在于HKEY_LOCAL_MACHINE和HKEY_CURRENT_USER下面。如果我们希望关闭这个转向的话,可以通过上面的Wow64DisableWow64FsRedirection和RegOpenKeyEx方法办到。RegOpenEx方法在C#中调用声明方法如下:
其中需要注意的是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。我们可以用如下代码来完全访问注册表。
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】:
*1、本文为原创技术文章,首发博客园个人站点(http://flyingbread.cnblogs.com/),转载和引用请注明作者及出处。
*2、本文必须全文转载和引用,任何组织和个人未授权不能修改任何内容,并且未授权不可用于商业。
*3、本声明为文章一部分,转载和引用必须包括在原文中。
******************************************************************************************/
当前计算机系统已经逐渐地从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;
}
{
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);
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);
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;
}
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】:
*1、本文为原创技术文章,首发博客园个人站点(http://flyingbread.cnblogs.com/),转载和引用请注明作者及出处。
*2、本文必须全文转载和引用,任何组织和个人未授权不能修改任何内容,并且未授权不可用于商业。
*3、本声明为文章一部分,转载和引用必须包括在原文中。
******************************************************************************************/