草庐IT

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

coder 2023-11-11 原文

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

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

  • MACAddress:MAC地址(此操作的目标)
  • ConfigManagerErrorCode:允许我确定适配器是否已启用并正在运行。 (如果已禁用,则应使用我的应用先前缓存的MAC地址(如果有))。
  • PNPDeviceID:通过检查“PCI”(如果需要,可能还有其他接口(interface))的前缀,我可以过滤掉非物理适配器,在我的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列将变为null,并且ConfigManagerErrorCode更改为非零)。

    不幸的是,这个类太慢了。在相对较新的基于Windows 7 Core i7的计算机上,Win32_NetworkAdapter上的任何查询始终需要0.3秒。因此,使用此方法将使应用程序启动又增加0.3秒(或更糟的时间),我认为这是 Not Acceptable 。尤其是因为我想不出一个单一的正当理由,所以弄清楚本地计算机上的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关于仅使用立即返回和正向返回的建议,并提供一些我正在做的示例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相同)。与GetAdaptersInfo相比,这对于查找本地MAC地址有什么优势?我想不出什么-从表面上看,GetAdaptersInfo似乎返回了比SendARP对本地适配器更彻底的信息集。现在我考虑了一下,我认为我的问题很大一部分集中在枚举的概念上:计算机上首先存在哪些适配器? SendARP不执行枚举:它假定您已经知道您想要MAC的适配器的IP地址。我需要弄清楚系统上存在哪些适配器。这引起了一些问题:
  • 如果拔下网络电缆会怎样?例如,这在笔记本电脑上会很常见(拔掉以太网,断开WiFi卡)。我尝试使用NetworkInterface.GetAllNetworkInterfaces()并在拔出媒体时使用GetIPProperties().UnicastAddresses列出了所有单播地址。 Windows没有列出地址,因此我无法想到可以传递给SendARP的任何地址。直观地讲,拔出的适配器仍将具有物理地址,但没有IP地址(因为它不在具有DHCP服务器的网络上),这是有道理的。
  • 这使我进入:如何获取要使用SendARP测试的本地IP地址列表?
  • 如何获取每个适配器的PNPDeviceID(或类似的ID,可用于过滤非物理适配器)?
  • 如何列出禁用的适配器,以便可以从缓存中查找MAC地址(即上次启用该地址时找到的MAC地址)?

  • 这些问题似乎无法由SendARP解决,这是我提出此问题的主要原因(否则,我将使用GetAdaptersInfo并继续进行操作...)。

    最佳答案

    我完全放弃了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的过多调用以获取不需要的信息是使它如此缓慢的原因。

    我所做工作的高级概述:

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

  • 结果是可以可靠地指示PC上的MAC地址,这些MAC地址应不受VMware,VirtualBox制造的“假”网卡的影响,而不受临时禁用的网卡的影响,以及不受USB,ExpressCard, PC卡或任何将来的可移动接口(interface)。

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

    关于c# - 快速替换Win32_NetworkAdapter WMI类以获取本地计算机的MAC地址,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7322253/

    有关c# - 快速替换Win32_NetworkAdapter WMI类以获取本地计算机的MAC地址的更多相关文章

    1. ruby 正则表达式 - 如何替换字符串中匹配项的第 n 个实例 - 2

      在我的应用程序中,我需要能够找到所有数字子字符串,然后扫描每个子字符串,找到第一个匹配范围(例如5到15之间)的子字符串,并将该实例替换为另一个字符串“X”。我的测试字符串s="1foo100bar10gee1"我的初始模式是1个或多个数字的任何字符串,例如,re=Regexp.new(/\d+/)matches=s.scan(re)给出["1","100","10","1"]如果我想用“X”替换第N个匹配项,并且只替换第N个匹配项,我该怎么做?例如,如果我想替换第三个匹配项“10”(匹配项[2]),我不能只说s[matches[2]]="X"因为它做了两次替换“1fooX0barXg

    2. ruby-on-rails - 在 ruby​​ 中使用 gsub 函数替换单词 - 2

      我正在尝试用ruby​​中的gsub函数替换字符串中的某些单词,但有时效果很好,在某些情况下会出现此错误?这种格式有什么问题吗NoMethodError(undefinedmethod`gsub!'fornil:NilClass):模型.rbclassTest"replacethisID1",WAY=>"replacethisID2andID3",DELTA=>"replacethisID4"}end另一个模型.rbclassCheck 最佳答案 啊,我找到了!gsub!是一个非常奇怪的方法。首先,它替换了字符串,所以它实际上修改了

    3. ruby - 从 Ruby 中的主机名获取 IP 地址 - 2

      我有一个存储主机名的Ruby数组server_names。如果我打印出来,它看起来像这样:["hostname.abc.com","hostname2.abc.com","hostname3.abc.com"]相当标准。我想要做的是获取这些服务器的IP(可能将它们存储在另一个变量中)。看起来IPSocket类可以做到这一点,但我不确定如何使用IPSocket类遍历它。如果它只是尝试像这样打印出IP:server_names.eachdo|name|IPSocket::getaddress(name)pnameend它提示我没有提供服务器名称。这是语法问题还是我没有正确使用类?输出:ge

    4. c# - 如何在 ruby​​ 中调用 C# dll? - 2

      如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

    5. ruby-on-rails - 如何在 mac os snow leopard 中升级 ruby​​ 和 rails - 2

      我想将我的MacSnowLeopardruby​​从1.8.7升级到1.9.1版本,有人知道轻松且最好的升级方法吗?因为我读了一些论坛/帖子/博客/讨论说覆盖苹果发布的ruby不好将Rails从版本2.2.2升级到2.3.8的最佳方法是什么?因为我找到的所有信息都仅适用于豹/老虎,而且很少有关于雪豹的复杂文章。他们还说覆盖apple提供的rails不好吗。谁能帮帮我?谢谢。 最佳答案 DanBenjamin有一些greatinstructionsforcompilingandinstallingRuby,RubyGemsandRai

    6. C# 到 Ruby sha1 base64 编码 - 2

      我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

    7. ruby - 在不使用 RVM 的情况下在 Mac 上卸载和升级 Ruby - 2

      我最近决定从我的系统中卸载RVM。在thispage提出的一些论点说服我:实际上,我的决定是,我根本不想担心Ruby的多个版本。我只想使用1.9.2-p290版本而不用担心其他任何事情。但是,当我在我的Mac上运行ruby--version时,它告诉我我的版本是1.8.7。我四处寻找如何简单地从我的Mac上卸载这个Ruby,但奇怪的是我没有找到任何东西。似乎唯一想卸载Ruby的人运行linux,而使用Mac的每个人都推荐RVM。如何从我的Mac上卸载Ruby1.8.7?我想升级到1.9.2-p290版本,并且我希望我的系统上只有一个版本。 最佳答案

    8. 基于C#实现简易绘图工具【100010177】 - 2

      C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

    9. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

      文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

    10. ruby - Ruby gsub 替换中的行为不一致? - 2

      两个gsub产生不同的结果。谁能解释一下为什么?代码也可在https://gist.github.com/franklsf95/6c0f8938f28706b5644d获得.ver=9999str="\tCFBundleDevelopmentRegion\n\ten\n\tCFBundleVersion\n\t0.1.190\n\tAppID\n\t000000000000000"putsstr.gsub/(CFBundleVersion\n\t.*\.).*()/,"#{$1}#{ver}#{$2}"puts'--------'putsstr.gsub/(CFBundleVersio

    随机推荐