Требуется справка по сортировке структуры Pinvoke — System.AccessViolationException

#c# #pinvoke #marshalling

#c# #pinvoke #сортировка

Вопрос:

Привет! Я только начал работать с pinvoke и столкнулся с проблемой. Я получаю AccessViolationException. Прежде всего, есть ли какой-нибудь способ отладить или отследить, какое поле вызывает эту ошибку? Единственное, что записывается, — это результирующая структура.

Вызов c выглядит следующим образом:

 MyFunc(int var1, _tuchar *var2, _tuchar *var3, _tuchar *var4, MyStruct *Result,
       _tuchar *var5, _tuchar *var6);
  

Структура c :

 typedef struct MyStruct 
{
   _tuchar *id;
   _tuchar *ErrorMessages;
   int int1; 
   _tuchar language[3]; 
   _tuchar *resu<
   int type;
   int number;
   int *type2; 
   _tuchar **blocks;
}
  

Структура C #:

 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct MyStruct
{
    [MarshalAs(UnmanagedType.LPStr)]
    public string Id;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst=500)]
    public char[] ErrorMessages;

    public int int1;

    [MarshalAs(UnmanagedType.LPStr)]
    public string language;

    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)]
    public char[] resu<

    public int type;

    public int number;

    public int type2;

    [MarshalAs(UnmanagedType.ByValArray)]
    public string[] blocks;
  

Объявление метода C #:

 [DllImport(MyPath, EntryPoint = "MyEntryPoint", SetLastError = true,
           CharSet = CharSet.Unicode)]
internal static extern int MyFunc(int var1, string var2, string var3,
      string var4, ref MyStruct Result, string var5, string var6);
  

Вызов C #:

 var result = new MyStruct();
MyFunc(0, "var2", "var3", "var4", ref result, "var5", "var6");
  

Надеюсь, я ничего не упустил.
Спасибо за любую помощь!

Комментарии:

1. Как выглядит MyFunc объявление в C #?

2. ах, я кое-что забыл, спасибо, приятель ^^

3. Гарантированно ли размер int в C должен быть 32-битным / конвертируемым с System.Int32? System.Int32 имеет очень точную гарантию — даже в отношении разрядности.

4. Нужны ли var* параметры для ввода или для вывода?

5. @Anton Semenov переменные * являются входными данными

Ответ №1:

Оооо, чувак! Вы выбрали довольно сложный случай для своего первого опыта работы. Я рекомендую сначала сделать что-нибудь попроще, а затем переходить к реальным вещам.

Во-первых, CharSet=CharSet.Ansi выглядит подозрительно. Все ваши строки и символы являются _tuchar , и я так понимаю, что u там означает «Unicode», не так ли? Если это так, вам нужно CharSet=CharSet.Unicode .

Во-вторых, (и это наиболее вероятный виновник) почему ErrorMessages поле маршалируется как ByValArray ? Вы знаете, что ByVal здесь означает «по значению», не так ли? Как в, не по ссылке. И вы знаете, что маленькая звездочка в C означает «ссылка», не так ли? Итак, почему ваше ссылочное поле ErrorMessages маршалируется как массив по значению? На случай, если вы не знаете, обычно говорят, что массив передается «по значению», когда передается все его содержимое, вместо того, чтобы просто передавать ссылку (указатель) на ячейку памяти, где хранится все это содержимое. В определении структуры C вы указываете _tuchar* , что означает «ссылка (указатель) на некоторую память, содержащую один или более _tuchars», тогда как в C # вы указываете [MarshalAs(UnmanagedType.ByValArray, SizeConst=500)] , что означает «здесь должно быть 500 _tuchars, не больше и не меньше». Видя, что ссылка (указатель) обычно занимает 4 байта (или 8 байт на 64-разрядных машинах), а 500 символов Юникода занимают 1000 байт, вы видите очевидное несоответствие прямо здесь.

В-третьих и в-четвертых, то же самое относится к result и blocks полям.

В-пятых, language поле представляет собой в точности обратную ситуацию: код C говорит «здесь 3 _tuchars», в то время как код C # говорит «здесь есть ссылка (указатель) на строку» (на случай, если вы не знаете, LPStr означает «Длинный указатель на строку»).

И, наконец, после устранения всех этих проблем я рекомендую вам выполнить вашу программу и распечатать результат вызова Marshal.SizeOf( typeof( MyStruct ) ) . Это точно покажет вам, насколько велика ваша структура, в .Мнение СЕТИ. Перейдите на сторону C и распечатайте sizeof( MyStruct ) . Это даст вам представление о том, что C думает о размере.

