【发布时间】: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