如何根据设备实例ID可靠快速地获取网卡的MAC地址

给定一个网卡的设备实例ID ,我想知道它的MAC地址。 集成英特尔千兆位卡的系统示例设备实例ID:

PCI\VEN_8086&DEV_10CC&SUBSYS_00008086&REV_00\3&33FD14CA&0&C8 

到目前为止,我使用的algorithm如下所示:

  1. DIGCF_DEVICEINTERFACE调用DIGCF_DEVICEINTERFACE
  2. 调用SetupDiEnumDeviceInfo来获取SP_DEVINFO_DATA的返回设备。
  3. 使用GUID_NDIS_LAN_CLASS调用SetupDiEnumDeviceInterfaces以获取设备接口。
  4. 为此返回的设备接口调用SetupDiGetDeviceInterfaceDetail 。 这将得到设备path为string: \\?\pci#ven_8086&dev_10cc&subsys_00008086&rev_00#3&33fd14ca&0&c8#{ad498944-762f-11d0-8dcb-00c04fc3358c}\{28fd5409-15bd-4c06-b62f-004d3a06f852}
  5. 在这一点上,我们有一个地址到网卡驱动程序的界面。 使用#4的结果使用CreateFile打开它。
  6. 使用IOCTL_NDIS_QUERY_GLOBAL_STATSOID_802_3_PERMANENT_ADDRESS调用DeviceIoControl以获取MAC地址。

这通常起作用,并且已经在相当多的机器上成功使用。 但是,看起来只有less数机器具有networking驱动程序,它们在步骤#6中没有正确响应DeviceIoControl请求; 即使将网卡驱动程序更新到最新,问题仍然存在。 这些更新,基于Windows 7的计算机。 具体来说, DeviceIoControl成功完成,但返回零字节,而不是包含MAC地址的预期的六个字节。

一个线索似乎在IOCTL_NDIS_QUERY_GLOBAL_STATS的MSDN页面上:

此IOCTL将在以后的操作系统版本中被弃用。 您应该使用WMI接口来查询微型端口驱动程序信息。 有关更多信息,请参阅NDIS对WMI的支持。

– 也许更新的网卡驱动程序不再实现这个IOCTL?

那么,我应该如何做到这一点? 是否有可能在我的方法中有一个疏忽,我正在做一些微小的错误? 还是我需要采取更多不同的方法? 一些替代方法似乎包括:

  • 查询Win32_NetworkAdapter WMI类:提供所需的信息,但由于可怕的性能而被拒绝。 请参阅Win32_NetworkAdapter WMI类的快速replace以获取本地计算机的MAC地址
  • 查询MSNdis_EthernetPermanentAddress WMI类:看起来是IOCTL_NDIS_QUERY_GLOBAL_STATS的WMIreplace,并直接从驱动程序查询OID – 这个工作在麻烦的networking驱动程序上。 不幸的是,返回的类实例只提供MAC地址和InstanceName ,这是一个本地化的string,如Intel(R) 82567LM-2 Gigabit Network Connection 。 查询MSNdis_EnumerateAdapter生成一个将InstanceName关联到DeviceName的列表,如\DEVICE\{28FD5409-15BD-4C06-B62F-004D3A06F852} 。 我不知道如何从DeviceName转到即插即用设备实例ID( PCI\VEN_8086...... )。
  • 调用GetAdaptersAddressesGetAdaptersInfo (不build议使用)。 在返回值中唯一可以find的非本地化标识符是适配器名称,这是一个类似于{28FD5409-15BD-4C06-B62F-004D3A06F852}的string – 与WMI NDIS类所返回的DeviceName相同。 所以再次,我不知道如何将其与设备实例ID相关联。 我不知道它是否可以100%的时间工作 – 例如,对于没有configurationTCP / IP协议的适配器。
  • NetBIOS方法:需要在卡上设置特定的协议,所以不能100%的时间工作。 一般来说,似乎是hack-ish,而不是我知道的与设备实例ID相关的方法。 我会拒绝这个方法。
  • UUID生成方法:由于原因我不会在这里详细说明。

看来,如果我能find一种方法从设备实例ID中获取卡的“GUID”,那么我将完成其余两种处理方式之一。 但是我还没有想出如何。 否则,WMI NDIS方法似乎是最有希望的。

获取网卡和MAC地址列表很容易,有几种方法。 以快速的方式做到这一点,让我把它关联到设备实例ID显然是很难…

