如何:在没有管理特权的情况下保存自定义区域性

更新:2010 年 12 月

本主题描述无需管理特权即可将自定义区域性数据保存到文件的技术。 以管理权限运行的应用程序使用 CultureAndRegionInfoBuilder.Register 方法注册自定义区域性,如主题如何:创建自定义区域性中所述。 此方法在内部调用 CultureDefinition.Compile 方法,后者不要求管理特权。

若要在没有管理特权的情况下保存自定义区域性,您可以使用反射直接访问 CultureDefinition.Compile 方法。

警告说明警告

直接调用 CultureDefinition.Compile 方法不受支持。该方法可能会更改,甚至会删除,恕不另行通知。此主题仅针对 .NET Framework 4 版 描述其行为。此方法使用不当会使您的计算机处于不稳定状态,甚至可能会导致应用程序崩溃或数据丢失。

关于 CultureDefinition.Compile

CultureDefinition.Compile 方法是 System.Globalization.CultureDefinition sysglobl.dll 程序集中内部类的成员之一。 CultureDefinition.Compile 将有关自定义区域性的信息写入文件。 但是,因为该方法没有向注册表写入任何内容,所以它不要求管理特权。

语法

internal static void Compile(
   CultureAndRegionInfoBuilder builder,
   String outFile
);

参数

  • builder
    要读取的 CultureAndRegionInfoBuilder 对象。 此对象的情况与 CultureAndRegionInfoBuilder.Register 的情况相同。

  • outFile
    表示指向输出文件的完整路径的字符串。 该方法将编写自定义区域性文件,但不会注册该文件。 将不会识别输出文件中包含的自定义区域性定义,即使它位于 %windir%\globalization 目录中。

异常

因为这是不受支持的私有方法,所以任何异常都应视为故障。

备注

下列为 CultureAndRegionInfoBuilder.Register 和 CultureDefinition.Compile 之间的差异。

  • CultureAndRegionInfoBuilder.Register 是受支持的公共方法。 作为内部方法,CultureDefinition.Compile 在未来版本中可能会发生更改或删除,因而不可靠。

  • CultureAndRegionInfoBuilder.Register 要求管理特权,而 CultureDefinition.Compile 将写入用户有权创建的任何文件。

  • CultureAndRegionInfoBuilder.Register 执行的验证比 CultureDefinition.Compile 多。 因此,直接调用后一种方法可能会创建一个通常无法在计算机上安装的无效区域性。 创建的区域性可能包含不规则数据或可能导致操作系统或 .NET Framework 出现故障的数据。

  • CultureAndRegionInfoBuilder.Register 始终在 %windir%\globalization 目录中创建其输出文件。 CultureDefinition.Compile 写入任何您指定的输出文件。

  • 如果 builder 参数指定的 CultureAndRegionInfoBuilder 对象包含不一致或意外的数据,则 CultureAndRegionInfoBuilder.Register 可能引发异常。 但是,此方法不会像 CultureAndRegionInfoBuilder.Register 一样进行全面验证。

    警告说明警告

    由于 CultureDefinition.Compile 采用的是有限验证机制,因此它会创建一些无效或不一致的区域性,从而有可能导致应用程序崩溃甚至丢失数据。

  • CultureAndRegionInfoBuilder.Register 在注册表中注册自定义区域性文件。 CultureDefinition.Compile 会创建自定义区域性,但不会在本地操作系统上注册(安装)它。 %windir%\globalization 目录中未注册的文件出现问题,并且在 .NET Framework 4 中不能完全发挥作用。 虽然看起来可能可操作,但注册不当的自定义区域性文件可能导致不稳定的行为或故障(如果被操作系统或 .NET Framework 访问)。

  • 当您调用 CultureAndRegionInfoBuilder.Register 方法时,它将创建自定义区域性文件。 它也设置 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\CustomLocale 注册表项,它会为之创建一个命名该区域性的字符串值。 CultureDefinition.Compile 方法会创建自定义区域性文件,但不会创建对应的注册表项。

    注意注意

    从 .NET Framework 3.5 版 开始,CultureDefinition.Compile 方法不再设置 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\IetfLanguage 注册表项,因为 .NET Framework 不使用该注册表项。已固定区域性名称以符合 RFC 4646。

直接使用 CultureDefinition.Compile

