快速replaceWin32_NetworkAdapter WMI类获取本地计算机的MAC地址

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

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

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

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

你可以在我的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更改为非零)。

不幸的是,这个class级太慢了。 Win32_NetworkAdapter上的任何查询在我相对较新的基于Windows 7 Core i7的计算机上始终需要0.3秒。 所以使用这个将会给应用程序启动(或者更糟)增加0.3秒,我觉得这是不可接受的。 这尤其是因为我想不出一个有效的原因,为什么要花费这么长时间才能找出本地计算机上的MAC地址和即插即用设备ID。

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

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

  • 这些API甚至不会列出禁用的networking适配器。 这意味着我无法从caching中查找禁用的适配器的MAC地址。
  • 我看不到如何让PNPDeviceID过滤出非物理适配器。

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

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

编辑:在回应亚历克斯K的build议使用返回立即和转发,也提供一些示例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秒。 所以build议的标志似乎没有任何影响:积极或消极。 这并不奇怪,因为如何在C#中只进行只读WMI查询的答案? 似乎表明,除非有大量的logging(例如数百到数千),否则不会观察到性能变化,而Win32_NetworkAdapter表不是这种情况。

编辑2:已经提出了多个答案来使用从IP帮助器API的SendARP (这是具有GetAdaptersInfo函数相同的API)。 通过GetAdaptersInfofind本地MAC地址有什么好处? 我想不出任何 – 从表面上看,GetAdaptersInfo似乎比SendARP为本地适配器返回一组更完整的信息。 现在我想到了,我认为我的问题很大一部分是关于枚举的概念:首先是计算机上存在哪些适配器? SendARP不执行枚举:它假定您已经知道您想要MAC的适配器的IP地址。 我需要弄清楚系统上有什么适配器。 这提出了一些问题:

  • 如果拔下网线会发生什么? 例如,在笔记本电脑上这会非常普遍(拔掉以太网,断开连接的WiFi卡)。 我尝试使用NetworkInterface.GetAllNetworkInterfaces(),并列出所有的单播地址使用GetIPProperties()。单播地址当媒体被拔出。 没有地址列出的Windows,所以我想不出任何可以传递给SendARP的地址。 直观地说,拔下的适配器仍然有物理地址,但没有IP地址(因为它不在具有DHCP服务器的networking上)是有意义的。
  • 这使我感到:我如何获得本地IP地址列表以使用SendARP进行testing?
  • 如何获得每个适配器的PNPDeviceID(或可用于过滤非物理适配器的类似ID)?
  • 如何列出禁用的适配器,以便我可以从caching中查找MAC地址(即,在上次启用时find的MAC地址)?

这些问题似乎不是由SendARP解决,是我问这个问题的主要原因(否则我会使用GetAdaptersInfo和继续前进的东西…)。

Solutions Collecting From Web of "快速replaceWin32_NetworkAdapter WMI类获取本地计算机的MAC地址"

我完全排除了WMI,取得了显着的进步,同时仍然获得了我想要的信息。 正如WMI所指出的那样,要花费0.30秒才能获得结果。 用我的版本,我可以在大约0.01秒内得到相同的信息。

我使用了设置API,配置管理器API,然后直接在NDIS网络驱动程序上创建OID请求来获取MAC地址。 安装程序API看起来很慢,特别是在获取属性值之类的东西时。 保持设置API调用至少是必要的。 (通过查看在设备管理器中加载设备的“详细信息”选项卡需要多长时间,您实际上可以看到它有多糟糕)。

猜测WMI为什么这么慢:我注意到WMI的Win32_NetworkAdapter总是花费相同的时间量,不管我查询的属性是哪一个子集。 看起来像WMI Win32_NetworkAdapter类的程序员是懒惰的,并没有优化他们的类,只收集请求的信息,像其他的WMI类一样。 他们可能收集所有的信息,无论是否要求。 他们可能很大程度上依靠安装程序API来执行此操作,并且对慢速安装程序API的过度调用以获取不需要的信息是如此缓慢。

我做了什么高层次的概述:

  1. 使用SetupDiGetClassDevs获取系统上存在的所有网络设备。
  2. 我筛选出所有没有“PCI”枚举的结果(使用SetupDiGetDeviceRegistryProperty和SPDRP_ENUMERATOR_NAME来获取枚举数)。
  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卡或任何未来的可移动界面。

编辑:所有网卡不支持IOCTL_NDIS_QUERY_GLOBAL_STATS。 绝大多数工作,但一些英特尔卡没有。 请参阅如何根据设备实例ID可靠快速地获取网卡的MAC地址

您应该能够从System.Net命名空间获取所需的所有内容。 例如,下面的示例从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(); } }