windows 快速替换 Win32_NetworkAdapter WMI 类以获取本地计算机的 MAC 地址

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7322253/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-15 18:01:07  来源:igfitidea点击:

Fast replacement for Win32_NetworkAdapter WMI class for getting MAC address of local computer

c#c++windowsnetworkingwmi

提问by James Johnston

TL;DR version of this question: WMI Win32_NetworkAdapterclass contains information I need, but is too slow. What's a faster method of getting information for the MACAddress, ConfigManagerErrorCode, and PNPDeviceID columns on Windows?

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

I need to retrieve information for attached network adapters so I can get a MAC address to uniquely identify the local Microsoft Windows computer. The WMI Win32_NetworkAdapterclass seems to have the information I'm looking for. The MACAddress, ConfigManagerErrorCode, and PNPDeviceID columns are the only ones I really need:

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

  • MACAddress: the MAC address (goal of this operation)
  • ConfigManagerErrorCode: allows me to determine if the adapter is enabled and running or not. (If it's disabled then I should use a MAC address previously cached by my app, if available).
  • PNPDeviceID: By checking for a prefix of "PCI" (and possibly other interfaces, if necessary) I can filter out non-physical adapters, of which there are several on my Windows 7 box (including virtual adapters, like for VMware / VirtualBox).
  • MACAddress:MAC地址(此操作的目标)
  • ConfigManagerErrorCode:允许我确定适配器是否已启用并正在运行。(如果它被禁用,那么我应该使用以前由我的应用程序缓存的 MAC 地址(如果可用)。
  • PNPDeviceID:通过检查前缀“PCI”(以及可能的其他接口,如有必要),我可以过滤掉非物理适配器,其中有几个在我的 Windows 7 机器上(包括虚拟适配器,例如 VMware / VirtualBox) .

My plan was to filter out non-physical devices using PNPDeviceID. Then I would use the MACAddress column on any remaining table entries (saving the address to a cache). When the device is disabled (as possibly indicated by a non-zero ConfigManagerErrorCode) and the MACAddress is null, I can use a previously-seen MACAddress for that device from my cache.

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

You can see the contents of this table on my Windows 7 computer. You can see there's tons of junk in there, but only one entry with a "PCI" PNPDeviceID.

您可以在我的 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
    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);
    }
00 [00000001] WAN Miniport (IKEv2) 0 ROOT\MS_AGILEVPNMINIPORT
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();
    }
}
00 [00000002] WAN Miniport (L2TP) 0 ROOT\MS_L2TPMINIPORT##代码##00 [00000003] WAN Miniport (PPTP) 0 ROOT\MS_PPTPMINIPORT##代码##00 [00000004] WAN Miniport (PPPOE) 0 ROOT\MS_PPPOEMINIPORT##代码##00 [00000005] WAN Miniport (IPv6) 0 ROOT\MS_NDISWANIPV6##代码##00 [00000006] WAN Miniport (Network Monitor) 0 ROOT\MS_NDISWANBH##代码##00 [00000007] Intel(R) 82567LM-2 Gigabit Network Connection 0 00:1C:C0:B0:C4:89 PCI\VEN_8086&DEV_10CC&SUBSYS_00008086&REV_00&33FD14CA&0&C8 [00000008] WAN Miniport (IP) 0 ROOT\MS_NDISWANIP##代码##00 [00000009] Microsoft ISATAP Adapter 0 ROOT\*ISATAP##代码##00 [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##代码##00 [00000012] VirtualBox Bridged Networking Driver Miniport 0 00:1C:C0:B0:C4:89 ROOT\SUN_VBOXNETFLTMP##代码##00 [00000013] VirtualBox Host-Only Ethernet Adapter 0 08:00:27:00:C4:A1 ROOT\NET##代码##00 [00000014] Microsoft ISATAP Adapter 0 ROOT\*ISATAP##代码##01 [00000015] VMware Virtual Ethernet Adapter for VMnet1 0 00:50:56:C0:00:01 ROOT\VMWARE##代码##00 [00000016] Microsoft ISATAP Adapter 0 ROOT\*ISATAP##代码##02 [00000017] VMware Virtual Ethernet Adapter for VMnet8 0 00:50:56:C0:00:08 ROOT\VMWARE##代码##01 [00000018] Microsoft ISATAP Adapter 0 ROOT\*ISATAP##代码##03

