65.9K
CodeProject 正在变化。 阅读更多。
Home

枚举和自动检测 USB 驱动器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (39投票s)

2010 年 3 月 7 日

Ms-PL

6分钟阅读

viewsIcon

246981

downloadIcon

23254

本文介绍了如何使用 .NET System.Management WMI (Windows Management Instrumentation) 包装器来枚举和描述 USB 磁盘驱动器。它还包含一个非互操作解决方案,用于检测驱动器在线或离线时的状态更改。

UsbManager_Demo

引言

本文介绍了如何使用 .NET System.Management WMI (Windows Management Instrumentation) 包装器来枚举和描述 USB 磁盘驱动器。它还包含一个非互操作解决方案,用于检测驱动器在线或离线时的状态更改。

目录

背景

在编写 iTuner 时,我需要能够枚举 USB 磁盘驱动器(MP3 播放器),并允许用户选择一个 iTuner 可以同步 iTunes 播放列表的设备。为了选择正确的设备,用户需要除了驱动器盘符之外的一些基本识别信息,例如卷名称、制造商型号名称和可用磁盘空间。作为一项附加功能,我希望 UI 能够随着 USB 驱动器的在线或离线自动更新。最后,我希望通过一个整洁、简单的界面来实现这一点,而不会给主应用程序增加巨大的复杂性。

注意: UsbManager.GetAvailableDisks 方法可以在任何应用程序类型中运行。但是,除非在 WPF 或 Windows Forms 应用程序的上下文中运行,否则 StateChanged 事件将不会被触发。这是因为事件处理程序依赖于 Windows 消息处理来拦截这些状态更改,如下所述。

Using the Code

作为应用程序开发人员和此 API 的使用者,您首先需要 UsbManager 类的实例。此类公开了一个非常简单的接口

class UsbManager : IDisposable
{
    public UsbManager ();
    public event UsbStateChangedEventHandler StateChanged;
    public UsbDiskCollection GetAvailableDisks ();
}

class UsbDiskCollection : ObservableCollection<UsbDisk>
{
    public bool Contains (string name);
    public bool Remove (string name);
}

class UsbDisk
{
    public ulong FreeSpace { get; }
    public string Model { get; }
    public string Name { get; }
    public ulong Size { get; }
    public string Volume { get; }
    public string ToString ();
}
  1. 使用其无参构造函数实例化一个新的 UsbManager
  2. 连接到 StateChanged 事件的事件处理程序。
  3. 如果需要,调用 GetAvailableDisks 来检索当前 USB 磁盘列表。

UsbDisk 类封装了与特定 USB 磁盘相关的信息。它仅包含普通最终用户可能用于区分驱动器的最易识别的字段,例如驱动器盘符(Name)、卷名称和制造商型号名称。它还指定了可用剩余空间和总磁盘大小,两者都以字节为单位。虽然序列号、分区或扇区数据等其他信息可能对开发人员感兴趣,但对最终用户来说非常晦涩。

就是这样!祝您编码愉快!好的,如果您想了解它是如何组合在一起的,请继续阅读。

代码内部

开发此 API 首先是探索 WMI 类及其关系的实践。不幸的是,WMI 没有一个包含我们所需所有属性的单一 WMI_USBDiskDrive 类。但信息是存在的。我首先使用了 KS-Soft 的 WMI Explorer 工具。它是免费的,可以在他们的网站上下载。

WMI 类概述

最吸引您注意的第一个 WMI 类是 Win32_DiskDrive。驱动器以创意且显而易见的名字列出,如“\\PHYSICALDRIVE0”。我们可以通过仅查看 InterfaceType 属性值为“USB”的驱动器来过滤这些驱动器。Win32_DiskDrive 还指定了驱动器的 Model 和 Size。还有许多其他属性,但在本例中没有一个非常有趣。这是检索 USB 驱动器设备 ID 和型号名称的 WMI 查询

select DeviceID, Model from Win32_DiskDrive where InterfaceType='USB'

