根据VID / PID查找并弹出USB设备

时间:2010-10-31 22:26:05

标签: c++ windows

我想将弹出命令发送到由其VID和PID标识的特定USB设备。我可以通过使用SetupDiEnumDeviceInfo()和SetupDiGetDeviceRegistryProperty()找到该设备,并匹配HARDWAREID字符串中的VID / PID编号,但这是我所拥有的。

我有一个SP_DEVINFO_DATA结构和一个HDEVINFO句柄。我如何将它们与驱动器号或卷路径相关联,以便我可以向它发送弹出命令?

1 个答案:

答案 0 :(得分:9)

好吧,我明白了。由Luke链接的CodeProject文章显示了如何将驱动器号与设备接口匹配,这种方式只有一半,所以我会给出答案,但它并不能解决整个问题。

我需要弄清楚如何找到USB设备的设备实例,并找到一种方法将其与设备接口相匹配。 CM_Locate_DevNode()和CM_Get_Child()函数是关键。最后,我可以使用IOCTL弹出设备。

我正在处理的设备是USB CD-ROM驱动器,这就是我将设备类型硬编码为CDROM的原因。我无法相信需要多少代码才能完成我认为相当简单的任务(我引用了我的客户2小时来编写这段代码,我需要花费四天的时间来完成这一切!)。这是最终的工作代码,希望能让你们中的一个人能够像我一样经历同样的地狱:

#include <SetupAPI.h>
#include <cfgmgr32.h>
#include <winioctl.h>

// Finds the device interface for the CDROM drive with the given interface number.
DEVINST GetDrivesDevInstByDeviceNumber(long DeviceNumber)
{
    const GUID *guid = &GUID_DEVINTERFACE_CDROM;

// Get device interface info set handle
// for all devices attached to system
    HDEVINFO hDevInfo = SetupDiGetClassDevs(guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
    if(hDevInfo == INVALID_HANDLE_VALUE)
        return 0;

// Retrieve a context structure for a device interface of a device information set.
    BYTE                             buf[1024];
    PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)buf;
    SP_DEVICE_INTERFACE_DATA         spdid;
    SP_DEVINFO_DATA                  spdd;
    DWORD                            dwSize;

    spdid.cbSize = sizeof(spdid);

// Iterate through all the interfaces and try to match one based on
// the device number.
    for(DWORD i = 0; SetupDiEnumDeviceInterfaces(hDevInfo, NULL,guid, i, &spdid); i++)
    {
    // Get the device path.
        dwSize = 0;
        SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, NULL, 0, &dwSize, NULL);
        if(dwSize == 0 || dwSize > sizeof(buf))
            continue;

        pspdidd->cbSize = sizeof(*pspdidd);
        ZeroMemory((PVOID)&spdd, sizeof(spdd));
        spdd.cbSize = sizeof(spdd);
        if(!SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, pspdidd,
                                            dwSize, &dwSize, &spdd))
            continue;

    // Open the device.
        HANDLE hDrive = CreateFile(pspdidd->DevicePath,0,
                                   FILE_SHARE_READ | FILE_SHARE_WRITE,
                                   NULL, OPEN_EXISTING, 0, NULL);
        if(hDrive == INVALID_HANDLE_VALUE)
            continue;

    // Get the device number.
        STORAGE_DEVICE_NUMBER sdn;
        dwSize = 0;
        if(DeviceIoControl(hDrive,
                           IOCTL_STORAGE_GET_DEVICE_NUMBER,
                           NULL, 0, &sdn, sizeof(sdn),
                           &dwSize, NULL))
        {
        // Does it match?
            if(DeviceNumber == (long)sdn.DeviceNumber)
            {
                CloseHandle(hDrive);
                SetupDiDestroyDeviceInfoList(hDevInfo);
                return spdd.DevInst;
            }
        }
        CloseHandle(hDrive);
    }

    SetupDiDestroyDeviceInfoList(hDevInfo);
    return 0;
}