(If I disable my physical adapter, then the MACAddress column goes to null, and ConfigManagerErrorCode changes to non-zero).

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

Unfortunately, this class is simply too slow. Any query on Win32_NetworkAdapter consistently takes 0.3 seconds on my relatively modern Windows 7 Core i7-based computer. So using this will add yet another 0.3 seconds to application startup (or worse), which I find unacceptable. This is especially because I can't think of a single valid reason why it should take so long to figure out what MAC addresses and plug-and-play device IDs are on the local computer.

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

Searching for other methods to get a MAC address yielded the GetAdaptersInfoand the newer GetAdaptersAddressesfunctions. They don't have the 0.3 second penalty that WMI imposes. These functions are the ones used by the .NET Framework's NetworkInterfaceclass (as determined by examining .NET source code), and the "ipconfig" command line tool (as determined by using Dependency Walker).

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

I made a simple example in C# that lists all network adapters using the NetworkInterface class. Unfortunately, using these APIs seems to have two shortcomings:

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

  • These APIs don't even list disabled network adapters to begin with. That means I can't look up the MAC address for a disabled adapter from my cache.
  • I don't see how I can get the PNPDeviceID to filter out non-physical adapters.
  • 这些 API 甚至没有列出禁用的网络适配器。这意味着我无法从缓存中查找已禁用适配器的 MAC 地址。
  • 我不知道如何让 PNPDeviceID 过滤掉非物理适配器。

My question is: what method can I use to get the MAC address of the local computer's physical adapters (whether it is enabled or not) in a few tens of milliseconds at most?

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

(I'm experienced at both C# and C++ and am fine with reading other languages, so I really don't care what language might be used in answers).

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

EDIT:In response to Alex K's suggestion for using return immediate and forward only, and also to provide some sample WMI code for what I am doing - here is some C# code that lists the columns of interest:

编辑:为了回应 Alex K 的仅使用立即返回和转发的建议,并为我正在做的事情提供一些示例 WMI 代码 - 这里是一些列出感兴趣的列的 C# 代码:

##代码##

I called this function 3 times and each time got about 0.36 seconds printed on the last line. So the suggested flags don't seem to have any effect: positive or negative. This is not too surprising, as the answer at How to make forward-only, read-only WMI queries in C#?seems to indicate that no change in performance will be observed unless there are a large number of records (e.g. hundreds to thousands), which is not the case with Win32_NetworkAdapter table.

我调用了这个函数 3 次,每次都在最后一行打印了大约 0.36 秒。所以建议的标志似乎没有任何影响:正面或负面。这并不奇怪,正如如何在 C# 中进行前向、只读 WMI 查询的答案一样似乎表明除非有大量记录(例如数百到数千),否则不会观察到性能变化,而 Win32_NetworkAdapter 表并非如此。

EDIT 2:Multiple answers have been proposed to use SendARPfrom the IP helper API (this is the same API that has the GetAdaptersInfo function). What advantages does this give over GetAdaptersInfo for finding the local MAC address? I cannot think of any - on the surface, GetAdaptersInfo seems to return a more thorough set of information than SendARP does for local adapters. Now that I think about it, I think that a big part of my question centers over the concept of enumeration: what adapters exist on the computer in the first place? SendARP does not perform enumeration: it assumes you already know the IP address of the adapter you want a MAC for. I need to figure out what adapters exist on the system. Some issues this raises:

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

  • What happens if the network cable is unplugged? This would be very common on a laptop, for example (unplugged Ethernet, disconnected WiFi card). I tried using NetworkInterface.GetAllNetworkInterfaces() and listing all the unicast addresses using GetIPProperties().UnicastAddresseswhen the media is unplugged. No addresses are listed by Windows, so I can't think of any address that could be passed to SendARP. Intuitively, it makes sense that an unplugged adapter would still have a physical address, but no IP address (since it is not on a network with a DHCP server).
  • Which brings me to: how do I get a list of local IP addresses to test with SendARP?
  • How do I get the PNPDeviceID (or similar ID that can be used to filter non-physical adapters) for each adapter?
  • How do I list disabled adapters, so I can look up a MAC address from my cache (i.e. the MAC address I found when it was last enabled)?
  • 拔掉网线会怎样?例如,这在笔记本电脑上很常见(未插入以太网,断开 WiFi 卡)。我尝试使用 NetworkInterface.GetAllNetworkInterfaces() 并在拔出媒体时使用GetIPProperties().UnicastAddresses列出所有单播地址。Windows 没有列出地址,所以我想不出任何可以传递给 SendARP 的地址。直觉上,拔下的适配器仍然有物理地址,但没有 IP 地址是有道理的(因为它不在具有 DHCP 服务器的网络上)。
  • 这让我想到:如何获取本地 IP 地址列表以使用 SendARP 进行测试?
  • 如何获取每个适配器的 PNPDeviceID(或可用于过滤非物理适配器的类似 ID)?
  • 如何列出禁用的适配器,以便我可以从缓存中查找 MAC 地址(即上次启用时找到的 MAC 地址)?

