macos 在 Mac 上读取和写入 USB (HID) 中断端点

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3368008/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 07:05:32  来源:igfitidea点击:

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

c++macosusbhidkernel-extension

提问by Demi

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. 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...

该设备是带有 HID 接口(3 类)的 USB 设备,具有两个端点,一个中断输入和一个中断输出。设备的性质是这样的,只有当主机请求数据时,数据才从输入端点上的设备发送出去:主机向它发送数据,设备在其输入中断端点上响应。将数据发送到设备(写入)要简单得多......

The code for Windows is rather straight-forward: I get a handle to the device and then call either ReadFile or WriteFile. Apparently much of the underlying asynchronous behavior is abstracted out. It appears to work fine.

Windows 的代码相当简单:我获得设备的句柄,然后调用 ReadFile 或 WriteFile。显然,大部分底层异步行为都被抽象出来了。它似乎工作正常。

On Mac, however, it is a bit stickier. I have tried a number of things, none which have been fully successful, but here are the two things which seemed most promising...

然而,在 Mac 上,它有点粘。我尝试了很多事情,没有一件完全成功,但这里有两件事似乎最有希望......

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. 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. I have tried using IOUSBinterfaceInterface183, so that I could call USBInterfaceOpenSeize, but that results in the same return error value.

1.) 尝试通过 IOUSBInterfaceInterface 访问设备(作为 USB),遍历端点以确定输入和输出端点,并(希望)使用 ReadPipe 和 WritePipe 进行通信。不幸的是,一旦我拥有它,我就无法打开界面,返回值 (kIOReturnExclusiveAccess) 指出某些东西已经以独占方式打开了设备。我曾尝试使用 IOUSBinterfaceInterfaceInterface183,以便我可以调用 USBInterfaceOpenSeize,但这会导致相同的返回错误值。

--- update 7/30/2010 ---
Apparently, the Apple IOUSBHIDDriver matches early to the device and this is what likely is preventing opening the 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. 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. 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.

--- 2010 年 7 月 30
日更新 ---显然,Apple IOUSBHIDDriver 很早就与设备匹配,这可能是阻止打开 IOUSBInterfaceInterface 的原因。从一些挖掘来看,防止 IOUSBHIDDriver 匹配的常用方法似乎是编写一个具有更高探测分数的无代码 kext(内核扩展)。这将尽早匹配,防止 IOUSBHIDDriver 打开设备,并且理论上应该允许我打开接口并直接写入和读取端点。这没关系,但我更愿意不必在用户计算机上安装其他东西。如果有人知道可靠的替代方案,我将不胜感激。

2.) Open the device as an IOHIDDeviceInterface122 (or later). 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.

2.) 将设备作为 IOHIDDeviceInterface122(或更高版本)打开。为了读取,我设置了一个异步端口、事件源和回调方法,以便在数据准备好时调用 - 当数据从输入中断端点上的设备发送时。但是,要写入设备所需的数据以初始化响应,我找不到方法。我难住了。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 上运行。

回答by Demi

I have now a working Mac driver to a USB device that requires communication through interrupt endpoints. Here is how I did it:

我现在有一个需要通过中断端点进行通信的 USB 设备的工作 Mac 驱动程序。这是我如何做到的:

Ultimately the method that worked well for me was option 1 (noted above). As noted, I was having issues opening the COM-style IOUSBInterfaceInterface to the device. It became clear over time that this was due to the HIDManager capturing the device. 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).

最终,对我来说效果很好的方法是选项 1(如上所述)。如前所述,我在打开设备的 COM 样式 IOUSBInterfaceInterface 时遇到问题。随着时间的推移,很明显这是由于 HIDManager 捕获了设备。一旦被捕获,我就无法从 HIDManager 夺取对设备的控制权(甚至 USBInterfaceOpenSeize 调用或 USBDeviceOpenSeize 调用都不起作用)。

To take control of the device I needed to grab it before the HIDManager. The solution to this was to write a codeless kext (kernel extension). 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. In my case I wanted only the plist, which would give the instructions to the kernel on what devices it matches. If the data gives a higher probe scorethan the HIDManager then I could essentially capture the device and use a user-space driver to communicate with it.