编辑: IOCTL调用的示例代码,如果它可以帮助任何人(忽略泄漏的hFile句柄):

 HANDLE hFile = CreateFile(dosDevice.c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); if (hFile == INVALID_HANDLE_VALUE) { DWORD err = GetLastError(); wcout << "GetMACAddress: CreateFile on " << dosDevice << " failed." << endl; return MACAddress(); } BYTE address[6]; DWORD oid = OID_802_3_PERMANENT_ADDRESS, returned = 0; //this fails too: DWORD oid = OID_802_3_CURRENT_ADDRESS, returned = 0; if (!DeviceIoControl(hFile, IOCTL_NDIS_QUERY_GLOBAL_STATS, &oid, sizeof(oid), address, 6, &returned, NULL)) { DWORD err = GetLastError(); wcout << "GetMACAddress: DeviceIoControl on " << dosDevice << " failed." << endl; return MACAddress(); } if (returned != 6) { wcout << "GetMACAddress: invalid address length of " << returned << "." << endl; return MACAddress(); } 

代码失败,打印:

 GetMACAddress: invalid address length of 0. 

所以DeviceIoControl返回非零值表示成功,但是返回零字节。

Solutions Collecting From Web of "如何根据设备实例ID可靠快速地获取网卡的MAC地址"

以下是一种方法:

  1. 调用GetAdaptersAddresses来获取IP_ADAPTER_ADDRESSES结构体的列表
  2. 迭代每个适配器,并从AdapterName字段中获取它的GUID(我不确定这种行为是否有保证,但是我的系统中的所有适配器都有一个GUID,文档中说AdapterName是永久的)
  3. 对于每个适配器,从HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}\<the adapter GUID>\Connection\PnPInstanceID (如果存在)读取注册表项(从此处获得此想法;在Google上搜索的关键似乎是有据可查的,所以它不可能改变)
  4. 从这个键你得到适配器的设备ID(如: PCI\VEN_14E4&DEV_16B1&SUBSYS_96B11849&REV_10\4&2B8260C3&0&00E4
  5. 为每个适配器执行此操作,直到找到匹配项为止。 当你得到你的比赛只是回到IP_ADAPTER_ADDRESSES并看看PhysicalAddress领域
  6. 得到一杯啤酒(可选)

如果没有一百万种方法来做这件事,那将不会是Windows!

我使用SetupDiGetDeviceRegistryProperty读取SPDRP_FRIENDLYNAME 。 如果没有找到,那么我读取SPDRP_DEVICEDESC 。 最终,这让我像“VirtualBox主机以太网适配器#2”的字符串。 然后,我将它与WMI NDIS类( MSNdis_EthernetPermanentAddress WMI类)中的InstanceName属性进行匹配。 如果有多个适配器共享相同的驱动程序(即“#2”,“#3”等),则必须读取这两个属性 – 如果只有一个适配器,则SPDRP_FRIENDLYNAME不可用,但如果有多个那么需要SPDRP_FRIENDLYNAME来区分它们。

这个方法让我有点紧张,因为我比较了一个本地化的字符串,而且没有任何文档能够保证我所做的工作总能正常工作。 不幸的是,我还没有找到更好的方法来记录工作。

其他一些替代方法涉及在无证的注册表位置徘徊。 一种方法是spencercw的方法,另一种方法是读取SPDRP_DRIVER ,它是HKLM\SYSTEM\CurrentControlSet\Control\Class下的子键的名称。 在驱动程序键下面,查找Linkage\Export值,然后看起来好像它可以匹配到MSNdis_EnumerateAdapter类的DeviceName属性。 但是没有我能找到的文件说这些值可以合法匹配。 此外,我发现有关Linkage\Export的唯一文档来自Win2000注册表引用,并明确表示应用程序不应该依赖它。

另一种方法是看我的原始问题,第4步:“ SetupDiGetDeviceInterfaceDetail这个返回的设备接口”。 设备接口路径实际上可以用来重建设备路径。 从设备接口路径开始: \\?\pci#ven_8086&dev_10cc&subsys_00008086&rev_00#3&33fd14ca&0&c8#{ad498944-762f-11d0-8dcb-00c04fc3358c}\{28fd5409-15bd-4c06-b62f-004d3a06f852} 。 然后,删除最后一个斜杠前的所有内容,留下: {28fd5409-15bd-4c06-b62f-004d3a06f852} 。 最后,将\Device\添加到此字符串中,并将其与WMI NDIS类匹配。 然而,这又似乎是没有记录的,依靠设备接口路径的实现细节。

最后,我调查的其他方法有自己的无证复杂化,至少与匹配SPDRP_FRIENDLYNAME / SPDRP_DEVICEDESC字符串一样严重。 所以我选择了更简单的方法,即将这些字符串与WMI NDIS类匹配。