c++ - OSX,USB 海量存储发现

标签 c++ macos usb iokit usb-mass-storage

我正在尝试在 MAC OSX 下发现 USB 大容量存储设备。我希望获得设备类别,并在此基础上确定该设备是否为大容量存储器。但是,对于我拥有的所有 USB 闪存驱动器,我得到的设备类 == 0,这似乎是一个复合设备。请帮我弄清楚,我做错了什么,或者,也许还有什么其他可靠的方法来发现 USB 大容量存储设备(我需要获取 PID、VID 和挂载点)。这是我的代码:

#import <iostream>
#import <IOKit/IOkitLib.h>
#import <IOKit/usb/IOUSBLib.h>
#import <IOKit/IOCFPlugIn.h>
#import <IOKit/usb/USBSpec.h>
#import <CoreFoundation/CoreFoundation.h>
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
CFMutableDictionaryRef matchingDictionary = NULL;
io_iterator_t foundIterator = 0;
io_service_t usbDevice;
matchingDictionary = IOServiceMatching(kIOUSBDeviceClassName);
IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &foundIterator);
for(usbDevice = IOIteratorNext(foundIterator); usbDevice; usbDevice = IOIteratorNext(foundIterator))
{
    IOCFPlugInInterface** plugin = NULL;
    SInt32 theScore=0;
    IOReturn err;

    err = IOCreatePlugInInterfaceForService(usbDevice, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin, &theScore);
    if (err!= 0){
        std::cout<<"error, error code: "<<err_get_code(err) <<std::endl;
    }
    else if (plugin && *plugin)
    {
        IOUSBDeviceInterface182** usbInterface = NULL;
        (*plugin)->QueryInterface(plugin, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID182),(LPVOID*)&usbInterface);
        (*plugin)->Release(plugin);
        if (usbInterface && *usbInterface)
        {
            UInt8 devClass;
            UInt8 devSubClass;
            UInt16 productId;
            UInt16 vendorID;

            //here I'm getting 0 for all my USB flash cards
            (*usbInterface)->GetDeviceClass(usbInterface,&devClass); 
            (*usbInterface)->GetDeviceVendor(usbInterface, &vendorID);
            (*usbInterface)->GetDeviceProduct(usbInterface, &productId);
            (*usbInterface)->GetDeviceSubClass(usbInterface, &devSubClass);
            std::cout<<"device class: "<<+devClass<<std::endl;
            std::cout<<"device sub class: "<<+devSubClass<<std::endl;
            std::cout<<"vendor ID: "<<vendorID<<std::endl;
            std::cout<<"product ID: "<<productId<<std::endl;
        }
    }
    IOObjectRelease(usbDevice);
}
IOObjectRelease(foundIterator);
return 0;

最佳答案

对我来说,以下遍历 USB 的方法适用于 OSX:

void scanUsbMassStorage()
{
 CFMutableDictionaryRef matchingDictionary = IOServiceMatching(kIOUSBInterfaceClassName);
 //now specify class and subclass to iterate only through USB mass storage devices:
 CFNumberRef cfValue;
 SInt32 deviceClassNum = kUSBMassStorageInterfaceClass;
 cfValue = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &deviceClassNum);
 CFDictionaryAddValue(matchingDictionary, CFSTR(kUSBInterfaceClass), cfValue);
 CFRelease(cfValue);
     SInt32 deviceSubClassNum = kUSBMassStorageSCSISubClass;
 cfValue = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &deviceSubClassNum);
 CFDictionaryAddValue(matchingDictionary, CFSTR(kUSBInterfaceSubClass), cfValue);
 CFRelease(cfValue);
 //NOTE: if you will specify only device class and will not specify subclass, it will return an empty iterator, 
 //and I don't know how to make a query for *any* subclass. 
 //BUT: all the devices I've checked had kUSBMassStorageSCSISubClass
 io_iterator_t foundIterator = 0;
 io_service_t usbInterface;
 IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &foundIterator);
 //iterate through USB mass storage devices
 for(usbInterface = IOIteratorNext(foundIterator); usbInterface; usbInterface = IOIteratorNext(foundIterator))
 {
    CFTypeRef bsdName = IORegistryEntrySearchCFProperty(usbInterface,
                                                        kIOServicePlane,
                                                        CFSTR(kIOBSDNameKey),
                                                        kCFAllocatorDefault,
                                                        kIORegistryIterateRecursively);

    CFTypeRef serial = IORegistryEntrySearchCFProperty(usbInterface,
                                                       kIOServicePlane,
                                                       CFSTR(kUsbSerialPropertyName),
                                                       kCFAllocatorDefault,
                                                       kIORegistryIterateRecursively|kIORegistryIterateParents);

    CFTypeRef pid = IORegistryEntrySearchCFProperty(usbInterface,
                                                    kIOServicePlane,
                                                    CFSTR(kUsbPidPropertyName),
                                                    kCFAllocatorDefault,
                                                    kIORegistryIterateRecursively|kIORegistryIterateParents);

    CFTypeRef vid = IORegistryEntrySearchCFProperty(usbInterface,
                                                    kIOServicePlane,
                                                    CFSTR(kUsbVidPropertyName),
                                                    kCFAllocatorDefault,
                                                    kIORegistryIterateRecursively|kIORegistryIterateParents);

    //now we can perform checks and casts from CFTypeRef like this:
    std::string filePathStr;
    std::string serialStr;
    uint16_t pidInt;
    uint16_t vidInt;
    //getMountPathByBSDName - see below
    bool stillOk = getMountPathByBSDName(bsdName, filePath);
    if (stillOk)
    {
        stillOk = CFTypeRef2AsciiString(serial, serialStr);
    }
    if (stillOk)
    {
        stillOk = CFTypeRef2uint16(pid, pidInt);
    }
    if (stillOk)
    {
        stillOk = CFTypeRef2uint16(vid, vidInt);
    }
    if (stillOK) 
    {
      //can do something with the values here
    }
 }

