简体   繁体   English

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

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

TL;DR version of this question: WMI Win32_NetworkAdapter class contains information I need, but is too slow. TL;此问题的DR版本:WMI Win32_NetworkAdapter类包含我需要的信息,但速度太慢。 What's a faster method of getting information for the MACAddress, ConfigManagerErrorCode, and PNPDeviceID columns on Windows? 在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. 我需要检索连接的网络适配器的信息,以便我可以获得一个MAC地址来唯一标识本地Microsoft Windows计算机。 The WMI Win32_NetworkAdapter class seems to have the information I'm looking for. WMI Win32_NetworkAdapter类似乎有我正在寻找的信息。 The MACAddress, ConfigManagerErrorCode, and PNPDeviceID columns are the only ones I really need: MACAddress,ConfigManagerErrorCode和PNPDeviceID列是我真正需要的唯一列:

  • MACAddress: the MAC address (goal of this operation) MACAddress:MAC地址(此操作的目标)
  • ConfigManagerErrorCode: allows me to determine if the adapter is enabled and running or not. ConfigManagerErrorCode:允许我确定适配器是否已启用并正在运行。 (If it's disabled then I should use a MAC address previously cached by my app, if available). (如果它被禁用,那么我应该使用以前由我的应用程序缓存的MAC地址,如果可用的话)。
  • 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). PNPDeviceID:通过检查“PCI”的前缀(以及可能的其他接口,如果需要),我可以过滤掉非物理适配器,其中有几个在我的Windows 7盒子上(包括虚拟适配器,如VMware / VirtualBox) 。

My plan was to filter out non-physical devices using PNPDeviceID. 我的计划是使用PNPDeviceID过滤掉非物理设备。 Then I would use the MACAddress column on any remaining table entries (saving the address to a cache). 然后我将在任何剩余的表条目上使用MACAddress列(将地址保存到缓存)。 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. 当设备被禁用时(可能由非零的ConfigManagerErrorCode指示)并且MACAddress为空,我可以从我的缓存中使用先前看到的该设备的MACAddress。

You can see the contents of this table on my Windows 7 computer. 您可以在我的Windows 7计算机上看到此表的内容。 You can see there's tons of junk in there, but only one entry with a "PCI" PNPDeviceID. 你可以看到那里有大量的垃圾,但只有一个带有“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

(If I disable my physical adapter, then the MACAddress column goes to null, and ConfigManagerErrorCode changes to non-zero). (如果我禁用了我的物理适配器,则MACAddress列变为null,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. 在我的相对现代的基于Windows 7 Core i7的计算机上,对Win32_NetworkAdapter的任何查询都需要0.3秒。 So using this will add yet another 0.3 seconds to application startup (or worse), which I find unacceptable. 所以使用它会再增加0.3秒的应用程序启动(或更糟),我觉得这是不可接受的。 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. 这尤其是因为我无法想到为什么需要花这么长时间才能弄清楚本地计算机上的MAC地址和即插即用设备ID。

Searching for other methods to get a MAC address yielded the GetAdaptersInfo and the newer GetAdaptersAddresses functions. 搜索获取MAC地址的其他方法产生了GetAdaptersInfo和较新的GetAdaptersAddresses函数。 They don't have the 0.3 second penalty that WMI imposes. 他们没有WMI强加的0.3秒罚款。 These functions are the ones used by the .NET Framework's NetworkInterface class (as determined by examining .NET source code), and the "ipconfig" command line tool (as determined by using Dependency Walker ). 这些函数是.NET Framework的NetworkInterface类(通过检查.NET源代码确定)和“ipconfig”命令行工具(通过使用Dependency Walker确定)使用的函数。

I made a simple example in C# that lists all network adapters using the NetworkInterface class. 我在C#中做了一个简单的例子,列出了使用NetworkInterface类的所有网络适配器。 Unfortunately, using these APIs seems to have two shortcomings: 不幸的是,使用这些API似乎有两个缺点:

  • These APIs don't even list disabled network adapters to begin with. 这些API甚至不会列出禁用的网络适配器。 That means I can't look up the MAC address for a disabled adapter from my cache. 这意味着我无法从缓存中查找已禁用适配器的MAC地址。
  • I don't see how I can get the PNPDeviceID to filter out non-physical adapters. 我不知道如何让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的建议只使用return immediate and 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);
    }

I called this function 3 times and each time got about 0.36 seconds printed on the last line. 我调用了这个函数3次,每次在最后一行打印约0.36秒。 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#? 这不是太令人惊讶,作为如何在C#中进行只进,只读WMI查询的答案 seems to indicate that no change in performance will be observed unless there are a large number of records (eg hundreds to thousands), which is not the case with Win32_NetworkAdapter table. 似乎表明除非有大量记录(例如数百到数千),否则不会观察到性能变化,而Win32_NetworkAdapter表则不然。

EDIT 2: Multiple answers have been proposed to use SendARP from the IP helper API (this is the same API that has the GetAdaptersInfo function). 编辑2:已经提出了多个答案来使用来自IP帮助程序API的SendARP (这是具有GetAdaptersInfo函数的相同API)。 What advantages does this give over GetAdaptersInfo for finding the local MAC address? 这有什么优势可以让GetAdaptersInfo找到本地MAC地址? I cannot think of any - on the surface, GetAdaptersInfo seems to return a more thorough set of information than SendARP does for local adapters. 我无法想到 - 从表面上看,GetAdaptersInfo似乎返回比SendARP对本地适配器更完整的信息集。 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. SendARP不执行枚举:它假定您已经知道您想要MAC的适配器的IP地址。 I need to figure out what adapters exist on the system. 我需要弄清楚系统上存在哪些适配器。 Some issues this raises: 这引起了一些问题:

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