我的下一站是 Win32_LogicalDisk 类。这从一开始就变得有趣,因为实例以驱动器盘符列出,如“C:”、“D:”和“S:”。我们还可以获取 FreeSpace VolumeName 属性。这是 WMI 查询

select FreeSpace, Size, VolumeName from Win32_LogicalDisk where Name='S:'

我们现在需要一种方法来关联 Win32_DiskDrive Win32_LogicalDisk ,以便将这些信息合并在一起。您可能认为会有一个共享字段允许您连接这两个类。可惜没有。这正是 Web 派上用场的地方,我在 MSDN 上发现了一些代码,演示了如何关联这些类。我们可以使用 associators 操作符来发现各种类之间的关联。给定一个 Win32_DiskDrive 实例,我们可以使用其 DeviceID 属性通过 Win32_DiskDriveToDiskPartition 确定关联的 Win32_DiskPartition 实例

associators of {Win32_DiskDrive.DeviceID='\\PHYSICALDRIVE1'}
      where AssocClass = Win32_DiskDriveToDiskPartition

然后,使用 Win32_DiskPartition.DeviceID,我们可以通过 Win32_LogicalDiskToPartition 确定关联的 Win32_LogicalDisk 实例

associators of {Win32_DiskPartition.DeviceID='Disk #0, Partition #1'}
      where AssocClass = Win32_LogicalDiskToPartition

使用 System.Management 实现 WMI 查询

要执行 WMI 查询,我们可以使用 System.Management.ManagementObjectSearcher 类。该类始终遵循相同的模式:搜索、获取、枚举,如下所示

ManagementObjectSearcher searcher = 
    new ManagementObjectSearcher("select * from Win32_DiskDrive");

ManagementObjectCollection items = searcher.Get();

foreach (ManagementObject item in items)
{
}

鉴于查询四个 WMI 类所需的级联调用,我们将得到一个相当难看的 foreach 循环嵌套。为了清理它并使逻辑更清晰,我为 ManagementObjectSearcher 类创建了一个简单的扩展方法。此扩展为 ManagementObjectSearcher 类添加了一个 First() 方法,该方法调用其 Get 方法,枚举结果集合并立即返回该集合中的第一个项

public static ManagementObject First (this ManagementObjectSearcher searcher)
{
    ManagementObject result = null;
    foreach (ManagementObject item in searcher.Get())
    {
        result = item;
        break;
    }
    return result;
}

将此辅助扩展与上面的 WMI 查询结合起来,我们会在 UsbManager.GetAvailableDisks() 中得到一个简单的代码。是的,我们仍然有一个嵌套结构,但与替代方法相比,测试 null 要清晰得多!

public UsbDiskCollection GetAvailableDisks ()
{
    UsbDiskCollection disks = new UsbDiskCollection();

    // browse all USB WMI physical disks
    foreach (ManagementObject drive in
        new ManagementObjectSearcher(
            "select DeviceID, Model from Win32_DiskDrive " +
             "where InterfaceType='USB'").Get())
    {
        // associate physical disks with partitions
        ManagementObject partition = new ManagementObjectSearcher(String.Format(
            "associators of {{Win32_DiskDrive.DeviceID='{0}'}} " +
                  "where AssocClass = Win32_DiskDriveToDiskPartition",
            drive["DeviceID"])).First();

        if (partition != null)
        {
            // associate partitions with logical disks (drive letter volumes)
            ManagementObject logical = new ManagementObjectSearcher(String.Format(
                "associators of {{Win32_DiskPartition.DeviceID='{0}'}} " + 
                    "where AssocClass= Win32_LogicalDiskToPartition",
                partition["DeviceID"])).First();

            if (logical != null)
            {
                // finally find the logical disk entry
                ManagementObject volume = new ManagementObjectSearcher(String.Format(
                    "select FreeSpace, Size, VolumeName from Win32_LogicalDisk " +
                     "where Name='{0}'",
                    logical["Name"])).First();

                UsbDisk disk = new UsbDisk(logical["Name"].ToString());
                disk.Model = drive["Model"].ToString();
                disk.Volume = volume["VolumeName"].ToString();
                disk.FreeSpace = (ulong)volume["FreeSpace"];
                disk.Size = (ulong)volume["Size"];

                disks.Add(disk);
            }
        }
    }

    return disks;
}