不过,从 BSD 名称获取挂载路径对我来说出乎意料地棘手,如果有人知道更好的方法,请分享。这是我的方法。有很多代码,但至少这适用于几个不同的 OSX 版本:

bool getMountPathByBSDName(CFTypeRef bsdName, std::string& dest)
{
std::list<std::string> bsdNames;
//for getChildBsdNames - see below =)
getChildBsdNames(bsdName, bsdNames);
DASessionRef session = DASessionCreate(kCFAllocatorDefault);
if (!session)
{
    return false;
}
for (const auto& bsdNameStr : bsdNames)
{
    DADiskRef disk = DADiskCreateFromBSDName(kCFAllocatorDefault, session, bsdNameStr.c_str());
    if (disk)
    {
        CFDictionaryRef diskInfo = DADiskCopyDescription(disk);
        if (diskInfo)
        {
            char buf[1024];
            CFURLRef fspath = (CFURLRef)CFDictionaryGetValue(diskInfo, kDADiskDescriptionVolumePathKey);
            if (CFURLGetFileSystemRepresentation(fspath, false, (UInt8 *)buf, 1024))
            {
                //for now, return the first found partition
                dest = std::string(buf);
                return true;
            }
        }
     }
}
return false;
}

最后 - getChildBsdNames 函数:

void getChildBsdNames(CFTypeRef bsdName, std::list<std::string>& tgtList)
{
std::string bsdNameStr;
if(!CFTypeRef2AsciiString(bsdName, bsdNameStr))
{
    return;
}
CFDictionaryRef matchingDictionary = IOBSDNameMatching(kIOMasterPortDefault, 0, bsdNameStr.c_str());
io_iterator_t it;
IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &it);
io_object_t service;
while ((service = IOIteratorNext(it)))
{
    io_iterator_t children;
    io_registry_entry_t child;

    IORegistryEntryGetChildIterator(service, kIOServicePlane, &children);
    while((child = IOIteratorNext(children)))
    {
        CFTypeRef bsdNameChild = IORegistryEntrySearchCFProperty(child,
                                                                kIOServicePlane,
                                                                CFSTR (kIOBSDNameKey),
                                                                kCFAllocatorDefault,
                                                                kIORegistryIterateRecursively);
        std::string bsdNameChildStr;
        if (CFTypeRef2AsciiString(bsdNameChild, bsdNameChildStr))
        {
            tgtList.push_back(bsdNameChildStr);
        }
    }
}
/**
 * The device could get name 'disk1s1, or just 'disk1'. In first case, the original bsd name would be 
 * 'disk1', and the child bsd name would be 'disk1s1'. In second case, there would be no child bsd names,
 * but the original one is valid for further work (obtaining various properties).
 */
if (tgtList.empty())
{
    tgtList.push_back(bsdNameStr);
}
}

附言有一个基于事件的机制,但由于某些原因对我来说这不是解决方案。我选择了轮询,但修改此代码以使其基于事件并不难。但请注意,该事件可能会在设备安装之前发生。

关于c++ - OSX,USB 海量存储发现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45173124/

相关文章:

objective-c - 报告 OSX 中的大小写不敏感(使用 OSXFuse)

ubuntu - 无法在 Ubuntu 12.04 的 USB 串行端口上设置波特率

usb - 使用 BeagleBone Black kernel >= 3.8 打开/关闭 USB 电源

python - Git:保持同一文件的两个位置同步

c++ - istringstream 不在变量中存储任何内容

c++ - 无法在我的 C++ 类中初始化 2d Vector

macos - 使用自定义类型标识符拖放不起作用

c++ - boost asio async_read 延迟(本地套接字)

macos - Darwin 内核架构和 OS X,32 位内核上的 64 位,这是如何工作的?

java - 需要帮助找到通过java代码连接到系统的USB驱动器的VID/PID