I wound up cutting WMI completely out of the equation and made a significant improvement while still getting the information I wanted. 我最终将WMI完全排除在外,并在获得我想要的信息的同时取得了显着的进步。 As noted with WMI, it was taking > 0.30 seconds to get results. 如WMI所述,获得结果需要> 0.30秒。 With my version, I can get the same information in about 0.01 seconds. 使用我的版本,我可以在大约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. 我使用了安装API,配置管理器API,然后直接在NDIS网络驱动程序上发出OID请求以获取MAC地址。 The setup API seems obnoxiously slow, especially when getting things like property values. 设置API看起来非常慢,特别是在获取属性值等内容时。 It's imperative to keep setup API calls at a minimum. 必须将设置API调用保持在最低限度。 (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). (通过查看在设备管理器中加载设备的“详细信息”选项卡所需的时间,您实际上可以看到它有多糟糕)。

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. 猜测为什么WMI这么慢:我注意到无论我查询哪个属性子集,WMI的Win32_NetworkAdapter总是花费相同的时间。 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. 看起来像WMI Win32_NetworkAdapter类程序员很懒,并没有优化他们的类只收集其他WMI类所做的请求信息。 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. 他们可能非常依赖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. 使用SetupDiGetClassDevs获取系统中存在的所有网络设备。
  2. I filter out all results that don't have an enumerator of "PCI" (use SetupDiGetDeviceRegistryProperty with SPDRP_ENUMERATOR_NAME to get the enumerator). 我筛选出所有没有“PCI”枚举器的结果(使用SetupDiGetDeviceRegistryProperty和SPDRP_ENUMERATOR_NAME获取枚举器)。
  3. For the remainder, I can use CM_Get_DevNode_Status to get the device status and error code. 对于其余部分,我可以使用CM_Get_DevNode_Status来获取设备状态和错误代码。 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). 如果设置DN_HAS_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. 因此,在这种情况下,我从我维护的缓存加载网卡的MAC地址。
  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. 父设备可以是可移动的,因此我也可以通过使用CM_Get_Parent和CM_Get_DevNode_Status递归检查设备树来查找父可移动设备。
  6. Any remaining devices are nonremovable network cards on the PCI bus. 任何剩余设备都是PCI总线上的不可移动网卡。
  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). 对于每个网络设备,我使用带有GUID_NDIS_LAN_CLASS GUID和DIGCF_DEVICEINTERFACE标志的SetupDiGetClassDevs来获取其接口(这仅在设备启用/没有问题时才有效)。
  8. Use IOCTL_NDIS_QUERY_GLOBAL_STATS with OID_802_3_PERMANENT_ADDRESS on the driver's interface to get the permanent MAC address. 在驱动程序的接口上使用IOCTL_NDIS_QUERY_GLOBAL_STATS和OID_802_3_PERMANENT_ADDRESS来获取永久MAC地址。 Save it in the cache, and then return. 将其保存在缓存中,然后返回。

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. 编辑:所有网卡都不支持IOCTL_NDIS_QUERY_GLOBAL_STATS。 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 有关其设备实例ID,请参阅如何可靠,快速地获取网卡的MAC地址

You should be able to get everything you need from the System.Net namespace. 您应该能够从System.Net命名空间获得所需的一切。 For example, the following sample is lifted from MSDN and does what you asked for in the original version of the question. 例如,以下示例从MSDN中解除,并在问题的原始版本中执行您要求的操作。 It displays the physical addresses of all interfaces on the local computer. 它显示本地计算机上所有接口的物理地址。

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();
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 NetAddresses在Win32_NetworkAdapter查询中始终为null - NetAddresses always null in Win32_NetworkAdapter query Visual Studio C#express中Win32_NetworkAdapter的引用命名空间是什么? - What is the reference namespace of Win32_NetworkAdapter in visual studio C# express? 使用WQL从WMI查询Win32_NTLogEvent类时,TimeGenerated属性是否基于计算机或GMT的本地时间? - When querying the Win32_NTLogEvent Class from WMI with WQL is the TimeGenerated property based on Local time of the computer or GMT? Win32_ComputerSystem WMI类的SystemType属性的值奇怪吗? - Strange values of SystemType property of Win32_ComputerSystem WMI class? WMI Win32_PageFileSetting - WMI Win32_PageFileSetting 使用WMI / Win32_NetworkAdapterConfiguration轮询C#中的IP地址更改 - Polling for IP address change in C# using WMI/Win32_NetworkAdapterConfiguration 使用Wmi win32_Process进行远程进程执行 - 获取进程的标准输出 - Remote Process Execution using Wmi win32_Process - Getting Stdout of Process 如何通过 WMI 从“Win32_ProcessStopTrace”类获取附加信息? - How to obtain additional info from the 'Win32_ProcessStopTrace' class through WMI? 为什么“Win32_CDROMDrive”类的“SerialNumber”属性在与WMI分开请求时返回null? - How come the “SerialNumber” property of the “Win32_CDROMDrive” class returns null when asking for it separately from WMI? 在Server 2008 SP2上对Win32_PageFileUsage类运行WMI查询时出现内存泄漏 - Memory leak while running WMI query to Win32_PageFileUsage class on Server 2008 SP2
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM