无法读取“ GetProcessPreferredUILanguages”函数返回的所有语言名称

时间:2019-06-04 19:25:39

标签: .net vb.net winapi

在我使用SetProcessPreferredUILanguages设置了5种首选语言之后,并确保它能正常工作是因为pulNumLanguages的长度与调用完成后我的自定义语言名称分隔字符串的长度相同。

然后,我现在尝试通过GetProcessPreferredUILanguages函数获取所有流程首选的UI语言。问题是我只能在返回的字符串缓冲区中读取一种(第一种)语言名称,但是pulNumLanguages指定返回5种语言...

因此,我将要求正确的方法来读取返回的字符串。

请注意有关pwszLanguagesBuffer参数的文档的内容:

  

指向双空终止多字符串缓冲区的指针,其中   函数优先检索有序,以空分隔的列表   顺序,从最优选开始。

这是我的定义:

<DllImport("Kernel32.dll", SetLastError:=True, ExactSpelling:=True, CharSet:=CharSet.Unicode)>
Public Shared Function GetProcessPreferredUILanguages(ByVal flags As UiLanguageMode,
                                                <Out> ByRef refNumLanguages As UInteger,
                    <MarshalAs(UnmanagedType.LPWStr)> ByVal languagesBuffer As StringBuilder,
                                                      ByRef refLanguagesBufferSize As UInteger
) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function

以及我如何使用它:

Public Shared Function GetProcessPreferredUILanguages() As IReadOnlyCollection(Of CultureInfo)

    Dim buffer As New StringBuilder(0)
    Dim numLangs As UInteger
    Dim bufferRequiredLength As UInteger

    ' I do this because If the StringBuilder capacity exceeds the exact required, then I got a blank (or unreadable) string.
    NativeMethods.GetProcessPreferredUILanguages(UiLanguageMode.Name, numLangs, Nothing, bufferRequiredLength)
    buffer.Capacity = CInt(bufferRequiredLength)

    NativeMethods.GetProcessPreferredUILanguages(UiLanguageMode.Name, numLangs, buffer, bufferRequiredLength)
    Console.WriteLine($"{NameOf(numLangs)}: {numLangs}")
    Console.WriteLine(buffer?.ToString().Replace(ControlChars.NullChar, " "))

    Dim langList As New List(Of CultureInfo)
    For Each langName As String In buffer.ToString().Split({ControlChars.NullChar}, StringSplitOptions.RemoveEmptyEntries)
        langList.Add(New CultureInfo(langName))
    Next
    Return langList

End Function

我认为问题是我缺少替换字符串中其他一些空字符的方法。


此外,出于测试目的,我还将分享与SetProcessPreferredUILanguages函数相关的源代码:

<DllImport("Kernel32.dll", SetLastError:=True, ExactSpelling:=True, CharSet:=CharSet.Unicode)>
Public Shared Function SetProcessPreferredUILanguages(ByVal flags As UiLanguageMode,
                    <MarshalAs(UnmanagedType.LPWStr)> ByVal languagesBuffer As String,
                                                <Out> ByRef refNumLanguages As UInteger
) As <MarshalAs(UnmanagedType.Bool)> Boolean
End Function

并且:

Public Function SetProcessPreferredUILanguages(ParamArray langNames As String()) As Integer

    If (langNames Is Nothing) Then
        Throw New ArgumentNullException(paramName:=NameOf(langNames))
    End If

    Dim langList As New List(Of String)
    For Each langName As String In langNames
        langList.Add(langName & ControlChars.NullChar)
    Next

    Dim numLangs As UInteger = CUInt(langList.Count)

    NativeMethods.SetProcessPreferredUILanguages(UiLanguageMode.Name, String.Concat(langList), numLangs)

    #If DEBUG Then
        If numLangs = langList.Count Then
            Debug.WriteLine("Successfully changed UI languages")
        ElseIf numLangs < 1 Then
            Debug.WriteLine("No language could be set.")
        Else
            Debug.WriteLine("Not all languages were set.")
        End If
    #End If

    langList.Clear()
    Return CInt(numLangs)

End Function

1 个答案:

答案 0 :(得分:1)

缓冲区包含一个以空字符结尾的多字符串:返回的字符串在第一个\0字符处被截断。

由于 GetProcessPreferredUILanguages 函数需要一个指向将包含区域性ID的缓冲区的指针,因此我们提供一个,然后使用指定的缓冲区长度将其编组回去。

这是GetProcessPreferredUILanguages函数的原始定义(其中使用dwFlags枚举提供 uint 参数):

public enum MUIFlags : uint
{
    MUI_LANGUAGE_ID = 0x4,      // Use traditional language ID convention
    MUI_LANGUAGE_NAME = 0x8,    // Use ISO language (culture) name convention
}

[SuppressUnmanagedCodeSecurity, SecurityCritical]
internal static class NativeMethods
{
    [DllImport("Kernel32.dll", SetLastError = true,  CharSet = CharSet.Unicode)]
    internal static extern bool GetProcessPreferredUILanguages(MUIFlags dwFlags, 
        ref uint pulNumLanguages, IntPtr pwszLanguagesBuffer, ref uint pcchLanguagesBuffer);

