.net - 无法读取“GetProcessPreferredUILanguages”函数返回的所有语言名称
问题描述
在我使用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
解决方案
缓冲区包含一个以 null 结尾的多字符串:返回的字符串在第一个\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#
's bool
,VB.Net
's Boolean
。<MarshalAs(UnmanagedType.Bool)>
不需要。
VB.Net 版本:
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
,最初设置为IntPtr.Zero
。
第一次调用该函数返回文化的数量和所需的缓冲区大小。我们只需要使用Marshal.StringToHGlobalUni分配指定的大小:
string langNames = new string('0', (int)bufferRequiredLength);
buffer = Marshal.StringToHGlobalUni(langNames);
要将其封送回来,我们可以指定需要复制到缓冲区中的字节数。如果我们不指定这个值,字符串无论如何都会被截断:
string langNames = Marshal.PtrToStringUni(buffer, (int)bufferRequiredLength);
当然,我们需要在退出时释放用于缓冲区的内存:
Marshal.FreeHGlobal(buffer);
修改方法的 C# 版本:
[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);
}
}
VB.Net 版本:
<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
和实现代码。
请注意,我已将所有声明更改为charset: Unicode
Marshal.StringToHGlobalUni ,它比Marshal.AllocHGlobal
.
在 Windows 10 1803 17134.765
、Windows 7上测试SP1
。两者都按预期工作。使用此处提供的代码。
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;
}
VB.Net 版本:
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
示例调用:
string allLanguages = string.Empty;
string[] languages = new[] { "en-US", "es-ES", "it-IT", "de-DE", "fr-FR" };
if (SetProcessPreferredUILanguages(languages) > 0)
{
var result = GetProcessPreferredUILanguages();
allLanguages = string.Join(", ", result.OfType<CultureInfo>()
.Select(c => c.Name).ToArray());
}
推荐阅读
- c++ - 如何修复此字符串验证(电话目录系统)
- java - 如何使用 Java Discord API 删除文本频道?
- c# - 如何使用 C# 更新链接到 PowerPoint 中的文本框的自定义属性
- javascript - 试图定义异步函数
- css - 如何正确显示我的 DataTables 表?
- javascript - JS - 阻止用户输入电话号码
- azure - 如何仅为一个设备/服务生成 Azure IoT 中心 SAS 令牌?
- linux - Buildroot - 在每次构建时重新提取 Github 包
- html - HTML + CSS 可悬停下拉菜单扩展标题
- c - Polyspace:在浮动类型中断言 min..max 的标量范围