简体   繁体   English

在Mac上读写USB(HID)中断端点

[英]Reading and writing to USB (HID) interrupt endpoints on Mac

I am attempting to communicate with a rather specific USB device and developing both Windows and Mac code to do so. 我试图与相当特定的USB设备通信,并同时开发Windows和Mac代码。

The device is a USB device with a HID interface (class 3) with two endpoints, an interrupt input and an interrupt output. 该设备是具有HID接口(3类)的USB设备,该设备具有两个端点,一个中断输入和一个中断输出。 The nature of the device is such that data is sent out from the device on the input endpoint only when data is requested from the host: the host sends it data which the device responds to on its input interrupt endpoint. 设备的性质是,仅当从主机请求数据时,才在输入端点上从设备发送数据:主机向其发送数据,设备在其输入中断端点上做出响应。 Getting data to the device (a write) is much more simple... 将数据获取到设备(写入)要简单得多...

The code for Windows is rather straight-forward: I get a handle to the device and then call either ReadFile or WriteFile. Windows的代码非常简单:我得到了设备的句柄,然后调用ReadFile或WriteFile。 Apparently much of the underlying asynchronous behavior is abstracted out. 显然,许多底层的异步行为被抽象出来了。 It appears to work fine. 它似乎工作正常。

On Mac, however, it is a bit stickier. 但是,在Mac上,它有点粘性。 I have tried a number of things, none which have been fully successful, but here are the two things which seemed most promising... 我尝试了很多事情,没有一件是完全成功的,但是下面两件事似乎最有希望...

1.) Attempt to get access to the device (as USB) via IOUSBInterfaceInterface, iterate through the endpoints to determine the input and output endpoints, and (hopefully) use ReadPipe and WritePipe to communicate. 1.)尝试通过IOUSBInterfaceInterface访问设备(作为USB),遍历端点以确定输入和输出端点,并(希望)使用ReadPipe和WritePipe进行通信。 Unfortunately I am unable to open the interface once I have it, with the return value (kIOReturnExclusiveAccess) noting that something already has the device open exclusively. 不幸的是,一旦获得接口,我将无法打开该接口,返回值(kIOReturnExclusiveAccess)指出某些东西已经专门打开了该设备。 I have tried using IOUSBinterfaceInterface183, so that I could call USBInterfaceOpenSeize, but that results in the same return error value. 我尝试使用IOUSBinterfaceInterface183,以便可以调用USBInterfaceOpenSeize,但这会导致相同的返回错误值。

--- update 7/30/2010 --- -更新7/30/2010-
Apparently, the Apple IOUSBHIDDriver matches early to the device and this is what likely is preventing opening the IOUSBInterfaceInterface. 显然,Apple IOUSBHIDDriver与设备较早匹配,这很可能导致无法打开IOUSBInterfaceInterface。 From some digging about it seems that the common way to prevent the IOUSBHIDDriver from matching is to write a code-less kext (kernel extension) with a higher probe score. 从某种程度上讲,防止IOUSBHIDDriver匹配的常见方法似乎是编写具有较高探针得分的无代码kext(内核扩展)。 This would match early, preventing the IOUSBHIDDriver from opening the device, and should, in theory, permit me to open the interface and to write and read to endpoints directly. 这将尽早匹配,从而防止IOUSBHIDDriver打开设备,并且从理论上讲,应该允许我打开接口并直接对端点进行读写。 This is OK, but I would much prefer not having to install something additional on the user machine. 可以,但是我更希望不必在用户计算机上安装其他东西。 If anyone knows of a solid alternative I would be thankful for the information. 如果有人知道一个可靠的选择,我将感谢您提供的信息。

2.) Open the device as an IOHIDDeviceInterface122 (or later). 2.)作为IOHIDDeviceInterface122(或更高版本)打开设备。 To read, I set up an async port, event source and callback method to be called when data is ready - when data is sent from the device on the input interrupt endpoint. 要进行读取,我设置了一个异步端口,事件源和回调方法,以便在数据准备就绪时-在输入中断端点上从设备发送数据时调用该方法。 However, to write the data — that the device needs — to initialize a response I can't find a way. 但是,写入设备所需的数据来初始化响应,我找不到方法。 I'm stumped. 我很沮丧 setReport typically writes to the control endpoint, plus I need a write that does not expect any direct response, no blocking. setReport通常会写到控制端点,再加上我需要的写操作不会产生任何直接响应,也不会阻塞。

