【问题标题】:Fast replacement for Win32_NetworkAdapter WMI class for getting MAC address of local computer快速替换 Win32_NetworkAdapter WMI 类以获取本地计算机的 MAC 地址
【发布时间】:2011-09-06 15:21:14
【问题描述】:

TL;这个问题的DR版本:WMIWin32_NetworkAdapter类包含我需要的信息,但是太慢了。在 Windows 上获取 MACAddress、ConfigManagerErrorCode 和 PNPDeviceID 列信息的更快方法是什么?

我需要检索连接的网络适配器的信息,以便获取 MAC 地址来唯一标识本地 Microsoft Windows 计算机。 WMI Win32_NetworkAdapter 类似乎有我正在寻找的信息。 MACAddress、ConfigManagerErrorCode 和 PNPDeviceID 列是我真正需要的唯一列:

  • MACAddress:MAC 地址(此操作的目标)
  • ConfigManagerErrorCode:允许我确定适配器是否已启用和运行。 (如果它被禁用,那么我应该使用以前由我的应用缓存的 MAC 地址,如果可用)。
  • PNPDeviceID:通过检查前缀“PCI”(可能还有其他接口,如有必要),我可以过滤掉非物理适配器,其中有几个在我的 Windows 7 机器上(包括虚拟适配器,如 VMware /VirtualBox)。

我的计划是使用 PNPDeviceID 过滤掉非物理设备。然后我会在任何剩余的表条目上使用 MACAddress 列(将地址保存到缓存中)。当设备被禁用(可能由非零 ConfigManagerErrorCode 指示)并且 MACAddress 为空时,我可以从我的缓存中为该设备使用以前看到的 MACAddress。

您可以在我的 Windows 7 计算机上看到此表的内容。您可以看到那里有大量垃圾,但只有一个条目带有“PCI”PNPDeviceID。

wmic:root\cli>NIC GET Caption, ConfigManagerErrorCode, MACAddress, PNPDeviceID
Caption                                                   ConfigManagerErrorCode  MACAddress         PNPDeviceID
[00000000] WAN Miniport (SSTP)                            0                                          ROOT\MS_SSTPMINIPORT\0000
[00000001] WAN Miniport (IKEv2)                           0                                          ROOT\MS_AGILEVPNMINIPORT\0000
[00000002] WAN Miniport (L2TP)                            0                                          ROOT\MS_L2TPMINIPORT\0000
[00000003] WAN Miniport (PPTP)                            0                                          ROOT\MS_PPTPMINIPORT\0000
[00000004] WAN Miniport (PPPOE)                           0                                          ROOT\MS_PPPOEMINIPORT\0000
[00000005] WAN Miniport (IPv6)                            0                                          ROOT\MS_NDISWANIPV6\0000
[00000006] WAN Miniport (Network Monitor)                 0                                          ROOT\MS_NDISWANBH\0000
[00000007] Intel(R) 82567LM-2 Gigabit Network Connection  0                       00:1C:C0:B0:C4:89  PCI\VEN_8086&DEV_10CC&SUBSYS_00008086&REV_00\3&33FD14CA&0&C8
[00000008] WAN Miniport (IP)                              0                                          ROOT\MS_NDISWANIP\0000
[00000009] Microsoft ISATAP Adapter                       0                                          ROOT\*ISATAP\0000
[00000010] RAS Async Adapter                              0                       20:41:53:59:4E:FF  SW\{EEAB7790-C514-11D1-B42B-00805FC1270E}\ASYNCMAC
[00000011] Microsoft Teredo Tunneling Adapter             0                                          ROOT\*TEREDO\0000
[00000012] VirtualBox Bridged Networking Driver Miniport  0                       00:1C:C0:B0:C4:89  ROOT\SUN_VBOXNETFLTMP\0000
[00000013] VirtualBox Host-Only Ethernet Adapter          0                       08:00:27:00:C4:A1  ROOT\NET\0000
[00000014] Microsoft ISATAP Adapter                       0                                          ROOT\*ISATAP\0001
[00000015] VMware Virtual Ethernet Adapter for VMnet1     0                       00:50:56:C0:00:01  ROOT\VMWARE\0000
[00000016] Microsoft ISATAP Adapter                       0                                          ROOT\*ISATAP\0002
[00000017] VMware Virtual Ethernet Adapter for VMnet8     0                       00:50:56:C0:00:08  ROOT\VMWARE\0001
[00000018] Microsoft ISATAP Adapter                       0                                          ROOT\*ISATAP\0003

