Как надежно и быстро получить MAC-адрес сетевой карты с учетом идентификатора экземпляра устройства
дали код экземпляра устройства для сетевой карты я хотел бы знать ее MAC-адрес. Пример ID экземпляра устройства в моей системе для интегрированной гигабитной карты Intel:
PCIVEN_8086&DEV_10CC&SUBSYS_00008086&REV_00&33FD14CA&0&C8
до сих пор алгоритм, который я использовал, работает следующим образом:
- вызов
SetupDiGetClassDevs
СDIGCF_DEVICEINTERFACE
. - вызов
SetupDiEnumDeviceInfo
чтобы получить возвращенное устройство вSP_DEVINFO_DATA
. - звоните
SetupDiEnumDeviceInterfaces
СGUID_NDIS_LAN_CLASS
чтобы получить интерфейс устройства. - вызов
SetupDiGetDeviceInterfaceDetail
для этого возвращенного интерфейса устройства. Это дает нам путь устройства в виде строки:?pci#ven_8086&dev_10cc&subsys_00008086&rev_00#3&33fd14ca&0&c8#{ad498944-762f-11d0-8dcb-00c04fc3358c}{28fd5409-15bd-4c06-b62f-004d3a06f852}
- на данный момент у нас есть адрес интерфейса драйвера сетевой карты. Откройте его с помощью
CreateFile
используя результат из #4. - вызов
DeviceIoControl
СIOCTL_NDIS_QUERY_GLOBAL_STATS
и OIDOID_802_3_PERMANENT_ADDRESS
получить Mac-адрес.
это обычно работает и успешно используется на довольно большом количестве машин. Однако, похоже, что очень немногие машины имеют сетевые драйверы, которые не отвечают должным образом на DeviceIoControl
запрос на шаге № 6; проблема сохраняется даже после обновления драйверов сетевой карты до последней. Это новые компьютеры под управлением Windows 7. В частности, DeviceIoControl
успешно завершается, но возвращает ноль байтов вместо ожидаемых шести байты, содержащие MAC-адрес.
подсказка, кажется, находится на странице MSDN для IOCTL_NDIS_QUERY_GLOBAL_STATS
:
этот IOCTL будет устаревшим в более поздних версиях операционной системы. Вы следует использовать интерфейсы WMI для запроса сведений о драйвере минипорта. Для дополнительные сведения см. В разделе Поддержка NDIS для WMI.
-- возможно, новые драйверы сетевых карт больше не реализуют этот IOCTL?
Итак, как мне заставить это работать? Возможно ли, что в моем подходе есть недосмотр, и я делаю что-то немного неправильно? Или мне нужно использовать другой подход? Некоторые альтернативные подходы, по-видимому, включают:
- запрос
Win32_NetworkAdapter
класс WMI: предоставляет необходимую информацию, но отклоняется из-за ужасной производительности. См.быстрая замена для Win32_NetworkAdapter WMI class для получения MAC-адреса локального компьютера - запрос
MSNdis_EthernetPermanentAddress
класс WMI: похоже, замена WMI дляIOCTL_NDIS_QUERY_GLOBAL_STATS
и запрашивает OID непосредственно у драйвера - и этот работает на проблемном сетевом драйвере. К сожалению, возвращаемые экземпляры класса предоставляют только MAC-адрес иInstanceName
, который является локализованной строкой, такой какIntel(R) 82567LM-2 Gigabit Network Connection
. ЗапросMSNdis_EnumerateAdapter
выводит список, который относится кInstanceName
доDeviceName
, какDEVICE{28FD5409-15BD-4C06-B62F-004D3A06F852}
. Я не уверен, как идти отDeviceName
на идентификатор экземпляра устройства plug-and-play (PCIVEN_8086......
). - звоните
GetAdaptersAddresses
илиGetAdaptersInfo
(рекомендуется). Единственный нелокализованный идентификатор, который я могу найти в возвращаемом значении, - это имя адаптера, которое является строкой{28FD5409-15BD-4C06-B62F-004D3A06F852}
- то же самое какDeviceName
возвращено классами NDIS WMI. Опять же, я не могу понять, как связать его с идентификатором экземпляра устройства. Я не уверен, что он будет работать 100% времени-например, для адаптеров без настроенного протокола TCP/IP. - метод NetBIOS: требует определенных протоколов на карту таким образом не будет работать 100% времени. Как правило, кажется hack-ish, и не способ относиться к идентификатору экземпляра устройства в любом случае, что я знаю. Я бы отказался от такого подхода.
- метод генерации UUID: отклонен по причинам, которые я не буду здесь развивать.
похоже, что если бы я мог найти способ получить "GUID" для карты из идентификатора экземпляра устройства, я был бы на моем пути с одним из оставшихся двух способов делать вещи. Но я не разобрался как еще. В противном случае подход NDIS WMI будет казаться наиболее перспективным.
получить список сетевых карт и MAC-адресов легко, и есть несколько способов сделать это. Делать это быстро, что позволяет мне связать его с идентификатором экземпляра устройства, по-видимому, сложно...
EDIT: пример кода вызова 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 возвращает ненулевое значение, указывающее на успех, но затем возвращает нулевые байты.
2 ответов
вот один из способов сделать это:
- вызов
GetAdaptersAddresses
чтобы получить списокIP_ADAPTER_ADDRESSES
структуры - повторите каждый адаптер и получите его GUID от
AdapterName
поле (я не уверен, что это поведение гарантировано, но все адаптеры в моей системе имеют GUID здесь, и документация говоритAdapterName
постоянный) - для каждого адаптера прочитайте раздел реестра из
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}\<the adapter GUID>\Connection\PnPInstanceID
(если он существует) (получил эту идею от здесь; поиск в Google этот ключ, кажется, хорошо документирован, поэтому он вряд ли изменится) - из этого ключа вы получаете код устройства для адаптера (что-то вроде:
PCI\VEN_14E4&DEV_16B1&SUBSYS_96B11849&REV_10&2B8260C3&0&00E4
) - этого для каждого адаптера, пока не найдете матча. Когда вы получите свой матч, просто вернитесь в
IP_ADAPTER_ADDRESSES
и посмотрите наPhysicalAddress
поле - получить пиво (необязательно)
это не было бы Windows, если бы не было миллиона способов сделать что-то!
я закончил с помощью SetupDiGetDeviceRegistryProperty
читать SPDRP_FRIENDLYNAME
. Если это не найдено, то я читаю SPDRP_DEVICEDESC
вместо. В конечном счете, это дает мне строку типа "VirtualBox Host-Only Ethernet Adapter #2". Затем я сопоставляю это со свойством InstanceName в классах NDIS WMI (MSNdis_EthernetPermanentAddress
класс WMI). Оба свойства должны быть прочитаны, если есть несколько адаптеров, использующих один и тот же драйвер (т. е. "#2", "#3", etc.) - если есть только один адаптер, то SPDRP_FRIENDLYNAME
недоступно, но если их больше одного тогда SPDRP_FRIENDLYNAME
требуется, чтобы отличить их.
метод заставляет меня немного нервничать, потому что я сравниваю то, что кажется локализованной строкой, и нет никакой документации, которую я нашел, которая гарантирует, что то, что я делаю, всегда будет работать. К сожалению,я не нашел лучших способов, которые документированы для работы.
несколько других альтернативных методов включают пресмыкание в недокументированных местах реестра. Одним из методов является метод spencercw, а другой было бы читать SPDRP_DRIVER
, что является названием подраздела под HKLM\SYSTEM\CurrentControlSet\Control\Class
. Под ключом драйвера найдите Linkage\Export
значение, которое затем кажется, что оно может быть сопоставлено с DeviceName
свойства MSNdis_EnumerateAdapter
класса. Но нет никакой документации, в которой я мог бы найти, что эти значения могут быть юридически сопоставлены. Кроме того, единственная документация, которую я нашел о 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\
к этой строке и сопоставьте ее с классами NDIS WMI. Опять же, однако, это кажется недокументированным и полагается на детали реализации устройства путь интерфейса.
SPDRP_DEVICEDESC
строки. Поэтому я выбрал более простой подход, который должен был просто сопоставить эти строки с классами NDIS WMI.