为了控制设备,我需要在 HIDManager 之前抓住它。对此的解决方案是编写一个无代码的 kext(内核扩展)。kext 本质上是一个位于 System/Library/Extensions 中的包,其中包含(通常)一个 plist(属性列表)和(偶尔)一个内核级驱动程序等。在我的情况下,我只想要 plist,它将向内核提供有关它匹配的设备的指令。如果数据给出比 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)
  2. Copy the kext to Extensions (sudo cp DemiUSBDevice.kext /System/Library/Extensions)
  3. Call the kextloadutility to load the kext for immediate use without restart (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)
  1. 将所有者更改为 root:wheel ( sudo chown root:wheel DemiUSBDevice.kext)
  2. 将 kext 复制到扩展 ( sudo cp DemiUSBDevice.kext /System/Library/Extensions)
  3. 调用kextload实用程序加载 kext 以供立即使用,无需重新启动 ( sudo kextload -vt /System/Library/Extensions/DemiUSBDevice.kext)
  4. 触摸扩展文件夹,以便下次重新启动时强制重建缓存 ( sudo touch /System/Library/Extensions)

At this point the system should use the kext to keep the HIDManager from capturing my device. Now, what to do with it? How to write to and read from it?

此时系统应该使用 kext 来防止 HIDManager 捕获我的设备。现在,该怎么办?如何写入和读取它?

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:

以下是我的代码的一些简化片段,减去任何错误处理,说明了解决方案。在能够对设备执行任何操作之前,应用程序需要知道设备何时连接(和分离)。请注意,这仅用于说明目的——一些变量是类级别的,一些是全局的,等等。这是设置附加/分离事件的初始化代码:

#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 is straightforward:

在此初始化代码中有两个方法用作回调:device_detach_callback 和 device_attach_callback(均在静态方法中声明)。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. In my code I have this broken into multiple methods, but here I'll present it as a big monolithic method...:

device_attach_callback 是最神奇的地方。在我的代码中,我将其分解为多种方法,但在这里我将其呈现为一个大的整体方法......:

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. An asynchronous writing of data can be done by calling something like:

此时我们应该有中断端点的编号和一个打开的 IOUSBInterfaceInterface 到设备。异步写入数据可以通过调用类似的东西来完成:

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 缓冲区,最后一个参数是传递给回调的可选上下文对象,并且 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). 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. After handling your callback you can call CFRunLoopStop(CFRunLoopGetCurrent())to stop the run loop and hand execution back to the main thread.

请注意,要接收这些回调,运行循环必须正在运行(有关 CFRunLoop 的更多信息,请参阅此链接)。实现此目的的一种方法是CFRunLoopRun()在调用异步读取或写入方法之后调用,此时主线程在运行循环运行时阻塞。处理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). After calling the read/write method, instead of calling CFRunLoopRun(), something like the following can be executed:

另一种替代方法(我在我的代码中这样做)是将上下文对象(在以下代码示例中称为“请求”)传递到 WritePipeAsync/ReadPipeAsync 方法中 - 该对象包含一个布尔完成标志(在本例中称为“is_done”) . 调用读/写方法后,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...

我希望这对人们有所帮助。我不得不从许多不完整的来源中提取来解决这个问题,这需要大量的工作才能运行良好......

回答by Brandon Fosdick

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(). The input callback can then copy the report into a shared buffer and call CFRunLoopStop(), which causes CFRunLoopRun() to return, thereby unblocking read(). Then, read() can return the report to the caller.

阻塞读函数可以为设备注册一个输入回调,在当前运行循环中注册设备,然后通过调用CFRunLoopRun()来阻塞。然后输入回调可以将报告复制到共享缓冲区并调用 CFRunLoopStop(),这会导致 CFRunLoopRun() 返回,从而解除 read() 的阻塞。然后, 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). But, the documentation for CFRunLoopStop() seems to indicate that nested calls to CFRunLoopRun() are handled properly. So, it should be ok.

想到的第二件事是调用代码已经运行了一个运行循环(例如 Cocoa 和 Qt 应用程序)的情况。但是,CFRunLoopStop() 的文档似乎表明对 CFRunLoopRun() 的嵌套调用得到了正确处理。所以,应该没问题。

Here's a bit of simplified code to go with that. I just implemented something similar in my HID Libraryand 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;
}

回答by estobbart

I ran into this same kIOReturnExclusiveAccess. Instead of fighting it (building a kext, etc). I found the device and used the POSIX api's.

我遇到了同样的 kIOReturnExclusiveAccess。而不是与之抗争(构建 kext 等)。我找到了设备并使用了 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);
  }