Search code examples
swiftiokit

How to call IOCFPlugInInterface.QueryInterface() to query USB devices


I want to convert this USB detector to Swift 3 https://gist.github.com/zachbadgett/471d72e83fee413d0f38

But I am stuck on this line:

let deviceInterfaceResult = plugInInterface.QueryInterface(
        plugInInterfacePtrPtr,
        CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID),
&deviceInterfaceVoidPtr)

Cannot call value of non-function type '(@convention(c) (UnsafeMutableRawPointer?, REFIID, UnsafeMutablePointer?) -> HRESULT)!'

enter image description here

Source QueryInterface:

public var QueryInterface: (@convention(c) (UnsafeMutableRawPointer?, REFIID, UnsafeMutablePointer<LPVOID?>?) -> HRESULT)!

How do I call this function? Using CFNotificationCallback in Swift, or, @convention(c) blocks in Swift this answer didn't help me.


Solution

  • Cœur's answer is very close. plugInInterface.QueryInterface() expects as the last argument the address of a double-indirect pointer

    UnsafeMutablePointer<UnsafeMutablePointer<IOUSBDeviceInterface>?>?
    

    but "disguised" as a pointer to LPVOID? aka UnsafeMutableRawPointer. Consequently, the obtained pointer must be dereferenced twice. withMemoryRebound() can be used for this pointer cast.

    I made some more changes to the code:

    • Use defer to continue with the next USB device even if the current iteration was "aborted" due to an error.
    • Remove unnecessary type annotations.
    • Use MemoryLayout<io_name_t>.size instead of 128.
    • Release usbDevice and the interface pointers after use to avoid memory leaks.
    • Move some variable declarations from the top to where they are needed.

    Putting it all together:

    import Foundation
    import IOKit
    import IOKit.usb
    import IOKit.usb.IOUSBLib
    
    print("Scanning USB Bus.....\n\n\n")
    
    //
    // These constants are not imported into Swift from IOUSBLib.h as they
    // are all #define constants
    //
    
    let kIOUSBDeviceUserClientTypeID = CFUUIDGetConstantUUIDWithBytes(kCFAllocatorDefault,
                                                                      0x9d, 0xc7, 0xb7, 0x80, 0x9e, 0xc0, 0x11, 0xD4,
                                                                      0xa5, 0x4f, 0x00, 0x0a, 0x27, 0x05, 0x28, 0x61)
    
    let kIOCFPlugInInterfaceID = CFUUIDGetConstantUUIDWithBytes(kCFAllocatorDefault,
                                                                0xC2, 0x44, 0xE8, 0x58, 0x10, 0x9C, 0x11, 0xD4,
                                                                0x91, 0xD4, 0x00, 0x50, 0xE4, 0xC6, 0x42, 0x6F)
    
    let kIOUSBDeviceInterfaceID = CFUUIDGetConstantUUIDWithBytes(kCFAllocatorDefault,
                                                                 0x5c, 0x81, 0x87, 0xd0, 0x9e, 0xf3, 0x11, 0xD4,
                                                                 0x8b, 0x45, 0x00, 0x0a, 0x27, 0x05, 0x28, 0x61)
    
    
    // Create dictionary with IOUSBDevice as IOProviderClass.
    let matchingDictionary: NSMutableDictionary = IOServiceMatching(kIOUSBDeviceClassName)
    
    // Get iterator for matching USB devices.
    var usbIterator = io_iterator_t()
    let matchingServicesResult = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &usbIterator)
    if matchingServicesResult != kIOReturnSuccess {
        print("Error getting deviceList!")
        exit(EXIT_FAILURE)
    }
    
    // Iterate devices until usbDevice == 0.
    var usbDevice = IOIteratorNext(usbIterator)
    while usbDevice != 0 {
        defer {
            usbDevice = IOIteratorNext(usbIterator)
        }
    
        // io_name_t imports to Swift as a tuple (Int8, ..., Int8) with 128 ints
        // although in device_types.h it is defined as
        //      typedef char io_name_t[128];
        var deviceNameCString = [CChar](repeating: 0, count: MemoryLayout<io_name_t>.size)
        let deviceNameResult = IORegistryEntryGetName(usbDevice, &deviceNameCString)
        if deviceNameResult != kIOReturnSuccess {
            print("Error getting device name")
            continue
        }
        let deviceName = String(cString: &deviceNameCString)
        print("USB device name: \(deviceName)")
    
        // Get plug-in interface for current USB device
        var plugInInterfacePtrPtr: UnsafeMutablePointer<UnsafeMutablePointer<IOCFPlugInInterface>?>?
        var score: Int32 = 0
        let plugInInterfaceResult = IOCreatePlugInInterfaceForService(
            usbDevice,
            kIOUSBDeviceUserClientTypeID,
            kIOCFPlugInInterfaceID,
            &plugInInterfacePtrPtr,
            &score)
    
        // USB device object is no longer needed.
        IOObjectRelease(usbDevice)
    
        // Dereference pointer for the plug-in interface
        guard plugInInterfaceResult == kIOReturnSuccess,
            let plugInInterface = plugInInterfacePtrPtr?.pointee?.pointee else {
                print("Unable to get Plug-In Interface")
                continue
        }
    
        // Use plug-in interface to get a device interface.
        var deviceInterfacePtrPtr: UnsafeMutablePointer<UnsafeMutablePointer<IOUSBDeviceInterface>?>?
        let deviceInterfaceResult = withUnsafeMutablePointer(to: &deviceInterfacePtrPtr) {
            $0.withMemoryRebound(to: Optional<LPVOID>.self, capacity: 1) {
                plugInInterface.QueryInterface(
                    plugInInterfacePtrPtr,
                    CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID),
                    $0)
            }
        }
    
        // Plug-in interface is no longer needed.
        _ = plugInInterface.Release(plugInInterfacePtrPtr)
    
        // Dereference pointer for the device interface.
        guard deviceInterfaceResult == kIOReturnSuccess,
            let deviceInterface = deviceInterfacePtrPtr?.pointee?.pointee else {
                print("Unable to get Device Interface")
                continue
        }
    
        // Use device interface to get vendor ID.
        var usbVendorID: UInt16 = 0
        let vendorResult = deviceInterface.GetDeviceVendor(deviceInterfacePtrPtr, &usbVendorID)
    
        // Device interface is no longer needed:
        _ = deviceInterface.Release(deviceInterfacePtrPtr)
    
        if vendorResult != kIOReturnSuccess {
            print("Unable to get device Vendor ID")
            continue
        }
    
        print("USB Vendor ID: \(usbVendorID)")
    }
    
    exit(EXIT_SUCCESS)