** ОСНОВНОЕ ОБНОВЛЕНИЕ **
Я допустил небольшую ошибку, но мне все еще интересно, что именно происходит.
Функция, которую я вызываю, на самом деле "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 string[]
странным, но предыдущие сигнатуры, которые вызывали эту функцию, использовали его для других типов, таких как bool
, поэтому я следовал шаблону.
Я обертываю это более дружественным методом .NET:
public void Foo(string s1, string s2, string s3)
{
int error = Dll.Foo(s1, s2, EnumType.Foo, s3);
// handle errors
}
Недавно я изменил подпись, добавив в атрибут DLLImport «BestFitMapping = false, ThrowOnUnmappableChar = true», чтобы соответствовать рекомендациям FxCop. Это красная сельдь, как я опишу позже.
Чего я ожидаю от этого изменения, так это ограниченной поддержки Юникода. Например, на машине с английской кодовой страницей будет выдано исключение, если вы передадите строку, содержащую символ японского языка. На японском компьютере я ожидал, что смогу передать японскую строку в функцию. Тест по английскому языку сработал, как и ожидалось, но японский тест выдает исключение 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.StringToHGlobalAnsi () и передаю этот указатель. Это работает для английского и японского языков. Мне неудобно не понимать, почему это решение, но оно работает.