#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**);