    [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    internal static extern bool SetProcessPreferredUILanguages(MUIFlags dwFlags, 
        string pwszLanguagesBuffer, ref uint pulNumLanguages);
}

顺便说一句,Win32函数的返回值声明为 BOOL ,它将被整理为C#的{​​{1}},bool' s VB.Net。不需要Boolean

VB.Net版本:

<MarshalAs(UnmanagedType.Bool)>

接收缓冲区被声明为 Public Enum MUIFlags As UInteger MUI_LANGUAGE_ID = &H4 ' Use traditional language ID convention MUI_LANGUAGE_NAME = &H8 ' Use ISO language (culture) name convention End Enum <SuppressUnmanagedCodeSecurity, SecurityCritical> Friend Class NativeMethods <DllImport("Kernel32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)> Friend Shared Function GetProcessPreferredUILanguages(dwFlags As MUIFlags, ByRef pulNumLanguages As UInteger, pwszLanguagesBuffer As IntPtr, ByRef pcchLanguagesBuffer As UInteger) As Boolean End Function <DllImport("Kernel32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)> Friend Shared Function SetProcessPreferredUILanguages(dwFlags As MUIFlags, pwszLanguagesBuffer As String, ByRef pulNumLanguages As UInteger) As Boolean End Function End Class ,最初设置为 IntPtr buffer
函数的第一个调用返回区域性数和所需的缓冲区大小。我们只需要使用Marshal.StringToHGlobalUni分配指定的大小:

IntPtr.Zero

要封送它,我们可以指定需要复制到缓冲区中的字节数。如果我们不指定此值,则无论如何该字符串都会被截断:

string langNames = new string('0', (int)bufferRequiredLength);
buffer = Marshal.StringToHGlobalUni(langNames);

当然,我们需要在退出时释放用于缓冲区的内存:

string langNames = Marshal.PtrToStringUni(buffer, (int)bufferRequiredLength);

修改后的方法的C#版本:

Marshal.FreeHGlobal(buffer);

VB.Net版本:

[SecuritySafeCritical]
public static List<CultureInfo> GetProcessPreferredUILanguages()
{
    uint numLangs = 0;
    uint bufferRequiredLength = 0;
    IntPtr buffer = IntPtr.Zero;
    try {
        bool result = NativeMethods.GetProcessPreferredUILanguages(MUIFlags.MUI_LANGUAGE_NAME, ref numLangs, IntPtr.Zero, ref bufferRequiredLength);
        if (!result) return null;

        string langNames = new string('0', (int)bufferRequiredLength);
        buffer = Marshal.StringToHGlobalUni(langNames);
        result = NativeMethods.GetProcessPreferredUILanguages(MUIFlags.MUI_LANGUAGE_NAME, ref numLangs, buffer, ref bufferRequiredLength);
        string langNames = Marshal.PtrToStringUni(buffer, (int)bufferRequiredLength);
        if (langNames.Length > 0)
        {
            string[] isoNames = langNames.Split(new[] { "\0" }, StringSplitOptions.RemoveEmptyEntries);
            var cultures = new List<CultureInfo>();
            foreach (string lang in isoNames) {
                cultures.Add(CultureInfo.CreateSpecificCulture(lang));
            }
            return cultures;
        }
        return null;
    }
    finally {
        Marshal.FreeHGlobal(buffer);
    }
}

更新

添加了{strong> <SecuritySafeCritical> Public Shared Function GetProcessPreferredUILanguages() As List(Of CultureInfo) Dim numLangs As UInteger = 0 Dim bufferRequiredLength As UInteger = 0 Dim buffer As IntPtr = IntPtr.Zero Try Dim result As Boolean = NativeMethods.GetProcessPreferredUILanguages(MUIFlags.MUI_LANGUAGE_NAME, numLangs, IntPtr.Zero, bufferRequiredLength) If Not result Then Return Nothing Dim langNames As String = New String("0"c, CInt(bufferRequiredLength)) buffer = Marshal.StringToHGlobalUni(langNames) result = NativeMethods.GetProcessPreferredUILanguages(MUIFlags.MUI_LANGUAGE_NAME, numLangs, buffer, bufferRequiredLength) langNames = Marshal.PtrToStringUni(buffer, CType(bufferRequiredLength, Integer)) If langNames.Length > 0 Then Dim isoNames As String() = langNames.Split({vbNullChar}, StringSplitOptions.RemoveEmptyEntries) Dim cultures = New List(Of CultureInfo)() For Each lang As String In isoNames cultures.Add(CultureInfo.CreateSpecificCulture(lang)) Next Return cultures End If Return Nothing Finally Marshal.FreeHGlobal(buffer) End Try End Function C#声明和实现代码。
请注意,我已将所有声明更改为 SetProcessPreferredUILanguages Marshal.StringToHGlobalUni,它比 charset: Unicode 更安全(并且可能更合适) 。

在Windows 10 Marshal.AllocHGlobal,Windows 7 1803 17134.765上进行了测试。两者均按预期工作。使用此处显示的代码。

SP1

VB.Net版本:

public static int SetProcessPreferredUILanguages(params string[] langNames)
{
    if ((langNames == null)) {
        throw new ArgumentNullException($"Argument {nameof(langNames)} cannot be null");
    }
    string languages = string.Join("\u0000", langNames);

    uint numLangs = 0;
    bool result = NativeMethods.SetProcessPreferredUILanguages(MUIFlags.MUI_LANGUAGE_NAME, languages, ref numLangs);
    if (!result) return 0;
    return (int)numLangs;
}

通话示例:

Public Shared Function SetProcessPreferredUILanguages(ParamArray langNames As String()) As Integer
    If (langNames Is Nothing) Then
        Throw New ArgumentNullException($"Argument {NameOf(langNames)} cannot be null")
    End If
    Dim languages As String = String.Join(vbNullChar, langNames)

    Dim numLangs As UInteger = 0
    Dim result As Boolean = NativeMethods.SetProcessPreferredUILanguages(MUIFlags.MUI_LANGUAGE_NAME, languages, numLangs)
    If (Not result) Then Return 0
    Return CType(numLangs, Integer)
End Function