BlueSky -- 人云亦云

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

[翻译文章,原文请参考:http://www.codeproject.com/Articles/51326/Net-Compilation-registry-accessing-and-application.aspx]

多长的一个标题,不是吗?这是因为在一些情况下,你必需要做一大堆事情才能让你的.NET应用程序同时在x86和x64环境下成功运行,尤其是在你还需要使用一些非安全代码(unmanaged code)或者需要访问注册表的时候。

编译和运行.NET组件(Compiling and running assemblies)

原生代码(例如C++等等)都会被编译成为平台相关的二进制代码。这就是你在编译时候需要指定目标平台为x86或者x64的原因。在.NET平台下,情况有所不同。就与你可能已经知道的一样,你的VB或者C#代码并不会编译成为二进制代码,而是MSIL代码。这些代码加载时,JIT(Just-in-time)编译器负责把中间代码转化成为二进制代码。在这个转化过程中,编译器会根据当前操作系统来决定编译成32位代码还是64位代码。这就是你可以不给.NET组件指定目标平台,而选择"Any CPU"的原因。下面的图片是Visual Studio工程配置页的截图,你在这里可以选择目标平台。

既然如上所述,我为什么还要为.NET组件指定目标平台呢? "Any CPU"不应该是最好的选择吗? 是的。在绝大多数情况下,程序员开发的都是纯.NET应用程序,它不依赖任何非托管代码,也不需要进一步的优化。所以"Any CPU"是最好的选择(让JIT编译器动态确定编译成什么样的代码)。但是仍然有些情况你会想去指定目标平台,例如:

  • 如果你使用了任何非托管代码(它是平台相关的),就需要注意目标平台的问题。因为当JIT编译器选择了一个和你的非托管代码不兼容的模式去做编译时,你的应用程序可能无法加载这些非托管代码。
  • 如果你想对你的代码做平台相关优化。例如,你的应用程序需要做大量的RAM内存访问时,你可能只想让它运行在64位模式下。

另外,还有其他和目标平台相关的问题,比如注册表访问。我们会在这篇文章的后面讨论它。这篇博文对目标平台有进一步的讨论。

平台兼容性(Platform compatibility)

  • 如果你的.NET组件使用"Any CPU"选项编译
    • 可以运行在X86环境中(JIT编译器把它编译成X86二进制代码)
    • 可以运行在X64环境中(JIT编译器把它编译成X64二进制代码)
  • 如果你的.NET组件使用"X64"作为目标平台编译
    • 不可以运行在X86环境中
    • 可以运行在X64环境中
  • 如果你的.NET组件使用"X86"作为目标平台编译
    • 可以运行在X86环境中
    • 可以运行在X64环境中(在WOW64模式下)

WOW64模式

因为向后兼容对于产品销售很有帮助,所以Windows 64位操作系统提供了一个为运行32位应用程序的兼容模式。它被称为WOW模式(Windows on Windows)。你有使用模拟器在PC上玩过电视游戏吗?它就类似这个模拟器。WOW64是一个在64位机器上运行32位应用程序的模拟器。尽管我们还会在后面讨论一些关于WOW64模式的问题,你可以先访问这里来了解WOW模式的更多细节。

检测"Any CPU"进程的位数(Detecting the bitness of a "Any CPU" process)

当你把.NET组件编译成为"Any CPU"时,会发生什么事情呢?如果你知道这个.NET组件总是运行在X86环境下,那运行这个组件的进程也会是32位的。但是如果这个.NET组件可以运行在X86和X64环境下,那运行这个组件的进程是32位还是64位呢?更进一步说,如果它运行在X64操作系统上,JIT编译器会选择32位还是64位来编译它呢?一般来说,它是64位的。但是怎样能够检测到这个呢?很简单,如下代码所示:

1 if (IntPtr.Size == 4)
2 return eArchitecture.x86;
3 else if
4 (IntPtr.Size == 8)
5 return eArchitecture.x64;

System.IntPtr被设计用来保存内存地址的。在.NET中,它提供了一个非常方便的属性告诉我们它的长度。因此,如果它的长度是8个字节,我们就是在用64位地址。如果是4字节,我们就在32位环境中。就是如此简单!

注意:如果你的组件编译时选择X86为目标平台,运行它的进程永远都是32位。即使运行在64位环境中,它使用的是WOW64模式(模拟32位),并且仍然是32位进程

Windows 64位基础结构(Windows 64-bit infrastructure)

如上所述,Windows 64位操作系统提供了WOW64模式来允许运行X86应用程序。微软的工程师们决定不在你的硬盘上混淆32位和64位应用程序,所有X64应用程序被安装到默认的"Program Files"文件夹下面,同时,X86应用程序被安装到新的"Program Files(X86)"文件夹下面。

Windows X64注册表

在X64的Windows操作系统中,WOW64模式为32位应用程序和64位应用程序提供了分开的注册表逻辑视图(logical view),这让应用程序像访问两个独立的注册表。当为X64平台开发或者部署应用程序时,这是需要考虑的重要一点,因为X86-targeted的安装工程会以不同的方式访问注册表。简单来说,它用以下的方式工作:

Windows截获来自应用程序和组件对于注册表的调用,并且重定向到相应的注册表逻辑视图。这个过程对应用程序是透明的。可以从这里了解到注册表重定向的更多信息。

尽管下面的命名方式不是百分百正确,我们仍然分别称这两个注册表逻辑视图为32位注册表和64位注册表。

使用Visual Studio安装工程部署应用程序(Deploy Application with Visual Studio Setup Projects)

和很多人一样,如果你也是使用Visual Studio安装工程和Windows 安装包来部署你的应用程序,并且你想让你的应用程序同时兼容"X86"和"X64"平台,这里有一组你需要考虑的事情。

Visual Studio工程的目标平台

就像一般的组件一样,安装工程也有目标平台属性,当你在工程浏览器(Solution Explorer)中选中安装工程时,你可以在属性页中看到这个属性。如下图所示:

不幸的是这里没有"Any CPU"选项,所以我们必须自己选择平台。这个平台选择会影响到下面的几个方面:

  • 你应用程序的默认安装目录:"Program Files" 或者"Program Files (X86)"。
  • 如果安装工程需要修改注册表,这个会影响到安装程序是更新64位注册表还是WOW64进程中重定向的32位注册表。如果你的安装工程用到注册表,这是一个重要的事情。

所以,我应该给安装工程选择怎样的目标平台呢?它依赖于你的应用程序怎样被编译的和怎样为运行在X64环境下做准备。具体如下:

  • 如果你的应用程序编译成X86平台相关的,选择X86作为安装工程的目标平台。
  • 如果你的应用程序编译成X64平台相关的,选择X64作为安装工程的目标平台。
  • 如果你的应用程序使用"Any CPU"编译:
    • 如果你的应用程序已经为运行在X64环境下准备好了(例如,注册表访问已经考虑到WOW64模式),选择X64
    • 如果你不能确信你的应用程序已经为X64环境准备好了,选择X86

那我的注册表键值在哪里呢?如上所述,如果你的安装工作使用X86作为目标平台,它就不会修改64位注册表,而是重定向到由WOW64模式提供的32位注册表。因此,当你正常打开"regedit.exe"时,并不能找到它们。取而代之的是你需要打开这个特别的RegEdit去显示32位注册表:[Windows Installation path]\SysWOW64\regedit.exe

从C#访问注册表(Acessing the rigistry from C#)

你已经理解了Windows X64基础架构怎样设计的,以及你的应用程序怎样被安装工程安装的。现在,你可能需要学习怎样处理Window X64中两个注册表视图(Windows 64位注册表和Windows 32位注册表)。尤其是当你的应用程序以X86编译的并运行在WOW64模式下时。

常见的.NET访问注册表方式

想象你现在需要访问"LOCAL_MACHINE\Software"注册表节点下面的某个键。在.NET中,一般用下面的代码来做这件事情(使用名字空间Microsoft.Win32):

1 RegistryKey key = Registry.LocalMachine.OpenSubKey("Software\\[Your Key Here]");

这种做法在Windows X64机器中将只能访问到"64位"注册表,即使你的进程是运行在WOW64模式下面。因此,如果你的应用程序是用X86安装工程安装的,我们上面要找的注册表项就会找不到(因为安装包目标平台是X86,所有的注册表项安装时都写到"32位"注册表中)。我们真正需要访问的是由WOW64提供的"32位"注册表。

从C#访问32位WOW64注册表

到目前为止,.NET平台还没有方法让我们直接访问WOW64模式下面的32位注册表。为了实现这个功能,我们必须要使用Windows自身的API调用,导入"advapi32.dll"并使用函数RegOpenKeyEx。这个函数有一个参数"samDesired",它是用来指定访问选项的标识组合,其中一个选项(KEY_WOW64_32KEY)就是指定我们希望打开的注册表视图是WOW64下的32位注册表。

samDesired中的标识

 

ValueMeaning

KEY_ALL_ACCESS (0xF003F)

Combines the STANDARD_RIGHTS_REQUIRED, KEY_QUERY_VALUE, KEY_SET_VALUE, KEY_CREATE_SUB_KEY, KEY_ENUMERATE_SUB_KEYS, KEY_NOTIFY, and KEY_CREATE_LINK access rights.

KEY_CREATE_LINK (0x0020)

Reserved for system use.

KEY_CREATE_SUB_KEY (0x0004)

Required to create a subkey of a registry key.

KEY_ENUMERATE_SUB_KEYS (0x0008)

Required to enumerate the subkeys of a registry key.

KEY_EXECUTE (0x20019)

Equivalent to KEY_READ.

KEY_NOTIFY (0x0010)

Required to request change notifications for a registry key or for subkeys of a registry key.

KEY_QUERY_VALUE (0x0001)

Required to query the values of a registry key.

KEY_READ (0x20019)

Combines the STANDARD_RIGHTS_READ, KEY_QUERY_VALUE, KEY_ENUMERATE_SUB_KEYS, and KEY_NOTIFY values.

KEY_SET_VALUE (0x0002)

Required to create, delete, or set a registry value.

KEY_WOW64_32KEY (0x0200)

Indicates that an application on 64-bit Windows should operate on the 32-bit registry view. For more information, see Accessing an Alternate Registry View.

This flag must be combined using the OR operator with the other flags in this table that either query or access registry values.

Windows 2000: This flag is not supported.

KEY_WOW64_64KEY (0x0100)

Indicates that an application on 64-bit Windows should operate on the 64-bit registry view. For more information, see Accessing an Alternate Registry View.

This flag must be combined using the OR operator with the other flags in this table that either query or access registry values.

Windows 2000: This flag is not supported.

KEY_WRITE (0x20006)

Combines the STANDARD_RIGHTS_WRITE, KEY_SET_VALUE, and KEY_CREATE_SUB_KEY access rights.

实现代码

1 [DllImport("advapi32.dll", CharSet = CharSet.Unicode, EntryPoint = "RegOpenKeyEx")]
2 static extern int RegOpenKeyEx(IntPtr hKey, string subKey, uint options, int sam,
3 out IntPtr phkResult);
4
5 [Flags]
6 public enum eRegWow64Options : int
7 {
8 None = 0x0000,
9 KEY_WOW64_64KEY = 0x0100,
10 KEY_WOW64_32KEY = 0x0200,
11 // Add here any others needed, from the table of the previous chapter
12 }
13
14 [Flags]
15 public enum eRegistryRights : int
16 {
17 ReadKey = 131097,
18 WriteKey = 131078,
19 }
20
21 public static RegistryKey OpenSubKey(RegistryKey pParentKey, string pSubKeyName,
22 bool pWriteable,
23 eRegWow64Options pOptions)
24 {
25 if (pParentKey == null || GetRegistryKeyHandle(pParentKey).Equals(System.IntPtr.Zero))
26 throw new System.Exception("OpenSubKey: Parent key is not open");
27
28 eRegistryRights Rights = eRegistryRights.ReadKey;
29 if (pWriteable)
30 Rights = eRegistryRights.WriteKey;
31
32 System.IntPtr SubKeyHandle;
33 System.Int32 Result = RegOpenKeyEx(GetRegistryKeyHandle(pParentKey), pSubKeyName, 0,
34 (int)Rights | (int)pOptions, out SubKeyHandle);
35 if (Result != 0)
36 {
37 System.ComponentModel.Win32Exception W32ex =
38 new System.ComponentModel.Win32Exception();
39 throw new System.Exception("OpenSubKey: Exception encountered opening key",
40 W32ex);
41 }
42
43 return PointerToRegistryKey(SubKeyHandle, pWriteable, false);
44 }
45
46 private static System.IntPtr GetRegistryKeyHandle(RegistryKey pRegisteryKey)
47 {
48 Type Type = Type.GetType("Microsoft.Win32.RegistryKey");
49 FieldInfo Info = Type.GetField("hkey", BindingFlags.NonPublic | BindingFlags.Instance);
50
51 SafeHandle Handle = (SafeHandle)Info.GetValue(pRegisteryKey);
52 IntPtr RealHandle = Handle.DangerousGetHandle();
53
54 return Handle.DangerousGetHandle();
55 }
56
57 private static RegistryKey PointerToRegistryKey(IntPtr hKey, bool pWritable,
58 bool pOwnsHandle)
59 {
60 // Create a SafeHandles.SafeRegistryHandle from this pointer - this is a private class
61 BindingFlags privateConstructors = BindingFlags.Instance | BindingFlags.NonPublic;
62 Type safeRegistryHandleType = typeof(
63 SafeHandleZeroOrMinusOneIsInvalid).Assembly.GetType(
64 "Microsoft.Win32.SafeHandles.SafeRegistryHandle");
65
66 Type[] safeRegistryHandleConstructorTypes = new Type[] { typeof(System.IntPtr),
67 typeof(System.Boolean) };
68 ConstructorInfo safeRegistryHandleConstructor =
69 safeRegistryHandleType.GetConstructor(privateConstructors,
70 null, safeRegistryHandleConstructorTypes, null);
71 Object safeHandle = safeRegistryHandleConstructor.Invoke(new Object[] { hKey,
72 pOwnsHandle });
73
74 // Create a new Registry key using the private constructor using the
75 // safeHandle - this should then behave like
76 // a .NET natively opened handle and disposed of correctly
77 Type registryKeyType = typeof(Microsoft.Win32.RegistryKey);
78 Type[] registryKeyConstructorTypes = new Type[] { safeRegistryHandleType,
79 typeof(Boolean) };
80 ConstructorInfo registryKeyConstructor =
81 registryKeyType.GetConstructor(privateConstructors, null,
82 registryKeyConstructorTypes, null);
83 RegistryKey result = (RegistryKey)registryKeyConstructor.Invoke(new Object[] {
84 safeHandle, pWritable });
85 return result;
86 }
87
88

"OpenSubKey"函数将会返回搜索的注册表项,它允许你指定从正常的注册表中读取还是从WOW64模式下的32位注册表中读取。下面的例子就是从WOW64模式下的32位注册表中读取注册表项:

1 try
2 {
3 RegistryKey key = OpenSubKey(Registry.LocalMachine,"Software\\[Key]",false,
4 eRegWow64Options.KEY_WOW64_32KEY);
5 }
6 catch
7 {
8 // Parent key not open, exception found at opening (probably related to
9 // security permissions requested)
10 }

你只需要把上面代码中的"[Key]"替换成你想要搜索的注册表项即可。

怎样让你的应用程序是注册表读取安全的(How to make your application a bulletproof registry reader)

现在,你已经有方法读取X64环境下的两个注册表了,我建议你用下面的方式来处理注册表访问问题

  1. 首先,使用.NET中的标准方式"Registry.LocalMachine.OpenSubKey"查找注册表项。这个方式能够覆盖你的程序运行在32位Windows上和运行在64位Windows上非WOW64模式的情况。
  2. 如果注册表项没有找到,用下面方式中的一种进行处理:
    1. 检测Windows版本
      1. 32位:异常,注册表没有找到
      2. 64位:尝试使用上面的代码去访问WOW64模式下的注册表。如果同样没有找到,启动异常处理
    2. 直接尝试去访问WOW64模式下的注册表,但把这段代码放到"try-catch"语句内。如果捕获到异常,或者注册表项没有找到,启动异常处理

Del.icio.us : .NET Compilation, .NET编译, WOW64, X64, X86, 注册表访问

posted on 2010-04-05 20:53  xuguilin  阅读(5071)  评论(8编辑  收藏  举报