I have looked around online and have tried many things, but none of them is giving me success. 我在网上四处张望,尝试了许多事情,但没有一个给我成功。 Any advice? 有什么建议吗? I can not use much of the Apple HIDManager code since much of that is 10.5+ and my application must work on 10.4 as well. 我不能使用太多的Apple HIDManager代码,因为其中很多是10.5+,我的应用程序也必须在10.4上运行。

I have now a working Mac driver to a USB device that requires communication through interrupt endpoints. 我现在有一个USB设备的工作Mac驱动程序,该设备需要通过中断端点进行通信。 Here is how I did it: 这是我的做法:

Ultimately the method that worked well for me was option 1 (noted above). 最终,最适合我的方法是选项1(上面已指出)。 As noted, I was having issues opening the COM-style IOUSBInterfaceInterface to the device. 如前所述,我在打开COM样式的IOUSBInterfaceInterface到设备时遇到问题。 It became clear over time that this was due to the HIDManager capturing the device. 随着时间的流逝,很明显这是由于HIDManager捕获了设备。 I was unable to wrest control of the device from the HIDManager once it was captured (not even the USBInterfaceOpenSeize call or the USBDeviceOpenSeize calls would work). 捕获设备后,我无法从HIDManager夺取该设备的控制权(甚至USBInterfaceOpenSeize调用或USBDeviceOpenSeize调用都不起作用)。

To take control of the device I needed to grab it before the HIDManager. 要控制该设备,我需要在HIDManager之前先进行抓取。 The solution to this was to write a codeless kext (kernel extension). 解决方案是编写一个无代码的kext(内核扩展)。 A kext is essentially a bundle that sits in System/Library/Extensions that contains (usually) a plist (property list) and (occasionally) a kernel-level driver, among other items. kext本质上是一个位于系统/库/扩展中的捆绑软件,其中除其他外,其中包含(通常)plist(属性列表)和(偶尔)内核级驱动程序。 In my case I wanted only the plist, which would give the instructions to the kernel on what devices it matches. 在我的情况下,我只需要plist,它将向内核提供有关其匹配的设备的指令。 If the data gives a higher probe score than the HIDManager then I could essentially capture the device and use a user-space driver to communicate with it. 如果数据提供的探针得分比HIDManager高,那么我基本上可以捕获该设备并使用用户空间驱动程序与其进行通信。

The kext plist written, with some project-specific details modified, is as follows: 编写的kext plist修改了一些项目特定的详细信息,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>OSBundleLibraries</key>
    <dict>
        <key>com.apple.iokit.IOUSBFamily</key>
        <string>1.8</string>
        <key>com.apple.kernel.libkern</key>
        <string>6.0</string>
    </dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>English</string>
    <key>CFBundleGetInfoString</key>
    <string>Demi USB Device</string>
    <key>CFBundleIdentifier</key>
    <string>com.demiart.mydevice</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>Demi USB Device</string>
    <key>CFBundlePackageType</key>
    <string>KEXT</string>
    <key>CFBundleSignature</key>
    <string>????</string>
    <key>CFBundleVersion</key>
    <string>1.0.0</string>
    <key>IOKitPersonalities</key>
    <dict>
        <key>Device Driver</key>
        <dict>
            <key>CFBundleIdentifier</key>
            <string>com.apple.kernel.iokit</string>
            <key>IOClass</key>
            <string>IOService</string>
            <key>IOProviderClass</key>
            <string>IOUSBInterface</string>
            <key>idProduct</key>
            <integer>12345</integer>
            <key>idVendor</key>
            <integer>67890</integer>
            <key>bConfigurationValue</key>
            <integer>1</integer>
            <key>bInterfaceNumber</key>
            <integer>0</integer>
        </dict>
    </dict>
    <key>OSBundleRequired</key>
    <string>Local-Root</string>
</dict>
</plist>

The idVendor and idProduct values give the kext specificity and increase its probe score sufficiently. idVendor和idProduct值赋予了kext特异性,并充分提高了其探针得分。

