Qt弹出U盘及光盘(Windows API)

1 概述

本文描述了在Qt下利用Windows API实现U盘及光盘弹出操作。

2 实现

通过查资料U盘弹出操作需要使用Windows API函数CM_Request_Device_Eject,该函数原型为:

CMAPI CONFIGRET CM_Request_Device_Eject(
  [in]            DEVINST        dnDevInst,
  [out, optional] PPNP_VETO_TYPE pVetoType,
  [out, optional] LPWSTR         pszVetoName,
  [in]            ULONG          ulNameLength,
  [in]            ULONG          ulFlags
);

实现关键是如何将U盘的驱动器盘符转化为DEVINST类型设备句柄。
需要引用的Windows API头文件

#include <windows.h>
#include <winioctl.h>
#include <setupapi.h>
#include <initguid.h>
#include <newdev.h>
#include <cfgmgr32.h>
#include <regstr.h>
#include <combaseapi.h>
#include <shlobj.h>

2.1 函数定义

定义下面5个函数实现磁盘弹出:

  • GetDiskNumber 通过驱动器名称返回磁盘号
  • GetDriverType 通过驱动器名称返回类型
  • GetDrivesDevInstByDiskNumber 通过驱动器号和类型返回磁盘设备号
  • IsRemovable 通过设备号判断磁盘是否可移出
  • RemoveDisk 通过驱动器号和类型弹出磁盘
  • EjectDisk 通过驱动器名称弹出磁盘

2.2 函数实现

2.2.1 GetDiskNumber
long GetDiskNumber(QString const& dirverPath, bool isDevicePath = false)
{
    long diskNumber  = -1;
    QString volumeAccessPath = isDevicePath ? dirverPath : QString("\\\\.\\%1").arg(dirverPath.toUpper());
    HANDLE hVolume = CreateFile(volumeAccessPath.toStdWString().c_str(), 0,
                FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);

    if(hVolume == INVALID_HANDLE_VALUE)
        return diskNumber;

    STORAGE_DEVICE_NUMBER sdn;
    DWORD dwBytesReturned = 0;
    long res = DeviceIoControl(hVolume, IOCTL_STORAGE_GET_DEVICE_NUMBER,
                                nullptr, 0, &sdn, sizeof(sdn), &dwBytesReturned, nullptr);
    if (res)
        diskNumber = sdn.DeviceNumber;

    CloseHandle(hVolume);

    return diskNumber;
}

该函数通过CreateFile打开一个文件句柄,在这个文件句柄通过DeviceIoControl获取设备号。传入参数dirverPath是驱动器名称,例如D:

2.2.2 GetDriverType
UINT GetDriverType(QString const& dirverPath)
{
    QString rootPath = QString("%1\\").arg(dirverPath.toUpper());
    return GetDriveType(rootPath.toStdWString().c_str());
}
该函数调用GetDriveType返回设备类型,传入参数dirverPath是驱动器名称,例如D:
2.2.3 GetDrivesDevInstByDiskNumber
DEFINE_GUID(GUID_DEVINTERFACE_DISK,  0x53f56307L, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b );
DEFINE_GUID(GUID_DEVINTERFACE_CDROM, 0x53f56308L, 0xb6bf, 0x11d0, 0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb, 0x8b );
#define MAX_BUF_SIZE 1024
DEVINST GetDrivesDevInstByDiskNumber(long diskNumber, long driverType)
{
    if(driverType != DRIVE_REMOVABLE
        && driverType != DRIVE_FIXED
        && driverType != DRIVE_CDROM)
     return 0;

    const GUID* guid;
    if(driverType == DRIVE_CDROM)
        guid = &GUID_DEVINTERFACE_CDROM;
    else
        guid = &GUID_DEVINTERFACE_DISK;

    HDEVINFO hDevInfo = SetupDiGetClassDevs(guid, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
    if(hDevInfo == INVALID_HANDLE_VALUE)
        return 0;

    DWORD dwIndex = 0;
    SP_DEVICE_INTERFACE_DATA devInterfaceData = {};
    devInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);

    while(true)
    {
        BOOL bRet = SetupDiEnumDeviceInterfaces(hDevInfo, nullptr, guid, dwIndex, &devInterfaceData);
        if(!bRet)
            break;

        SP_DEVICE_INTERFACE_DATA spdid;
        DWORD dwSize;

        spdid.cbSize = sizeof(spdid);
        SetupDiEnumInterfaceDevice(hDevInfo, nullptr, guid, dwIndex, &spdid);
        SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, nullptr, 0, &dwSize, nullptr);

        BYTE buf[MAX_BUF_SIZE];
        PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)buf;
        if(dwSize != 0 && dwSize <= sizeof(buf)) {
            pspdidd->cbSize = sizeof(*pspdidd);

            SP_DEVINFO_DATA spdd;

            ZeroMemory((PVOID)&spdd, sizeof(spdd));
            spdd.cbSize = sizeof(spdd);
            long res = SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, pspdidd, dwSize, &dwSize, &spdd);
            if(res)
            {
                if(GetDiskNumber(QString::fromStdWString(pspdidd->DevicePath), true) == diskNumber)
                {
                        SetupDiDestroyDeviceInfoList(hDevInfo);
                        return spdd.DevInst;
                }
            }
        }
        dwIndex++;
    }
    SetupDiDestroyDeviceInfoList(hDevInfo);
    return 0;
}

