USB HID связь с P / Invoke (Win32 API) и параллельными потоками - PullRequest
1 голос
/ 26 января 2012

Моя цель - создать приложение, способное взаимодействовать с USB HID-устройствами.Основной поток (UI) создает соответствующий дескриптор.

 this.handle =
        Win32.CreateFile(
            devicePath,
            Win32.GENERIC_READ | Win32.GENERIC_WRITE,
            0,
            IntPtr.Zero,
            Win32.OPEN_EXISTING,
            Win32.FILE_FLAG_OVERLAPPED,
            IntPtr.Zero);

После этого запускается два потока.Первое - это непрерывное чтение данных с устройства.Если доступных данных нет, они блокируются.Он использует перекрывающееся чтение.Дескриптор может быть передан объекту FileStream, но я должен использовать вызовы P / Invoke.Моя реализация основана на нескольких библиотеках HID.Таким образом, поток выполняет следующий цикл:

while (true)
{
    var overlapped = new Win32.Overlapped();
    overlapped.Offset = 0;
    overlapped.OffsetHigh = 0;
    overlapped.Event = this.readEvent;

    var readResult = 
        Win32.ReadFile(
            this.handle,
            buffer, 
            (uint)this.InputReportLength, 
            IntPtr.Zero, 
            ref overlapped);

    if (readResult == 0)
    {
        int errorCode = Marshal.GetLastWin32Error();

        // Overlapped operation is running => 0x3e5
        if (errorCode != 0x3e5)
        {
            break;
        }
    }

    var result = Win32.WaitForSingleObject(overlapped.Event, Win32.WAIT_INFINITE);

    if (result != Win32.WAIT_OBJECT_0 || this.handle == IntPtr.Zero)
    {
        // Handle is cleared
        break;
    }

    uint bytesRead = 0;
    Win32.GetOverlappedResult(this.handle, ref overlapped, out bytesRead, false);

    if (bytesRead > 0)
    {
        byte[] report = new byte[this.InputReportLength];
        Array.Copy(buffer, report, Math.Min(bytesRead, report.Length));

        // Report data
        OnDataReceived(report);
    }
}

Второй поток получает команды из параллельной очереди, собирает их в двоичный файл, а затем вызывает следующий метод, который записывает данные на устройство.

 IntPtr writeEvent = Win32.CreateEvent(IntPtr.Zero, false, true, Guid.NewGuid().ToString());

 var overlapped = new Win32.Overlapped();
 overlapped.Offset = 0;
 overlapped.OffsetHigh = 0;
 overlapped.Event = writeEvent;

 int writeResult = 
    Win32.WriteFile(
       this.handle, buffer, 
       (uint)buffer.Length,
       IntPtr.Zero, 
       ref overlapped);

 if (writeResult == 0)
 {
     int errorCode = Marshal.GetLastWin32Error();

     // Overlapped operation is running => 0x3e5
     if (errorCode != 0x3e5)
     {
         throw new IOException(string.Format("Cannot write device ({0})", errorCode));
     }
 }

 var result = Win32.WaitForSingleObject(writeEvent, Win32.WAIT_INFINITE);

 Win32.CloseHandle(writeEvent);

 if (result != Win32.WAIT_OBJECT_0)
 {
     throw new IOException("Failed to write");
 }

Приложение получает данные, правильно отправленные устройством.Программа просто работает без сбоев. НО если приложение отправляет данные на устройство (помещая объекты команд в указанную очередь), все приложения аварийно завершают работу самопроизвольно .Авария происходит неопределенным образом.В чем причина этого?Моя идея заключается в том, что это вызвано одновременным доступом.Однако одновременное использование объекта FileStream (путем передачи ему дескриптора) не приводит к такому сбою.

1 Ответ

2 голосов
/ 27 января 2012

Я наконец понял, в чем проблема.Между вызовом P / Invoke и завершением операции ввода-вывода CLR может реорганизовать структуру памяти.Двоичный буфер или структура Overlapped могут быть перемещены в новую область памяти.После завершения ввода-вывода данные результата записываются в исходную область памяти, что приводит к сбою приложения.

Решение состоит в том, чтобы закрепить области памяти во время ожидания операции ввода-вывода.Массив байтов можно закрепить с помощью ключевого слова fixed.Структура Overlapped не может быть закреплена таким образом, вместо этого можно использовать метод GCHandle.Alloc.

Следующий код демонстрирует модифицированную операцию записи:

public unsafe void Send(byte[] data)
{
    byte[] buffer = new byte[this.OutputReportLength];
    Array.Copy(data, 1, buffer, 1, Math.Min(buffer.Length, data.Length) - 1);

    fixed (byte* bufferPointer = buffer)
    {
        IntPtr writeEvent = Win32.CreateEvent(IntPtr.Zero, false, true, Guid.NewGuid().ToString());

        var overlapped = new Win32.Overlapped();
        overlapped.Offset = 0;
        overlapped.OffsetHigh = 0;
        overlapped.Event = writeEvent;

        GCHandle pinnedOverlapped = GCHandle.Alloc(overlapped, GCHandleType.Pinned);

        try
        {
            int writeResult =
                Win32.WriteFile(
                    this.handle,
                    bufferPointer,
                    (uint)this.OutputReportLength,
                    IntPtr.Zero,
                    &overlapped);

            if (writeResult == 0)
            {
                int errorCode = Marshal.GetLastWin32Error();

                // Overlapped operation is running => 0x3e5 
                if (errorCode != 0x3e5)
                {
                    throw new IOException(string.Format("Cannot write device ({0})", errorCode));
                }
            }

            var result = Win32.WaitForSingleObject(writeEvent, Win32.WAIT_INFINITE);

            if (result != Win32.WAIT_OBJECT_0)
            {
                throw new IOException("Failed to write");
            }
        }
        finally
        {
            Win32.CloseHandle(writeEvent);
            pinnedOverlapped.Free();
        }
    }

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...