(如果我禁用我的物理适配器,则 MACAddress 列变为空,并且 ConfigManagerErrorCode 变为非零)。

不幸的是,这门课太慢了。在我相对现代的基于 Windows 7 Core i7 的计算机上,对 Win32_NetworkAdapter 的任何查询始终需要 0.3 秒。因此,使用它会再增加 0.3 秒的应用程序启动时间(或更糟),我认为这是不可接受的。这尤其是因为我想不出一个正当的理由为什么要花这么长时间才能弄清楚本地计算机上的 MAC 地址和即插即用设备 ID。

搜索获取 MAC 地址的其他方法产生了 GetAdaptersInfo 和更新的 GetAdaptersAddresses 函数。他们没有 WMI 施加的 0.3 秒惩罚。这些函数由 .NET Framework 的 NetworkInterface 类(通过检查 .NET 源代码确定)和“ipconfig”命令行工具(通过使用Dependency Walker 确定)使用。

我用 C# 做了一个简单的例子,列出了所有使用 NetworkInterface 类的网络适配器。不幸的是,使用这些 API 似乎有两个缺点:

  • 这些 API 甚至一开始都没有列出禁用的网络适配器。这意味着我无法从缓存中查找已禁用适配器的 MAC 地址。
  • 我不知道如何获取 PNPDeviceID 以过滤掉非物理适配器。

我的问题是:我可以用什么方法在最多几十毫秒的时间内获取本地计算机物理适配器的MAC地址(无论是否启用)?