拦截驱动器状态更改

现在我们能够枚举当前可用的 USB 磁盘驱动器,了解其中一个何时离线或新驱动器何时上线将会很方便。这就是 UsbManager.DriverWindow 类的目的。

DriverWindow 类继承自 System.Windows.Forms.NativeWindow ,并且是 UsbManager 封装的一个 private 类。NativeWindow WndProc 方法提供了一个方便的位置来拦截和处理 Windows 消息。我们需要 Windows 消息是 WM_DEVICECHANGE,其 LParam 值必须是 DBT_DEVTYP_VOLUMEWParam 值也很重要,我们查找两个 DBT 值(以及一个可选的第三个)。

  • DBT_DEVICEARRIVAL - 当设备或媒体已插入并可用时广播
  • DBT_DEVICEREMOVECOMPLETE - 当设备或媒体已物理移除时广播
  • DBT_DEVICEQUERYREMOVE - 广播以请求移除设备或媒体的权限;我们不处理此消息,但它提供了拒绝移除设备的可能

DBT_DEVICEARRIVAL DBT_DEVICEREMOVECOMPLETE 都传递一个 DEV_BROADCAST_VOLUME 结构。这实际上是一个 DEV_BROADCAST_HDR,其 dbcv_devicetype 设置为 DBT_DEVTYP_VOLUME,因此我们知道可以将数据包强制转换为 DEV_BROADCAST_VOLUME

[StructLayout(LayoutKind.Sequential)]
public struct DEV_BROADCAST_VOLUME
{
    public int dbcv_size;       // size of the struct
    public int dbcv_devicetype; // DBT_DEVTYP_VOLUME
    public int dbcv_reserved;   // reserved; do not use
    public int dbcv_unitmask;   // Bit 0=A, bit 1=B, and so on (bitmask)
    public short dbcv_flags;    // DBTF_MEDIA=0x01, DBTF_NET=0x02 (bitmask)
}

dbcv_unitmask 字段是一个位掩码,其中前 26 个低位对应一个 Windows 驱动器盘符。显然,一个设备可能与多个驱动器盘符相关联,但我们只关心第一个可用的

DriverWindow UsbManager 触发自己的 StateChanged 事件。UsbManager 然后决定是否需要检索信息(对于新设备它会检索),然后触发自己的 StateChanged 事件来通知使用者。

StateChanged 事件

本文附带的演示应用程序仅用几行代码就展示了 UsbManager 的全部功能。它首先枚举所有现有的 USB 磁盘驱动器并在 TextBox 中显示它们。然后,它连接到 UsbManager.StateChanged 事件的处理程序。此事件定义如下

public event UsbStateChangedEventHandler StateChanged

查看 StateChanged 实现,您会注意到 add remove 语句已扩展。这允许我们在使用者正在收听时才实例化 DriverWindow 实例,并在所有使用者停止收听时将其释放。

您的处理程序必须声明为 UsbStateChangedEventHandler,如下所示

public delegate void UsbStateChangedEventHandler (UsbStateChangedEventArgs e);

UsbStateChangedEventArgs 声明为

public class UsbStateChangedEventArgs : EventArgs
{
    public UsbDisk Disk;
    public UsbStateChange State;
}
  • State 属性是一个 enum,指定 AddedRemovingRemoved 之一。
  • Disk 属性是一个 UsbDisk 实例。如果 State Added,则 Disk 的所有属性都应已填充。但是,如果 State Removing Removed,则仅填充 Name 属性,因为我们无法检测已不存在的设备属性。

结论

如果您觉得本文对您有帮助,并且喜欢 iTuner 应用程序,请考虑 捐赠以支持 iTuner 的持续改进,以及更多有用的文章。谢谢!

© . All rights reserved.