DllImport -> как обрабатывать дескриптор в C#

#c# #c #dllimport #handle #intptr

#c# #c #dllimport #дескриптор #intptr

Вопрос:

в моем коде C # я хочу импортировать C DLL. Я использую dllimport, и он отлично работает с некоторыми функциями. Но в одной функции я получаю ДЕСКРИПТОР, который мне понадобится позже для вызова другой функции.

    [DllImport("SiUSBXp.dll")]
   public static extern int SI_Open(UInt32 deviceNum,ref IntPtr devHandle );   // this function gets the HANDLE
   [DllImport("SiUSBXp.dll")]
   public static extern int SI_Write([In]IntPtr devHandle, [In, Out] byte[] inputByte, UInt32 size,ref UInt32 bytesWritten); // this function needs the HANDLE
  

В моем коде эти функции вызываются следующим образом:

    IntPtr devHandle = new IntPtr();
   UInt32 bytesWritten = new UInt32();
   byte[] byteArr = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
   SI_Open(0, ref devHandle);
   SI_Write(devHandle, byteArr, 10, ref bytesWritten);
  

Если я сделаю это так, я получу «System.Исключение AccessViolationException». Я искал здесь и в Интернете, но не нашел конкретного ответа. Как мне правильно использовать IntPtr, чтобы он работал?
С наилучшими пожеланиями

Тоби

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

1. Как выглядят прототипы функций C для SI_Open и SI_Write?

2. Вы инициализируете массив байтов до 10 элементов, но достаточно ли этого для функции SI_Write?

3. SI_STATUS WINAPI SI_Open(DWORD dwDevice, ДЕСКРИПТОР* cyHandle );

4. SI_STATUS WINAPI SI_Write(ОБРАБАТЫВАЕТ cyHandle, LPVOID lpBuffer, DWORD dwBytesToWrite, LPDWORD lpdwBytesWritten );

5. К массиву байтов => на самом деле он работал с «тем же» массивом в коде C . Также я кое-что попробовал -> Я инициализировал IntPtr, подобный этому «IntPtr Test = new IntPtr()» — Когда я вызываю функцию записи с этим IntPtr, она не выдает exception…so Я действительно думаю, что ошибка связана с IntPtr

Ответ №1:

Ваша функция SI_Write выглядит очень похоже на файл записи в Windows Kernel32.

Итак, я бы сделал это:

 [DllImport("SiUSBXp.dll", SetLastError = true)]
static extern int SI_Open(uint dwDevice, ref IntPtr cyHandle);  

[DllImport("SiUSBXp.dll", SetLastError = true)]
static extern int SI_Write(IntPtr cyHandle, byte[] lpBuffer,
   uint dwBytesToWrite, out uint lpdwBytesWritten);
  

РЕДАКТИРОВАТЬ: я нашел эту документацию USBXPRESS® PROGRAMMER’S GUIDE в Интернете, и в ней говорится, что прототип SI_Write на самом деле выглядит намного ближе к WriteFile, чем я думал. В документе указано следующее:

 SI_STATUS SI_Write (HANDLE Handle, LPVOID Buffer, DWORD NumBytesToWrite,
DWORD *NumBytesWritten, OVERLAPPED* o = NULL)
  

Это означает, что прототип .NET должен быть таким вместо:

 [DllImport("SiUSBXp.dll")]
static extern int SI_Write(IntPtr Handle, byte[] Buffer,
   uint NumBytesToWrite, out uint NumBytesWritten, IntPtr o);
  

o является необязательным, поэтому вы можете передать IntPtr.Zero.

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

1. @Тоби — странно. Возможно, реализация C делает что-то необычное. Когда вы говорите, что это не работает, это все еще ошибка AccessViolationException?

2. Вот именно…. Я не знаю, в чем проблема. Я могу обойти проблему, если я а) объявлю буфер в SI_Write как out (но, как уже упоминалось в другом ответе, это на самом деле неправильно) — и если я это сделаю, я получу странные значения, поступающие на устройство. и б) Если я объявляю количество байтов для записи равным нулю, но, эй, это тоже не решение ^^ — Но я думаю, что проблема заключается в массиве байтов, поскольку я могу использовать IntPtr для вызова функции SI_Close (не упомянутой здесь ) без проблем!

3. Ладно, что ж, у меня есть одна хорошая и одна плохая новость — хорошая в том, что ошибка не возникает — НА ДАННЫЙ МОМЕНТ ^^ — плохая в том, что теперь функция SI_Write возвращает 12 (0x0C ), который определен в заголовке как: SI_SYSTEM_ERROR_CODE 0x0c — сейчас проведу несколько проб и ошибок, а потом сообщу.