使用 CultureDefinition.Compile 保存自定义区域性:

  1. 将代码包含在您的应用程序中,以便执行任何必要的验证。 请记住,在直接使用 CultureDefinition.Compile 方法时,不会执行 CultureAndRegionInfoBuilder.Register 方法执行的验证。 虽然生成的自定义区域性文件是可访问的,但未正确构造的文件可能会表现出非法或异常行为。

  2. 不是调用 CultureAndRegionInfoBuilder.Register,而是调用 CultureDefinition.Compile 方法,并将它传递给 CultureAndRegionInfoBuilder 对象,连同相应的文件名。

  3. 按照下一过程中的说明注册生成的自定义区域性文件。

注册(安装)自定义区域性文件

若要注册(安装)自定义区域性文件:

  1. 将代码包含在您的应用程序中,以便将 CultureDefinition.Compile 调用的输出写入到 %windir%\globalization 子目录。

  2. 若要使自定义区域性成功,在 .NET Framework 4 下运行时,包括要写入 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\CustomLocale 注册表项的代码。

示例

下面的示例使用 CultureAndRegionInfoBuilder 类来创建名为 x-en-US-example 的自定义区域性,该区域性基于英语(美国)区域性,但使用不同的货币符号(而不是美元 $)。 该示例使用反射通过调用私有 CultureDefinition.Compile 方法来编译区域性定义。 然后,它复制所编译的。 nlp 文件到 Windows 目录的全球化子目录,并在注册表中注册自定义区域性。 下一步,它实例化一个表示自定义区域性的 CultureInfo 对象,并在格式化操作中使用它。 最后,该示例调用 CultureAndRegionInfoBuilder.Unregister 方法来删除自定义区域性定义。 注意,必须将对 Microsoft.VisualBasic.DLL 的引用添加到项目中,以成功编译该示例。

Imports Microsoft.Win32
Imports System.Globalization
Imports System.IO
Imports System.Reflection

Module Example
   Private Const MAX_PATH As Integer = 260
   Private Const CUSTOM_KEY As String = "SYSTEM\CurrentControlSet\Control\Nls\CustomLocale"
   Private Declare Function GetWindowsDirectory Lib "Kernel32" _
           Alias "GetWindowsDirectoryA" _ 
           (lpBuffer As String, nSize As Integer) As Integer

   Private cultureName As String = "x-en-US-example"

   Public Sub Main()
      ' Create an alternate en-US culture.
      Dim enUS As New CultureAndRegionInfoBuilder(cultureName, CultureAndRegionModifiers.None)
      enUS.LoadDataFromCultureInfo(CultureInfo.CreateSpecificCulture("en-US"))
      enUS.LoadDataFromRegionInfo(New RegionInfo("US"))
      enUS.NumberFormat.CurrencySymbol = "USD"
      enUS.NumberFormat.CurrencyPositivePattern = 2
      enUS.NumberFormat.CurrencyNegativePattern = 12

      ' Use reflection to get the CultureDefinition.Compile method.
      Dim assem As Assembly = Assembly.Load("sysglobl, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")            
      Dim defType As Type = assem.GetType("System.Globalization.CultureDefinition")
      Dim method As MethodInfo = defType.GetMethod("Compile", BindingFlags.NonPublic Or BindingFlags.Static)
      Dim tempPath As String = ".\" + cultureName + ".nlp"
      Dim args() As Object = { enUS, tempPath }

      ' Delete target file if it already exists.
      If File.Exists(tempPath) Then File.Delete(tempPath)

      ' Compile the culture definition.
      method.Invoke(Nothing, args)  

      ' Copy the file.
      Try
         Dim buffer As New String(ChrW(0), MAX_PATH)
         Dim charsWritten As Integer = GetWindowsDirectory(buffer, MAX_PATH)
         Dim fileName As String = String.Format("{0}{1}Globalization{1}{2}.nlp", 
                                                buffer.Substring(0, charsWritten),
                                                Path.DirectorySeparatorChar,
                                                cultureName) 
         File.Copy(tempPath, fileName, True)
         WriteToRegistry(CUSTOM_KEY, cultureName)       
      Catch e As UnauthorizedAccessException
         Console.WriteLine("You must run this application as an administrator")
         Console.WriteLine("so that you can install culture definition files.") 
         Exit Sub
      End Try

      ' Create and use the new culture.
      Try
         Dim value As Decimal = 1603.42d
         Dim ci As New CultureInfo(cultureName)
         Console.WriteLine(String.Format(ci, "{0:C2}", value))
      Catch e As CultureNotFoundException
         Console.WriteLine("Unable to create the '{0}' culture.", cultureName)
      End Try

      CultureAndRegionInfoBuilder.Unregister(cultureName)
   End Sub

   Public Sub WriteToRegistry(keyName As String, valueName As String)
      Dim key As RegistryKey = Registry.LocalMachine.OpenSubKey(keyName, True)
      ' Create the key if it does not already exist.
      If key Is Nothing      
         key = Registry.LocalMachine.CreateSubKey(keyName)
         If key Is Nothing Then Throw New NullReferenceException("Cannot create the registry key")
      End If
      ' Set the new name
      key.SetValue(valueName, valueName)
      key.Close()
   End Sub
