簡體   English   中英

快速替換Win32_NetworkAdapter WMI類以獲取本地計算機的MAC地址

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

TL;此問題的DR版本:WMI Win32_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列變為null,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 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);
    }

我調用了這個函數3次,每次在最后一行打印約0.36秒。 所以建議的標志似乎沒有任何影響:正面或負面。 這不是太令人驚訝,作為如何在C#中進行只進,只讀WMI查詢的答案 似乎表明除非有大量記錄(例如數百到數千),否則不會觀察到性能變化,而Win32_NetworkAdapter表則不然。

編輯2:已經提出了多個答案來使用來自IP幫助程序API的SendARP (這是具有GetAdaptersInfo函數的相同API)。 這有什么優勢可以讓GetAdaptersInfo找到本地MAC地址? 我無法想到 - 從表面上看,GetAdaptersInfo似乎返回比SendARP對本地適配器更完整的信息集。 現在我考慮一下,我認為我的問題的一個重要部分集中在枚舉的概念上:首先在計算機上存在哪些適配器? SendARP不執行枚舉:它假定您已經知道您想要MAC的適配器的IP地址。 我需要弄清楚系統上存在哪些適配器。 這引起了一些問題:

  • 如果拔下網線,會發生什么? 這在筆記本電腦上很常見,例如(未插入以太網,斷開連接的WiFi卡)。 我嘗試使用NetworkInterface.GetAllNetworkInterfaces()並使用GetIPProperties()列出所有單播地址當拔出媒體時,單播地址。 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的過度調用以獲取不需要的信息是導致它如此緩慢的原因。

我所做的高級概述:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM