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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4065473/
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-09-15 15:30:49  来源:igfitidea点击:

Find and eject a USB device based on its VID/PID

c++windows

提问by Adam Pierce

I want to send an eject command to a specific USB device identified by it's VID and PID. I can find the device by using SetupDiEnumDeviceInfo() and SetupDiGetDeviceRegistryProperty() and matching the VID/PID numbers in the HARDWAREID string but that's as far as I've got.

我想向由 VID 和 PID 标识的特定 USB 设备发送弹出命令。我可以通过使用 SetupDiEnumDeviceInfo() 和 SetupDiGetDeviceRegistryProperty() 并匹配 HARDWAREID 字符串中的 VID/PID 数字来找到设备,但就我所知。

I have a SP_DEVINFO_DATA struct and a HDEVINFO handle. How would I relate these to a drive letter or volume path so I can send it an eject command?

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

采纳答案by Adam Pierce

Well, I figured it out. The CodeProject article linked to by Luke shows how to match the drive letter to a device interface which is half the way there so I'll +1 that answer but it doesn't solve the whole problem.

嗯,我想通了。Luke 链接的 CodeProject 文章展示了如何将驱动器号与设备接口相匹配,该接口位于那里的一半,因此我会+1 该答案,但它并不能解决整个问题。

I needed to figure out how to find the device instance for my USB device and find a way to match that to the device interface. The CM_Locate_DevNode() and CM_Get_Child() functions were the key to this. Finally I can use an IOCTL to eject the device.

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

The device I am dealing with is a USB CD-ROM drive which is why I have hard-coded the device type to CDROM. I can't believe how much code is required to do what I thought would be a fairly straightforward task (I quoted my client 2 hours to write this code, it's taken me four days to figure it all out!). Here's the final working code which will hopefully save one of you out there from going through the same hell as I just have:

我正在处理的设备是 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);
        }   
    }
}