End Module
' The example displays the following output:
'         USD 1,603.42
using Microsoft.Win32;
using System;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;

public class Example
{
   private const int MAX_PATH = 260;
   private const string CUSTOM_KEY = @"SYSTEM\CurrentControlSet\Control\Nls\CustomLocale";

   [DllImport("kernel32", SetLastError=true)]
   private static extern int GetWindowsDirectory(StringBuilder lpBuffer, 
                                                  int nSize);

   private static string cultureName = "x-en-US-example";

   public static void Main()
   {
      // Create an alternate en-US culture.
      CultureAndRegionInfoBuilder enUS = new CultureAndRegionInfoBuilder(cultureName, CultureAndRegionModifiers.None);
      enUS.LoadDataFromCultureInfo(CultureInfo.CreateSpecificCulture("en-US"));
      enUS.LoadDataFromRegionInfo(new RegionInfo("US"));
      enUS.NumberFormat.CurrencySymbol = "USD";
      enUS.NumberFormat.CurrencyPositivePattern = 2;
      enUS.NumberFormat.CurrencyNegativePattern = 12;

      // Use reflection to get the CultureDefinition.Compile method.
      Assembly assem = Assembly.Load("sysglobl, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");            
      Type defType = assem.GetType("System.Globalization.CultureDefinition");
      MethodInfo method = defType.GetMethod("Compile", BindingFlags.NonPublic | BindingFlags.Static);
      string tempPath = @".\" + cultureName + ".nlp";
      object[] args = { enUS, tempPath };
      // Delete target file if it already exists.
      if (File.Exists(tempPath))
         File.Delete(tempPath);

      // Compile the culture definition.
      method.Invoke(null, args);  
      // Copy the file.
      try {
         StringBuilder buffer = new StringBuilder(MAX_PATH);
         int charsWritten = GetWindowsDirectory(buffer, MAX_PATH);
         string fileName = String.Format("{0}{1}Globalization{1}{2}.nlp", 
                                         buffer.ToString().Substring(0, charsWritten),
                                         Path.DirectorySeparatorChar,
                                         cultureName); 
         File.Copy(tempPath, fileName, true);
         WriteToRegistry(CUSTOM_KEY, cultureName);       
      }
      catch (UnauthorizedAccessException) {
         Console.WriteLine("You must run this application as an administrator");
         Console.WriteLine("so that you can install culture definition files."); 
         return;
      }

      // Create and use the new culture.
      try {
         decimal value = 1603.42m;
         CultureInfo ci = new CultureInfo(cultureName);
         Console.WriteLine(String.Format(ci, "{0:C2}", value));
      }
      catch (CultureNotFoundException) {
         Console.WriteLine("Unable to create the '{0}' culture.", cultureName);
      }

      CultureAndRegionInfoBuilder.Unregister(cultureName);
   }

   public static void WriteToRegistry(string keyName, string valueName)
   {
      RegistryKey key = Registry.LocalMachine.OpenSubKey(keyName, true);
      // Create the key if it does not already exist.
      if (key == null) {      
         key = Registry.LocalMachine.CreateSubKey(keyName);
         if (key == null) throw new NullReferenceException("Cannot create the registry key");
      }
      // Set the new name
      key.SetValue(valueName, valueName);
      key.Close();
   }
}
// The example displays the following output:
//        USD 1,603.42

请参见

任务

如何:创建自定义区域性

修订记录

Date

修订记录

原因

2010 年 12 月

针对 .NET Framework 4 进行了修订,并添加了示例。

信息补充。