该函数流程如下:

  • 通过SetupDiGetClassDevs获取设备信息hDevInfo
  • 通过SetupDiEnumDeviceInterfaces和hDevInfo遍历磁盘设备
  • 通过SetupDiGetDeviceInterfaceDetail获取设备信息pspdidd和spdd
  • 通过GetDiskNumber和pspdidd->DevicePath获取设备号并与传入的diskNumber比较.
  • 如果匹配到返回spdd.DevInst
2.2.4 IsRemovable
bool IsRemovable(DEVINST devInst)
{
    ULONG status = 0;
    ULONG problemNumber = 0;
    long res = CM_Get_DevNode_Status(&status, &problemNumber, devInst, 0);
    return (res == CR_SUCCESS && (status & DN_REMOVABLE) != 0);    
}

该函通过CM_Get_DevNode_Status和设备ID返回设备是否是可移出的

2.2.5 RemoveDisk
#define DELAY_TIME 200 //ms
bool RemoveDisk(long diskNumber, long driverType, int tryCount = 3)
{
    if(diskNumber < 0)
        return false;

    DEVINST devInst = GetDrivesDevInstByDiskNumber(diskNumber, driverType);
    if(!devInst)
        return false;
    long res  = CM_Get_Parent(&devInst, devInst, 0);// disk's parent, e.g the usb device, sata port
    if(res != CR_SUCCESS)
        return false;

    bool isRemovable = IsRemovable(devInst);
    WCHAR vetoName[MAX_PATH];
    PNP_VETO_TYPE vetoType = PNP_VetoTypeUnknown;
    for(int i = 0; i < tryCount; i++)
    {
        vetoName[0] = 0;
        if(isRemovable)
            res = CM_Request_Device_Eject(devInst, &vetoType, vetoName, sizeof(vetoName), 0);
        else
            res = CM_Query_And_Remove_SubTree(devInst, &vetoType, vetoName, sizeof(vetoName), 0);
        bool bSuccess = (res == CR_SUCCESS && vetoType == PNP_VetoTypeUnknown);
        if(bSuccess)
            return true;
        Sleep(DELAY_TIME);
    }
    return false;
}

该函数流程如下:

  • 根据diskNumber和diskNumber返回设备号devInst
  • 返回devInst设备的父设备号,这里设备号是磁盘逻辑设备号,其父设备号是USB或SATA物理设备,弹出设备需要物理设备号。
  • 最后调用CM_Request_Device_Eject弹出设备,默认重试3次。

2.2.6 EjectDisk

bool  EjectDisk(QString const& dirverPath)
{
    long dirverType(GetDriverType(dirverPath));
    long diskNumber(GetDiskNumber(dirverPath));
    return RemoveDisk(diskNumber, dirverType);
}

3 使用

EjectDisk("F:");

4 总结

函数EjectDisk是阻塞调用,如果U盘正在占用情况下该函数调用花费时间比较长,解决方案是将EjectDisk放入到线程中调用,后续文章Qt线程另一种使用方式会讲述如何将阻塞调用放入线程中。

  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

flysnow010

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值