4. @Toby — Из документа SI_SYSTEM_ERROR_CODE означает, что теперь вы можете использовать Marshal. Получите Last Win32error и получите «реальную» ошибку Windows, лежащую в основе. Обратите внимание, что я добавил SetLastError к прототипам C # в своем ответе, поэтому маршалируйте. GetLastWin32Error будет работать.

5. Боже мой, это работает!!! На самом деле я внес небольшое изменение — я удалил ключевое слово «out» из последнего IntPtr. —> статический внешний int SI_Write (дескриптор IntPtr, буфер byte[], uint NumBytesToWrite, out uint NumBytesWritten,IntPtr o); Теперь данные правильно поступают на устройство, и функция закрывается должным образом! Итак, я бы на самом деле сказал, что это все …? Или было неправильно удалять ключевое слово out ??! В любом случае, я уже хочу сказать вам -> большое, очень большое спасибо за то, что уделили мне время, помогая!!!

Ответ №2:

Вы совершаете классическую ошибку программиста C, вы не проверяете возвращаемое значение функций. Который сообщает вам, произошел сбой функции или нет. Вероятный сценарий заключается в том, что SI_Open() вернул код сбоя. Вы игнорируете это и все равно используете неинициализированное значение дескриптора. Бабах не является чем-то необычным.

Следующая возможная ошибка заключается в том, что вы не используете свойство CallingConvention в инструкции [DllImport]. Скорее всего, это потребуется, Cdecl используется по умолчанию, если только собственная функция не объявлена с помощью __stdcall . Также отличный способ вызвать kaboom. Если у вас все еще возникают проблемы, вам придется отладить машинный код.

Кстати, вы избавляетесь от неудобного синтаксиса, используя out вместо ref . В обеих функциях.

    [DllImport("SiUSBXp.dll", CallingConvention = CallingConvention.Cdecl)]
   public static extern int SI_Open(UInt32 deviceNum, out IntPtr devHandle );
  

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

1. Я действительно проверил возвращаемые значения (и SI_Open успешно выполняется), но я просто стер это, чтобы у вас было более четкое представление о критических точках исходного кода.

2. Не стесняйтесь посмотреть это в библиотеке MSDN. Сообщение обновлено.

3. Но разве это не должно быть «CallingConvention. Stdcall» вместо Cdecl? Потому что в MSDN сказано: «Вызываемый объект очищает стек. Это соглашение по умолчанию для вызова неуправляемых функций с помощью platform invoke.» … ?

4. Да, это значение по умолчанию. Который работает только до тех пор, пока ответственные программисты C / C используют ключевое слово __stdcall в объявлении. Что не так часто встречается. Я не могу видеть этот код отсюда, вы намного ближе.

Ответ №3:

попробуйте это:

    [DllImport("SiUSBXp.dll")]
   public static extern int SI_Open(UInt32 deviceNum, ref IntPtr devHandle);   // this function gets the HANDLE
   [DllImport("SiUSBXp.dll")]
   public static extern int SI_Write(IntPtr devHandle, ref byte[] inputByte, UInt32 size, ref UInt32 bytesWritten); // this function needs the HANDLE
  

Редактировать:

@Ханс Пассант прав. Это правильный способ передать байт[] в параметр LPVOID. ссылка используется для принудительного преобразования объекта в LPVOID, но не нужна для массива. Что происходит, когда вы пытаетесь это сделать?

    [DllImport("SiUSBXp.dll")]
   public static extern int SI_Write(IntPtr devHandle, byte[] inputByte, UInt32 size, ref UInt32 bytesWritten); // this function needs the HANDLE
  

Вы пробовали ответить, который дал @Simon Mourier? Он был первым, кто предоставил это объявление, и его ответ заслуживает того, чтобы быть принятым.

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

1. Но байты, которые я пытаюсь записать, неправильно записаны в uC : (

2. какое значение байтов записывается после вызова?

3. Этот ответ не может быть правильным, массив уже передан как указатель (LPVOID). Объявление его ref делает его указателем на указатель (LPVOID*).

4. К вашей правке —> В этом случае я получаю «обычное» исключение AccessViolation…. итак, по крайней мере, мы указали, что ошибки находятся внутри массива байтов, а не в IntPtr!

Ответ №4:

плохо: static extern void DoStuff(**byte[] inputByte**);

хорошо: static extern void DoStuff(**[In, MarshalAs(UnmanagedType.LPArray)] byte[] inputByte**);