// Returns true if the given device instance belongs to the USB device with the given VID and PID.
bool matchDevInstToUsbDevice(DEVINST device, DWORD vid, DWORD pid)
{
// This is the string we will be searching for in the device harware IDs.
    TCHAR hwid[64];
    _stprintf(hwid, _T("VID_%04X&PID_%04X"), vid, pid);

// Get a list of hardware IDs for all USB devices.
    ULONG ulLen;
    CM_Get_Device_ID_List_Size(&ulLen, NULL, CM_GETIDLIST_FILTER_NONE);
    TCHAR *pszBuffer = new TCHAR[ulLen];
    CM_Get_Device_ID_List(NULL, pszBuffer, ulLen, CM_GETIDLIST_FILTER_NONE);

// Iterate through the list looking for our ID.
    for(LPTSTR pszDeviceID = pszBuffer; *pszDeviceID; pszDeviceID += _tcslen(pszDeviceID) + 1)
    {
    // Some versions of Windows have the string in upper case and other versions have it
    // in lower case so just make it all upper.
        for(int i = 0; pszDeviceID[i]; i++)
            pszDeviceID[i] = toupper(pszDeviceID[i]);

        if(_tcsstr(pszDeviceID, hwid))
        {
        // Found the device, now we want the grandchild device, which is the "generic volume"
            DEVINST MSDInst = 0;
            if(CR_SUCCESS == CM_Locate_DevNode(&MSDInst, pszDeviceID, CM_LOCATE_DEVNODE_NORMAL))
            {
                DEVINST DiskDriveInst = 0;
                if(CR_SUCCESS == CM_Get_Child(&DiskDriveInst, MSDInst, 0))
                {
                // Now compare the grandchild node against the given device instance.
                    if(device == DiskDriveInst)
                        return true;
                }
            }
        }
    }

    return false;
}

// Eject the given drive.
void ejectDrive(TCHAR driveletter)
{
    TCHAR devicepath[16];
    _tcscpy(devicepath, _T("\\\\.\\?:"));
    devicepath[4] = driveletter;

    DWORD dwRet = 0;
    HANDLE hVol = CreateFile(devicepath, GENERIC_READ, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
    if(hVol == INVALID_HANDLE_VALUE)
        return;

    if(!DeviceIoControl(hVol, FSCTL_LOCK_VOLUME, 0, 0, 0, 0, &dwRet, 0))
        return;

    if(!DeviceIoControl(hVol, FSCTL_DISMOUNT_VOLUME, 0, 0, 0, 0, &dwRet, 0))
        return;

    DeviceIoControl(hVol, IOCTL_STORAGE_EJECT_MEDIA, 0, 0, 0, 0, &dwRet, 0);

    CloseHandle(hVol);
}

// Find a USB device by it's Vendor and Product IDs. When found, eject it.
void usbEjectDevice(unsigned vid, unsigned pid)
{
    TCHAR devicepath[8];
    _tcscpy(devicepath, _T("\\\\.\\?:"));

    TCHAR drivepath[4];
    _tcscpy(drivepath, _T("?:\\"));

// Iterate through every drive letter and check if it is our device.
    for(TCHAR driveletter = _T('A'); driveletter <= _T('Z'); driveletter++)
    {
    // We are only interested in CDROM drives.
        drivepath[0] = driveletter;
        if(DRIVE_CDROM != GetDriveType(drivepath))
            continue;

    // Get the "storage device number" for the current drive.
        long DeviceNumber = -1;
        devicepath[4]     = driveletter;
        HANDLE hVolume    = CreateFile(devicepath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE,
                                        NULL, OPEN_EXISTING, 0, NULL);
        if(INVALID_HANDLE_VALUE == hVolume)
            continue;

        STORAGE_DEVICE_NUMBER sdn;
        DWORD dwBytesReturned = 0;
        if(DeviceIoControl(hVolume, IOCTL_STORAGE_GET_DEVICE_NUMBER,
                            NULL, 0, &sdn, sizeof(sdn), &dwBytesReturned, NULL))
            DeviceNumber = sdn.DeviceNumber;
        CloseHandle(hVolume);
        if(DeviceNumber < 0)
            continue;

    // Use the data we have collected so far on our drive to find a device instance.
        DEVINST DevInst = GetDrivesDevInstByDeviceNumber(DeviceNumber);

    // If the device instance corresponds to the USB device we are looking for, eject it.
        if(DevInst)
        {
            if(matchDevInstToUsbDevice(DevInst, vid, pid))
                ejectDrive(driveletter);
        }   
    }
}