These issues don't seem to be addressed by SendARP, and are the major reason I asked this question (otherwise I'd be using GetAdaptersInfo and moving on with things...).

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

回答by James Johnston

I wound up cutting WMI completely out of the equation and made a significant improvement while still getting the information I wanted. As noted with WMI, it was taking > 0.30 seconds to get results. With my version, I can get the same information in about 0.01 seconds.

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

I used the setup API, configuration manager API, and then made OID requests directly on the NDIS network driver to get the MAC address. The setup API seems obnoxiously slow, especially when getting things like property values. It's imperative to keep setup API calls at a minimum. (You can actually see just how bad it is by looking at how long it takes to load the "Details" tab of a device in Device manager).

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

A guess on why WMI was so slow: I noted that WMI's Win32_NetworkAdapter always took the same amount of time no matter which subset of properties I queried. Seems like the WMI Win32_NetworkAdapter class programmers were lazy and didn't optimize their class to only gather requested information like other WMI classes do. They probably gather all information, whether requested or not. They probably significantly rely on Setup API to do this, and the excessive calls to the slow Setup API to get unwanted information is what makes it so slow.

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

High level overview of what I did:

我所做的高级概述:

  1. Use SetupDiGetClassDevs to get all network devices that are present on the system.
  2. I filter out all results that don't have an enumerator of "PCI" (use SetupDiGetDeviceRegistryProperty with SPDRP_ENUMERATOR_NAME to get the enumerator).
  3. For the remainder, I can use CM_Get_DevNode_Status to get the device status and error code. All devices with removable device status codes are filtered out.
  4. If DN_HAS_PROBLEM is set such that there is a non-zero error code then the device is probably disabled (or has some other problem). The driver isn't loaded so we can't make a request to the driver. Therefore in this case I load the MAC address for the network card from a cache I maintain.
  5. A parent device could be removable so I filter those out too by recursively examining the device tree using CM_Get_Parent and CM_Get_DevNode_Status to look for parent removable devices.
  6. Any remaining devices are nonremovable network cards on the PCI bus.
  7. For each network device, I use SetupDiGetClassDevs with GUID_NDIS_LAN_CLASS GUID and DIGCF_DEVICEINTERFACE flag to get its interfaces (this only works if the device is enabled / does not have a problem).
  8. Use IOCTL_NDIS_QUERY_GLOBAL_STATS with OID_802_3_PERMANENT_ADDRESS on the driver's interface to get the permanent MAC address. Save it in the cache, and then return.
  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 地址。将其保存在缓存中,然后返回。

The result is a robust indication of MAC addresses on the PC that should be immune to "fake" network cards made by VMware, VirtualBox, largely immune to network cards that are temporarily disabled, and immune to transient network cards attached via USB, ExpressCard, PC Card, or any future removable interface.

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

EDIT:IOCTL_NDIS_QUERY_GLOBAL_STATS isn't supported by all network cards. The vast majority work, but some Intel cards do not. See How to reliably and quickly get the MAC address of a network card given its device instance ID

编辑:并非所有网卡都支持 IOCTL_NDIS_QUERY_GLOBAL_STATS。绝大多数工作,但一些英特尔卡不。请参阅如何在给定设备实例 ID 的情况下可靠且快速地获取网卡的 MAC 地址

回答by David Heffernan

You should be able to get everything you need from the System.Netnamespace. For example, the following sample is lifted from MSDNand does what you asked for in the original version of the question. It displays the physical addresses of all interfaces on the local computer.

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

##代码##