Буфер содержит завершенную нулем многострочную строку: возвращаемая строка усекается до первого \0
символа.
Поскольку функция GetProcessPreferredUILanguages
ожидает указатель на буфер, который будет содержать идентификаторы культур, давайте предоставим один, а затем перенаправим его обратно, используя указанную длину буфера.
Это оригинальное определение функции 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#
'bool
, VB.Net
' 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());
}