在我使用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
答案 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