Если они окажутся разными, посмотрите, что не так. Попробуйте удалять поля одно за другим, пока они не станут одинаковыми. Это выдаст вам поля виновника. Работайте с ними.

В целом, я полагаю, вам сначала нужно лучше понять, как все работает. Этот случай, пожалуй, слишком сложный для новичка.

Удачи!

Комментарии:

1. Что ж, сэр, вы тот самый мужчина : ) Я очень благодарен вам за ваш вклад. посмотрим, смогу ли я решить эту проблему. Единственное, чего я действительно не могу понять, — это эти строки, в которые c будет записывать… Я подумал, что если я отправлю строку с 500 символами, то c dll не будет записывать данные за пределы boundary. Существует ли ограничение на то, насколько большим может быть результат?

2. Может ли быть так, что наличие доступных для записи строковых полей в структуре невозможно? Или вы знаете, как они должны быть определены (сообщения об ошибках и результат)?

Ответ №2:

Это своего рода попытка блуждать в темноте, но вы пробовали украшать строковые параметры с помощью MarshalAs(UnmanagedType.LPWStr) :

 [DllImport(MyPath, EntryPoint = "MyEntryPoint", SetLastError = true,
    CharSet = CharSet.Unicode)]
internal static extern int MyFunc(
    int var1,
    [MarshalAs(UnmanagedType.LPWStr)]
    string var2, 
    [MarshalAs(UnmanagedType.LPWStr)]
    string var3,
    [MarshalAs(UnmanagedType.LPWStr)]
    string var4,
    ref MyStruct Result,
    [MarshalAs(UnmanagedType.LPWStr)]
    string var5,
    [MarshalAs(UnmanagedType.LPWStr)]
    string var6);
  

Я считаю, что маршалинг по умолчанию, выбранный для строк, является BStr и _tuchar должен расширяться до wchar_t , поэтому я предполагаю, что LPWStr это правильный метод маршалинга (указатель на строку с широким набором символов).


Обновление: Различные вещи на MyStruct выглядят не совсем правильно:

ErrorMessages помечается как ByValArray , и так далее .Сетевое взаимодействие, вероятно, ожидает MyStruct , что оно будет выглядеть примерно так:

 typedef struct MyStruct 
{
   _tuchar *id;
   _tuchar ErrorMessages[500];
   // Rest of MyStruct
  

Что, вероятно, вызовет проблемы — то же самое для result .

Также я думаю, что это language следует использовать ByValArray с размером 3.

Наконец, blocks вероятно, должно быть передано с использованием LPArray ByValArray не кажется правильным.

(Кстати, это все в основном догадки — я надеюсь, что это указывает вам правильное направление, но у меня нет такого большого опыта взаимодействия с P / Invoke)


Еще одно обновление: При MyStruct объявлении кодировки как Ansi, но на MyFunc своем Unicode языке… неуправляемая dll скомпилирована с использованием Unicode или Ansi? Если он использует Unicode, то я считаю, что вы должны использовать его LPWStr при сортировке строк, и с Ansi это должно быть LPStr .

Комментарии:

1. хм, я попробую оформить строки, но эти строки предназначены только для чтения, а не для записи. я полагаю, что проблема заключается в результате, я прочитал, что для записи строки следует использовать stringbuilder, но вы не можете использовать stringbuilder в структуре без получения исключения сортировки. Не знаю об uni или ansi, есть ли какой-нибудь способ выяснить это?

2. @Dashu Некоторые атрибуты в MyStruct также кажутся не совсем правильными — я обновил свой вопрос, но имейте в виду, что я, вероятно, не знаю, о чем говорю! :-p

3. @Dashu только что заметил, что это MyStruct передается как ref параметр, и поэтому я думаю, что это ErrorMessages , вероятно, является источником вашей проблемы — насколько я понимаю . Уровень сетевой сортировки ожидает, что размер MyStruct будет не менее 500, хотя на самом деле он намного меньше этого.

4. Не удается выполнить маршалинг поля ‘language’ типа ‘MyStruct’: недопустимая комбинация управляемого / неуправляемого типа (строковые поля должны быть сопряжены с LPStr, LPWStr, LPTStr, BStr или ByValTStr). Среда выполнения, кажется, говорит ^^

5. Да, я не совсем понимаю, как выделить память для строки без использования stringbuilder…