(我在 C# 和 C++ 方面都有经验,并且阅读其他语言也很好,所以我真的不在乎答案中可能使用哪种语言)。

编辑:响应 Alex K 的建议,即仅使用 return immediate 和 forward,并为我正在做的事情提供一些示例 WMI 代码 - 这是一些列出的列的 C# 代码兴趣:

    public static void NetTest() {
        System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();
        EnumerationOptions opt = new EnumerationOptions();
        // WMI flag suggestions from Alex K:
        opt.ReturnImmediately = true;
        opt.Rewindable = false;
        ManagementObjectSearcher searcher = new ManagementObjectSearcher("root\\cimv2", "select MACAddress, PNPDeviceID, ConfigManagerErrorCode from Win32_NetworkAdapter", opt);
        foreach (ManagementObject obj in searcher.Get()) {
            Console.WriteLine("=========================================");
            foreach (PropertyData pd in obj.Properties) {
                Console.WriteLine("{0} = {1}", pd.Name, pd.Value);
            }
        }
        Console.WriteLine(sw.Elapsed.TotalSeconds);
    }

我调用了这个函数 3 次,每次在最后一行打印大约 0.36 秒。所以建议的标志似乎没有任何效果:正面或负面。这并不奇怪,因为How to make forward-only, read-only WMI queries in C#? 的答案似乎表明除非有大量记录(例如数百到数千),否则不会观察到性能变化,而 Win32_NetworkAdapter 表则不是这种情况。

编辑 2: 已提出多个答案以使用 IP 帮助程序 API 中的 SendARP(这是具有 GetAdaptersInfo 函数的同一 API)。在查找本地 MAC 地址方面,这比 GetAdaptersInfo 有什么优势?我想不出任何 - 从表面上看,GetAdaptersInfo 似乎返回的信息集比 SendARP 为本地适配器所做的更全面。现在我考虑一下,我认为我的问题的很大一部分集中在枚举的概念上:计算机上首先存在哪些适配器? SendARP 不执行枚举:它假定您已经知道您想要 MAC 的适配器的 IP 地址。我需要弄清楚系统上存在哪些适配器。这引发了一些问题:

  • 如果拔下网线会怎样?这在笔记本电脑上很常见,例如(未插入的以太网、未连接的 WiFi 卡)。我尝试使用 NetworkInterface.GetAllNetworkInterfaces() 并在拔下媒体时使用 GetIPProperties().UnicastAddresses 列出所有单播地址。 Windows 没有列出任何地址,所以我想不出任何可以传递给 SendARP 的地址。直观地说,拔下的适配器仍然有物理地址,但没有 IP 地址(因为它不在具有 DHCP 服务器的网络上),这是有道理的。
  • 这让我想到:如何获取本地 IP 地址列表以使用 SendARP 进行测试?
  • 如何获取每个适配器的 PNPDeviceID(或可用于过滤非物理适配器的类似 ID)?
  • 如何列出禁用的适配器,以便从缓存中查找 MAC 地址(即上次启用时找到的 MAC 地址)?

SendARP 似乎没有解决这些问题,这也是我提出这个问题的主要原因(否则我将使用 GetAdaptersInfo 并继续处理......)。

【问题讨论】:

  • 不是一个真正的答案,但您是否尝试过使用 WBEM_FLAG_RETURN_IMMEDIATE | 的性能? ExecQuery() 中的 WBEM_FLAG_FORWARD_ONLY 标志?

标签: c# c++ windows networking wmi


【解决方案1】:

我最终将 WMI 完全排除在外,并在获得我想要的信息的同时取得了显着的改进。正如 WMI 所指出的,获得结果需要 > 0.30 秒。使用我的版本,我可以在大约 0.01 秒内获得相同的信息。

我使用了设置 API、配置管理器 API,然后直接在 NDIS 网络驱动程序上发出 OID 请求以获取 MAC 地址。设置 API 看起来慢得令人讨厌,尤其是在获取属性值之类的东西时。必须将设置 API 调用保持在最低限度。 (您实际上可以通过查看在设备管理器中加载设备的“详细信息”选项卡需要多长时间来了解它有多糟糕)。

关于 WMI 为何如此缓慢的猜测:我注意到无论我查询哪个属性子集,WMI 的 Win32_NetworkAdapter 总是花费相同的时间。似乎 WMI Win32_NetworkAdapter 类的程序员很懒惰,没有像其他 WMI 类那样优化他们的类来只收集请求的信息。他们可能会收集所有信息,无论是否请求。他们可能非常依赖 Setup API 来执行此操作,而过度调用缓慢的 Setup API 以获取不需要的信息是导致它如此缓慢的原因。

对我所做工作的高级概述:

  1. 使用 SetupDiGetClassDevs 获取系统上存在的所有网络设备。
  2. 我过滤掉所有没有“PCI”枚举器的结果(使用带有 SPDRP_ENUMERATOR_NAME 的 SetupDiGetDeviceRegistryProperty 来获取枚举器)。
  3. 对于其余部分,我可以使用 CM_Get_DevNode_Status 获取设备状态和错误代码。所有具有可移动设备状态代码的设备都会被过滤掉。
  4. 如果 DN_HAS_PROBLEM 设置为存在非零错误代码,则设备可能已禁用(或存在其他问题)。驱动程序未加载,因此我们无法向驱动程序发出请求。因此,在这种情况下,我会从我维护的缓存中加载网卡的 MAC 地址。
  5. 父设备可能是可移动的,因此我也通过使用 CM_Get_Parent 和 CM_Get_DevNode_Status 递归检查设备树来查找父可移动设备来过滤掉这些设备。
  6. 任何剩余的设备都是 PCI 总线上不可移动的网卡。
  7. 对于每个网络设备,我使用带有 GUID_NDIS_LAN_CLASS GUID 和 DIGCF_DEVICEINTERFACE 标志的 SetupDiGetClassDevs 来获取其接口(这仅在设备启用/没有问题时才有效)。
  8. 在驱动程序接口上使用 IOCTL_NDIS_QUERY_GLOBAL_STATS 和 OID_802_3_PERMANENT_ADDRESS 来获取永久 MAC 地址。将其保存在缓存中,然后返回。

结果是 PC 上的 MAC 地址的可靠指示应该不受 VMware、VirtualBox 制造的“假”网卡的影响,很大程度上不受暂时禁用的网卡的影响,并且不受通过 USB 连接的临时网卡的影响、ExpressCard、PC Card 或任何未来的可移动接口。

编辑: 并非所有网卡都支持 IOCTL_NDIS_QUERY_GLOBAL_STATS。绝大多数工作,但一些英特尔卡没有。见How to reliably and quickly get the MAC address of a network card given its device instance ID

【讨论】:

  • 这很有用,除了我认为您省略了 DIGCF_DEVICEINTERFACE 步骤和 IOCTL_NDIS_QUERY_GLOBAL_STATS 步骤之间的一些细节,我收集的是一个 DeviceIoControl 调用,它需要一个需要设备名称的 CreateFile 句柄(msdn 建议某种形式"\\.\AdapterServiceName") 你是说 DIGCF_DEVICEINTERFACE 会给你吗?
【解决方案2】:

您应该能够从 System.Net 命名空间中获得所需的一切。例如,以下sample is lifted from MSDN 并执行您在问题的原始版本中要求的内容。它显示本地计算机上所有接口的物理地址。

public static void ShowNetworkInterfaces()
{
    IPGlobalProperties computerProperties = IPGlobalProperties.GetIPGlobalProperties();
    NetworkInterface[] nics = NetworkInterface.GetAllNetworkInterfaces();
    Console.WriteLine("Interface information for {0}.{1}     ",
            computerProperties.HostName, computerProperties.DomainName);
    if (nics == null || nics.Length < 1)
    {
        Console.WriteLine("  No network interfaces found.");
        return;
    }

    Console.WriteLine("  Number of interfaces .................... : {0}", nics.Length);
    foreach (NetworkInterface adapter in nics)
    {
        IPInterfaceProperties properties = adapter.GetIPProperties(); //  .GetIPInterfaceProperties();
        Console.WriteLine();
        Console.WriteLine(adapter.Description);
        Console.WriteLine(String.Empty.PadLeft(adapter.Description.Length,'='));
        Console.WriteLine("  Interface type .......................... : {0}", adapter.NetworkInterfaceType);
        Console.Write("  Physical address ........................ : ");
        PhysicalAddress address = adapter.GetPhysicalAddress();
        byte[] bytes = address.GetAddressBytes();
        for(int i = 0; i< bytes.Length; i++)
        {
            // Display the physical address in hexadecimal.
            Console.Write("{0}", bytes[i].ToString("X2"));
            // Insert a hyphen after each byte, unless we are at the end of the 
            // address.
            if (i != bytes.Length -1)
            {
                 Console.Write("-");
            }
        }
        Console.WriteLine();
    }
}

【讨论】:

  • 查看我的最新编辑...我有几个关于如何使用 SendArp 的问题。
  • 如果您想问其他问题,请这样做。请不要只是继续扩展这个我相信我已经回答过的问题。不断提出越来越多的问题是不公平的。
  • 如果我没有完全正确地遵循程序,我很抱歉 - 我在这里还是比较新的。我认为编辑我的问题会更容易,而不是尝试在狭窄的 cmets 部分回复每个人(这实际上不适用于代码示例等)。我应该怎么做?问题是,如果我问另一个问题,除了复制/粘贴这个问题之外,我想不出要问什么:我不明白 SendARP 如何帮助回答我最初关于如何列出网络适配器(包括禁用)的问题, PNP 设备 ID(和 MAC 地址,如果可用)。
  • @James:SendARP 实际上没有帮助你,因为你不知道 IP 地址。大卫的回答应该让你在正确的轨道上解决你的问题。
  • @Niklas:这就是我的想法——在某些情况下甚至可能没有 IP(例如,网线被拔掉)。 WMI 给我的是枚举; SendARP 不像 Win32_NetworkAdapter WMI 表那样枚举网卡 - 它似乎不是一个完整的替代品,而是一个小的构建块。
猜你喜欢
  • 2013-03-24
  • 2010-09-12
  • 1970-01-01
  • 2011-06-14
  • 2011-09-04
  • 1970-01-01
  • 2019-04-26
  • 1970-01-01
  • 2010-09-08
相关资源
最近更新 更多