In order to use the kext, the following things need to be done (which my installer will do for clients): 为了使用kext,需要完成以下操作(我的安装程序将对客户端执行此操作):

  1. Change the owner to root:wheel ( sudo chown root:wheel DemiUSBDevice.kext ) 将所有者更改为root:wheel( sudo chown root:wheel DemiUSBDevice.kext
  2. Copy the kext to Extensions ( sudo cp DemiUSBDevice.kext /System/Library/Extensions ) 将kext复制到Extensions( sudo cp DemiUSBDevice.kext /System/Library/Extensions
  3. Call the kextload utility to load the kext for immediate use without restart ( sudo kextload -vt /System/Library/Extensions/DemiUSBDevice.kext ) 调用kextload实用程序以加载kext以立即使用,而无需重新启动( sudo kextload -vt /System/Library/Extensions/DemiUSBDevice.kext
  4. Touch the Extensions folder so that the next restart will force a cache rebuild ( sudo touch /System/Library/Extensions ) 触摸Extensions文件夹,以便下次重新启动时将强制重建缓存( sudo touch /System/Library/Extensions

At this point the system should use the kext to keep the HIDManager from capturing my device. 此时,系统应使用kext阻止HIDManager捕获我的设备。 Now, what to do with it? 现在,该怎么办? How to write to and read from it? 如何对其进行读写?

Following are some simplified snippets of my code, minus any error handling, that illustrate the solution. 以下是我的代码的一些简化片段,减去任何错误处理,这些片段说明了解决方案。 Before being able to do anything with the device, the application needs to know when the device attaches (and detaches). 在使用设备执行任何操作之前,应用程序需要知道设备何时连接(和分离)。 Note that this is merely for purposes of illustration — some of the variables are class-level, some are global, etc. Here is the initialization code that sets the attach/detach events up: 请注意,这仅仅是出于说明的目的-有些变量是类级别的,有些是全局的,等等。这是用于设置attach / detach事件的初始化代码:

#include <IOKit/IOKitLib.h>
#include <IOKit/IOCFPlugIn.h>
#include <IOKit/usb/IOUSBLib.h>
#include <mach/mach.h>

#define DEMI_VENDOR_ID 12345
#define DEMI_PRODUCT_ID 67890

void DemiUSBDriver::initialize(void)
{
    IOReturn                result;
    Int32                   vendor_id = DEMI_VENDOR_ID;
    Int32                   product_id = DEMI_PRODUCT_ID;
    mach_port_t             master_port;
    CFMutableDictionaryRef  matching_dict;
    IONotificationPortRef   notify_port;
    CFRunLoopSourceRef      run_loop_source;

    //create a master port
    result = IOMasterPort(bootstrap_port, &master_port);

    //set up a matching dictionary for the device
    matching_dict = IOServiceMatching(kIOUSBDeviceClassName);

    //add matching parameters
    CFDictionarySetValue(matching_dict, CFSTR(kUSBVendorID),
        CFNumberCreate(kCFAllocatorDefault, kCFNumberInt32Type, &vendor_id));
    CFDictionarySetValue(matching_dict, CFSTR(kUSBProductID),
        CFNumberCreate(kCFAllocatorDefault, kCFNumberInt32Type, &product_id));

    //create the notification port and event source
    notify_port = IONotificationPortCreate(master_port);
    run_loop_source = IONotificationPortGetRunLoopSource(notify_port);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source, 
      kCFRunLoopDefaultMode);

    //add an additional reference for a secondary event 
    //  - each consumes a reference...
    matching_dict = (CFMutableDictionaryRef)CFRetain(matching_dict);

    //add a notification callback for detach event
    //NOTE: removed_iter is a io_iterator_t, declared elsewhere
    result = IOServiceAddMatchingNotification(notify_port, 
      kIOTerminatedNotification, matching_dict, device_detach_callback, 
      NULL, &removed_iter);

    //call the callback to 'arm' the notification
    device_detach_callback(NULL, removed_iter);

    //add a notification callback for attach event
    //NOTE: added_iter is a io_iterator_t, declared elsewhere
    result = IOServiceAddMatchingNotification(notify_port, 
      kIOFirstMatchNotification, matching_dict, device_attach_callback, 
      NULL, &g_added_iter);
    if (result)
    {
      throw Exception("Unable to add attach notification callback.");
    }

    //call the callback to 'arm' the notification
    device_attach_callback(NULL, added_iter);

    //'pump' the run loop to handle any previously added devices
    service();
}

There are two methods that are used as callbacks in this initialization code: device_detach_callback and device_attach_callback (both declared at static methods). 在此初始化代码中,有两种方法用作回调:device_detach_callback和device_attach_callback(均在静态方法中声明)。 device_detach_callback is straightforward: device_detach_callback很简单:

//implementation
void DemiUSBDevice::device_detach_callback(void* context, io_iterator_t iterator)
{
    IOReturn       result;
    io_service_t   obj;

    while ((obj = IOIteratorNext(iterator)))
    {
        //close all open resources associated with this service/device...

        //release the service
        result = IOObjectRelease(obj);
    }
}

device_attach_callback is where most of the magic happens. device_attach_callback是大多数魔术发生的地方。 In my code I have this broken into multiple methods, but here I'll present it as a big monolithic method...: 在我的代码中,我将其分解为多种方法,但是在这里,我将其呈现为一种大型的整体方法...:

void DemiUSBDevice::device_attach_callback(void * context, 
    io_iterator_t iterator)
{
    IOReturn                   result;
    io_service_t           usb_service;
    IOCFPlugInInterface**      plugin;   
    HRESULT                    hres;
    SInt32                     score;
    UInt16                     vendor; 
    UInt16                     product;
    IOUSBFindInterfaceRequest  request;
    io_iterator_t              intf_iterator;
    io_service_t               usb_interface;

    UInt8                      interface_endpoint_count = 0;
    UInt8                      pipe_ref = 0xff;

    UInt8                      direction;
    UInt8                      number;
    UInt8                      transfer_type;
    UInt16                     max_packet_size;
    UInt8                      interval;

    CFRunLoopSourceRef         m_event_source;
    CFRunLoopSourceRef         compl_event_source;

    IOUSBDeviceInterface245** dev = NULL;
    IOUSBInterfaceInterface245** intf = NULL;

    while ((usb_service = IOIteratorNext(iterator)))
    {
      //create the intermediate plugin
      result = IOCreatePlugInInterfaceForService(usb_service, 
        kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin, 
        &score);

      //get the device interface
      hres = (*plugin)->QueryInterface(plugin, 
        CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID245), (void**)&dev);

      //release the plugin - no further need for it
      IODestroyPlugInInterface(plugin);

      //double check ids for correctness
      result = (*dev)->GetDeviceVendor(dev, &vendor);
      result = (*dev)->GetDeviceProduct(dev, &product);
      if ((vendor != DEMI_VENDOR_ID) || (product != DEMI_PRODUCT_ID))
      {
        continue;
      }

      //set up interface find request
      request.bInterfaceClass     = kIOUSBFindInterfaceDontCare;
      request.bInterfaceSubClass  = kIOUSBFindInterfaceDontCare;
      request.bInterfaceProtocol  = kIOUSBFindInterfaceDontCare;
      request.bAlternateSetting   = kIOUSBFindInterfaceDontCare;

      result = (*dev)->CreateInterfaceIterator(dev, &request, &intf_iterator);

      while ((usb_interface = IOIteratorNext(intf_iterator)))
      {
        //create intermediate plugin
        result = IOCreatePlugInInterfaceForService(usb_interface, 
          kIOUSBInterfaceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin, 
          &score);

        //release the usb interface - not needed
        result = IOObjectRelease(usb_interface);

        //get the general interface interface
        hres = (*plugin)->QueryInterface(plugin, CFUUIDGetUUIDBytes(
          kIOUSBInterfaceInterfaceID245), (void**)&intf);

        //release the plugin interface
        IODestroyPlugInInterface(plugin);

        //attempt to open the interface
        result = (*intf)->USBInterfaceOpen(intf);

        //check that the interrupt endpoints are available on this interface
        //calling 0xff invalid...
        m_input_pipe = 0xff;  //UInt8, pipe from device to Mac
        m_output_pipe = 0xff; //UInt8, pipe from Mac to device

        result = (*intf)->GetNumEndpoints(intf, &interface_endpoint_count);
        if (!result)
        {
          //check endpoints for direction, type, etc.
          //note that pipe_ref == 0 is the control endpoint (we don't want it)
          for (pipe_ref = 1; pipe_ref <= interface_endpoint_count; pipe_ref++)
          {
            result = (*intf)->GetPipeProperties(intf, pipe_ref, &direction,
              &number, &transfer_type, &max_packet_size, &interval);
            if (result)
            {
              break;
            }

            if (transfer_type == kUSBInterrupt)
            {
              if (direction == kUSBIn)
              {
                m_input_pipe = pipe_ref;
              }
              else if (direction == kUSBOut)
              {
                m_output_pipe = pipe_ref;
              }
            }
          }
        }

        //set up async completion notifications
        result = (*m_intf)->CreateInterfaceAsyncEventSource(m_intf, 
          &compl_event_source);
        CFRunLoopAddSource(CFRunLoopGetCurrent(), compl_event_source, 
          kCFRunLoopDefaultMode);

        break;
      }

      break;
    }
}

At this point we should have the numbers of the interrupt endpoints and an open IOUSBInterfaceInterface to the device. 此时,我们应该具有中断端点的编号和设备的开放IOUSBInterfaceInterface。 An asynchronous writing of data can be done by calling something like: 可以通过调用以下内容来完成数据的异步写入:

result = (intf)->WritePipeAsync(intf, m_output_pipe, 
          data, OUTPUT_DATA_BUF_SZ, device_write_completion, 
          NULL);

where data is a char buffer of data to write, the final parameter is an optional context object to pass into the callback, and device_write_completion is a static method with the following general form: 其中data是要写入的数据的char缓冲区,final参数是要传递到回调中的可选上下文对象,device_write_completion是具有以下常规形式的静态方法:

void DemiUSBDevice::device_write_completion(void* context, 
    IOReturn result, void* arg0)
{
  //...
}

reading from the interrupt endpoint is similar: 从中断端点读取的内容类似:

result = (intf)->ReadPipeAsync(intf, m_input_pipe, 
          data, INPUT_DATA_BUF_SZ, device_read_completion, 
          NULL);

where device_read_completion is of the following form: 其中device_read_completion具有以下形式:

void DemiUSBDevice::device_read_completion(void* context, 
    IOReturn result, void* arg0)
{
  //...
}

Note that to receive these callbacks the run loop must be running ( see this link for more information about the CFRunLoop ). 请注意,要接收这些回调,必须运行run循环( 有关CFRunLoop的更多信息,请参见此链接 )。 One way to achieve this is to call CFRunLoopRun() after calling the async read or write methods at which point the main thread blocks while the run loop runs. 一种实现方法是在调用异步读取或写入方法后调用CFRunLoopRun() ,此时运行循环运行时主线程将阻塞。 After handling your callback you can call CFRunLoopStop(CFRunLoopGetCurrent()) to stop the run loop and hand execution back to the main thread. 处理完回调之后,您可以调用CFRunLoopStop(CFRunLoopGetCurrent())停止运行循环并将执行交还给主线程。

Another alternative (which I do in my code) is to pass a context object (named 'request' in the following code sample) into the WritePipeAsync/ReadPipeAsync methods - this object contains a boolean completion flag (named 'is_done' in this example). 另一种选择(我在我的代码中执行)是将上下文对象(在下面的代码示例中称为“ request”)传递到WritePipeAsync / ReadPipeAsync方法中-该对象包含一个布尔值完成标志(在本示例中为“ is_done”) 。 After calling the read/write method, instead of calling CFRunLoopRun() , something like the following can be executed: 调用read / write方法之后,可以执行以下操作,而不是调用CFRunLoopRun()

while (!(request->is_done))
{
  //run for 1/10 second to handle events
  Boolean returnAfterSourceHandled = false;
  CFTimeInterval seconds = 0.1;
  CFStringRef mode = kCFRunLoopDefaultMode;
  CFRunLoopRunInMode(mode, seconds, returnAfterSourceHandled);
}

This has the benefit that if you have other threads that use the run loop you won't prematurely exit should another thread stop the run loop... 这样做的好处是,如果您有其他使用运行循环的线程,则在另一个线程停止运行循环的情况下,您不会过早退出...

I hope that this is helpful to people. 我希望这对人们有帮助。 I had to pull from many incomplete sources to solve this problem and this required considerable work to get running well... 为了解决这个问题,我不得不从许多不完整的资源中获取资源,这需要大量的工作才能正常运行...

I ran into this same kIOReturnExclusiveAccess. 我遇到了同样的kIOReturnExclusiveAccess。 Instead of fighting it (building a kext, etc). 而不是与之抗争(建造结局等)。 I found the device and used the POSIX api's. 我找到了设备并使用了POSIX API。

//My funcation was named differently, but I'm using this for continuity..
void DemiUSBDevice::device_attach_callback(void * context, 
    io_iterator_t iterator)
{
DeviceManager *deviceManager = (__bridge DADeviceManager *)context;
  io_registry_entry_t device;
  while ((device = IOIteratorNext(iterator))) {

    CFTypeRef prop;
    prop = IORegistryEntrySearchCFProperty(device,
                                           kIOServicePlane,
                                           CFSTR(kIODialinDeviceKey),
                                           kCFAllocatorDefault,
                                           kIORegistryIterateRecursively);
    if(prop){
      deviceManager->devPath = (__bridge NSString *)prop;
      [deviceManager performSelector:@selector(openDevice)];
    }
  }
}

once devPath is set you can call open and read/write.. 一旦设置了devPath,就可以调用open和read / write。

int dfd;
dfd = open([devPath UTF8String], O_RDWR | O_NOCTTY | O_NDELAY);
  if (dfd == -1) {
    //Could not open the port.
    NSLog(@"open_port: Unable to open %@", devPath);
    return;
  } else {
    fcntl(fd, F_SETFL, 0);
  }

After reading this question a few times and thinking about it for a bit, I thought of another solution for emulating blocking read behavior, but using the HID manager instead of replacing it. 在几次阅读此问题并进行了思考之后,我想到了另一种模拟阻塞读取行为的解决方案,但使用HID管理器而不是替换它。

A blocking read function can register an input callback for the device, register the device on the current run loop, and then block by calling CFRunLoopRun(). 阻塞读取功能可以为设备注册输入回调,在当前运行循环上注册设备,然后通过调用CFRunLoopRun()进行阻塞。 The input callback can then copy the report into a shared buffer and call CFRunLoopStop(), which causes CFRunLoopRun() to return, thereby unblocking read(). 然后,输入回调可以将报告复制到共享缓冲区中,并调用CFRunLoopStop(),这将导致CFRunLoopRun()返回,从而取消阻塞read()。 Then, read() can return the report to the caller. 然后,read()可以将报告返回给调用方。

The first issue I can think of is the case where the device is already scheduled on a run loop. 我能想到的第一个问题是设备已经在运行循环中进行调度的情况。 Scheduling and then unscheduling the device in the read function may have adverse affects. 在读取功能中安排设备的时间,然后再安排其时间,可能会产生不利影响。 But that would only be a problem if the application is trying to use both synchronous and asynchronous calls on the same device. 但这仅是一个问题,如果应用程序尝试在同一设备上同时使用同步和异步调用。

The second thing that comes to mind is the case where the calling code already has a run loop running (Cocoa and Qt apps for example). 想到的第二件事是调用代码已经有一个运行循环正在运行的情况(例如Cocoa和Qt应用程序)。 But, the documentation for CFRunLoopStop() seems to indicate that nested calls to CFRunLoopRun() are handled properly. 但是,CFRunLoopStop()的文档似乎表明对CFRunLoopRun()的嵌套调用已得到正确处理。 So, it should be ok. 因此,应该没问题。

Here's a bit of simplified code to go with that. 这里有一些简化的代码。 I just implemented something similar in my HID Library and it seems to work, although I haven't tested it extensively. 我刚刚在我的HID库中实现了类似的功能,尽管我没有对其进行广泛的测试,但它似乎仍然可以工作。

/* An IN report callback that stops its run loop when called. 
   This is purely for emulating blocking behavior in the read() method */
static void input_oneshot(void*           context,
                          IOReturn        result,
                          void*           deviceRef,
                          IOHIDReportType type,
                          uint32_t        reportID,
                          uint8_t*        report,
                          CFIndex         length)
{
    buffer_type *const buffer = static_cast<HID::buffer_type*>(context);

    /* If the report is valid, copy it into the caller's buffer
         The Report ID is prepended to the buffer so the caller can identify
         the report */
    if( buffer )
    {
        buffer->clear();    // Return an empty buffer on error
        if( !result && report && deviceRef )
        {
            buffer->reserve(length+1);
            buffer->push_back(reportID);
            buffer->insert(buffer->end(), report, report+length);
        }
    }

    CFRunLoopStop(CFRunLoopGetCurrent());
}

// Block while waiting for an IN interrupt report
bool read(buffer_type& buffer)
{
    uint8_t _bufferInput[_lengthInputBuffer];

    // Register a callback
    IOHIDDeviceRegisterInputReportCallback(deviceRef, _bufferInput, _lengthInputBuffer, input_oneshot, &buffer);

    // Schedule the device on the current run loop
    IOHIDDeviceScheduleWithRunLoop(deviceRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);

    // Trap in the run loop until a report is received
    CFRunLoopRun();

    // The run loop has returned, so unschedule the device
    IOHIDDeviceUnscheduleFromRunLoop(deviceRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);

    if( buffer.size() )
        return true;
    return false;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM