P/Invoke 未使用调用 GetUserDefaultLocaleName() 的 StringBuilder 参数返回预期结果

P/Invoke not returning expected result with StringBuilder parameter for call to GetUserDefaultLocaleName()

我正在尝试使用 GetUserDefaultLocaleName 获取当前在区域设置控制面板中设置的区域设置的区域性名称。但是,当我将 StringBuilder 作为参数传递时,我没有得到预期的结果。

static class NativeMethods
{
    [DllImport("kernel32.dll")]
    public static extern int GetUserDefaultLocaleName(StringBuilder buf, int bufferLength);
}

static void Main(string[] args)
{
    const int nChars = 256;
    var sb = new StringBuilder(nChars);
    int cultureNameLength = NativeMethods.GetUserDefaultLocaleName(sb, nChars);
    string cultureName = sb.ToString();
    Console.WriteLine(cultureName);
    Console.ReadLine();
}

如果我将当前文化设置为 "en-US",当我 运行 代码时,cultureName 设置为 "e"(仅当前文化的第一个字母),但 cultureNameLength设置为 6(预期为空终止字符串 "en-US[=21=]")

为什么这只是 return 文化名称的第一个字母(我也测试了其他文化和 "fr-FR" returns "f")?有没有我可以传递的不同的数据结构而不是 StringBuilder 来成功地获得它 return 区域性名称?

症状:GetUserDefaultLocaleName() returns 一个值 > 0(成功),但 StringBuilder 缓冲区似乎只包含 1 个字符。

  • DllImport attribute's Charset 字段默认值设置为 Charset.Ansi,因此平台调用编组字符串从其托管格式 (Unicode) 到 ANSI 格式。
  • 由于字符串是以 Unicode 格式生成的,因此编组的 ANSI 字符串在第一个空 ([=13=]) 字符处被截断。

Use this field with a member of the CharSet enumeration to specify the marshaling behavior of string parameters and to specify which entry-point name to invoke (the exact name given or a name ending with "A" or "W"). The default enumeration member for C# and Visual Basic is CharSet.Ansi and the default enumeration member for C++ is CharSet.None, which is equivalent to CharSet.Ansi. In Visual Basic, you use the Declare statement to specify the CharSet field.

另请参阅:Specifying a Character Set

要解决问题,请明确设置CharSet = CharSet.Unicode
CharSet = CharSet.Auto 也可以解决问题,但最好不要相信它,字符串是以 Unicode 格式生成的。最好不要让目标平台确定不同的字符串格式,它可能会失败。

internal const int LOCALE_NAME_MAX_LENGTH = 85;

[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
internal static extern int GetUserDefaultLocaleName(StringBuilder buf, int bufferLength);

// [...]

var sb = new StringBuilder(LOCALE_NAME_MAX_LENGTH);
int locale = GetUserDefaultLocaleName(sb, LOCALE_NAME_MAX_LENGTH);

注意:LOCALE_NAME_MAX_LENGTH 应该被导入,而不是硬编码为 85,但这是您可以在 C# 中执行的操作:)