Можете ли вы вызвать многобайтовый ANSI в varargs? Что я делаю неправильно?
-
05-07-2019 - |
Вопрос
** ОСНОВНОЕ ОБНОВЛЕНИЕ ** Я сделал небольшую ошибку, но мне все еще интересно, что именно происходит.
Функция, которую я вызываю, на самом деле является "fooV", функцией с такой подписью:
foo(const char *, const char *, EnumType, va_list)
Это очищает AccessViolationException, которые я получил, но не объясняет, почему параметры params работают для всех остальных типов .NET, за исключением строк, которые должны быть преобразованы в многобайтовые символы ANSI. Я собираюсь попросить разработчика DLL раскрыть версию, которая на самом деле использует ... в списке параметров, любые подсказки для PInvoking, если va_list является параметром?
** старая запись **
Это связано, но отличается от недавнего вопроса Я спросил .
Я должен использовать PInvoke для вызова функции библиотеки C, которая имеет следующую подпись:
foo(const char *, const char *, EnumType, ...)
Функция настраивает ресурс в зависимости от StructType; интересующий меня случай настраивает что-то, что ожидает, что varargs будет одной многобайтовой строкой ANSI. Я использовал эту подпись PInvoke для вызова функции:
[DllImport(DllName, CharSet = CharSet.Ansi, EntryPoint = "foo")]
public static extern int Foo(string s1, string s2, EnumType st1, params string[] s3);
Строка params []
показалась мне странной, но предыдущие сигнатуры, которые вызывали эту функцию, использовали ее для других типов, таких как bool
, поэтому я следовал шаблону. р>
Я обернул это более дружественным методом .NET:
public void Foo(string s1, string s2, string s3)
{
int error = Dll.Foo(s1, s2, EnumType.Foo, s3);
// handle errors
}
Недавно я изменил подпись, включив в нее " BestFitMapping = false, ThrowOnUnmappableChar = true " в атрибуте DLLImport, чтобы соответствовать рекомендациям FxCop. Это красная сельдь, как я опишу позже.
Чего я ожидаю от этого изменения, так это ограниченной поддержки Unicode. Например, на машине с английской кодовой страницей будет выдано исключение, если вы передадите строку, содержащую символ японского языка. На японском компьютере я ожидал, что смогу передать японскую строку в функцию. Тест по английскому языку сработал, как и ожидалось, но японский тест выдает исключение System.Runtime.InteropServices.COME с HRESULT 0x8007007A. Это происходит даже без настроек BestFitMapping и ThrowOnUnmappableChar.
Я немного осмотрелся и увидел несколько сайтов, которые предлагали PInvoke varargs, просто указав нормальные аргументы, поэтому я попробовал эту подпись:
[DllImport(DllName, CharSet = CharSet.Ansi, EntryPoint = "foo")]
public static extern int Foo(string s1, string s2, EnumType st1, string s3);
Это создает исключение AccessViolationException, когда я использую его на английском или японском компьютере.
Я не могу использовать UTF-8 или другую кодировку Unicode, потому что эта библиотека C ожидает и обрабатывает только многобайтовую ANSI. Я обнаружил, что указание CharSet.Unicode для этой функции работает, но я беспокоюсь, что это только совпадение, а не то, на что я должен полагаться. Я думал о преобразовании строки в byte [] с использованием системной кодовой страницы, но не могу понять, как указать, что я хотел бы передать байтовый массив параметру varargs.
Что происходит? На английском компьютере английские символы работают нормально, а японские символы генерируют исключение ArgumentException, как и ожидалось. На японском компьютере английские символы работают нормально, а японские символы генерируют COMException. Что-то не так с моей подписью PInvoke? Я попытался использовать атрибут MarshalAs
, чтобы указать тип LPArray и подтип LPStr, но это не удается аналогичным образом. UnmanagedType.LPStr указывает, что это однобайтовая строка ANSI; Есть ли способ указать многобайтовую строку ANSI?
** ОБНОВЛЕНИЕ ** Вот дополнение, учитывающее текущие комментарии.
Если я укажу соглашение о вызовах CDecl и приму обычные параметры, подобные этому:
[DllImport(DllName, CharSet = CharSet.Ansi, EntryPoint = "foo", CallingConvention = CallingConvention.Cdecl)]
public static extern int Foo(string s1, string s2, EnumType e1, string s3)
Когда я использую эту спецификацию, я получаю AccessViolationException. Поэтому я попробовал это:
[DllImport(DllName, CharSet = CharSet.Ansi, EntryPoint = "foo", CallingConvention = CallingConvention.CDecl)]
public static extern int Foo(string s1, string s2, EnumType e1, string s3)
Это работает для английского языка и вызывает исключение COMException при использовании японских символов.
Единственное, что я нашел, что работает последовательно, это:
[DllImport(DllName, CharSet = CharSet.Ansi, EntryPoint = "foo", BestFitMapping = false, ThrowOnUnmappableChar = true)]
public static extern int Foo(string s1, string s2, EnumType e1, params IntPtr[] s3)
Чтобы сделать это, я использую Marshal.Stri
Решение
Вероятно, вам также нужно указать CallingConvention = CallingConvention.Cdecl
, поскольку функция varargs будет использовать соглашение о вызовах _cdecl
(по умолчанию используется Winapi, в свою очередь по умолчанию используется StdCall) . р>
Вам может понадобиться что-то еще, но я уверен, что вам понадобится хотя бы это.