[swift-users] IOKit and USB devices with swift 3

Quinn "The Eskimo!" eskimo1 at apple.com
Mon Sep 26 04:48:06 CDT 2016

On 23 Sep 2016, at 17:46, Jérôme Duquennoy via swift-users <swift-users at swift.org> wrote:

> I am trying to access an USB device in a swift project, with quite limited knowledge of IOKit and not a [lot] of experience using unsafe pointers with swift.

Oh, fun times!

First things first, you don’t need IOMasterPort; use kIOMasterPortDefault instead.

Second, IOServiceMatching won’t return nil unless you run out of memory; you can just ignore that possibility.

Next, unless you absolutely have to, I’d avoid getting between IOServiceMatching and IOServiceAddMatchingNotification.  These two have unusual memory management behaviour (IOServiceMatching returns a +1 reference and IOServiceAddMatchingNotification consumes that reference).  It looks like the current SDK has the right annotations for this but it’s still easy to run into trouble.

IO_OBJECT_NULL rather than 0.

There’s two parts to your overall problem:

A. discovering devices

B. talking to devices

I’d separate these so you can debug them independently.  Specifically, you can test B from a single matching service (IOServiceGetMatchingService) without having to deal with A at all.  You can then generalise that to a list (using IOServiceGetMatchingServices) before moving on to tackle A.

As to your actual crash, you seem to be missing a level of indirection in `getDeviceInterface(forPluginInterface:)`.  Consider this code:

200   private func getDeviceInterface(forPluginInterface pluginInterfacePtrP…
201     var deviceInterfaceRawPtr: UnsafeMutableRawPointer? = nil
203     let deviceInterfaceResult = pluginInterfacePtrPtr.pointee?.pointee.Q…
206     if (deviceInterfaceResult != kIOReturnSuccess) || (deviceInterfaceRa…
207       throw Error("Could not get device interface for plugin interface")…
208     }
210     return deviceInterfaceRawPtr!.assumingMemoryBound(to: IOUSBDeviceInt…
211   }

Expand line 210…211 to this:

210   let res = deviceInterfaceRawPtr!.assumingMemoryBound(to: IOUSBDevice…
211   return res
212 }

and set a breakpoint on 211.  At the breakpoint `res` is gibberish.

(lldb) p res.pointee
(IOUSBDeviceInterface) $R1 = {
  _reserved = (_rawValue = 0x000000010916ea48 IOUSBDeviceClass::sUSBDevice…
  QueryInterface = 0x0000610000101830 -> 0x000000010916e250 IOUSBLib`vtabl…
  AddRef = 0x0000880300008703
  Release = nil

Hint: the `Release` property should never be nil (-:

You need to add a level of indirection:

210   let res = deviceInterfaceRawPtr!.assumingMemoryBound(to: 
211   return res.pointee
212 }

at which point your breakpoint on 211 which show sensible results:

(lldb) p res.pointee.pointee
(IOUSBDeviceInterface) $R1 = {
  _reserved = nil
  QueryInterface = 0x000000010a349cc0 IOUSBLib`IOUSBIUnknown::genericQueryInterface(void*, CFUUIDBytes, void**)
  AddRef = 0x000000010a349cd2 IOUSBLib`IOUSBIUnknown::genericAddRef(void*)
  Release = 0x000000010a349ce2 IOUSBLib`IOUSBIUnknown::genericRelease(void*)

You can see this when you look at the equivalent code in C.  For example, this line:

kr = (*privateDataRef->deviceInterface)->GetLocationID(privateDataRef->deviceInterface, &locationID);

from the USBPrivateDataSample.


`privateDataRef` is indirected once with the `*` and again with the `->` (in C, `foo->bar` expands to `(*foo).bar`.

I recommend that you take a leaf out of the C’s book here and store the double indirected value.  That is, have `getDeviceInterface(forPluginInterface:)` return `UnsafeMutablePointer<UnsafeMutablePointer<IOUSBDeviceInterface>>` rather than `UnsafeMutablePointer<IOUSBDeviceInterface>`.

                   *                   *                   *

This is all super unpleasant and I wish I had time to write up more detailed instructions.  Hopefully this will get you unblocked.

Share and Enjoy
Quinn "The Eskimo!"                    <http://www.apple.com/developer/>
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

More information about the swift-users mailing list