C++:在 Vista 上获取网络适配器的 MAC 地址?
Posted
技术标签:
【中文标题】C++:在 Vista 上获取网络适配器的 MAC 地址?【英文标题】:C++: Get MAC address of network adapters on Vista? 【发布时间】:2010-09-18 07:47:47 【问题描述】:我们目前正在使用NetBios method,它在 XP 下运行正常。在 Vista 下的初步测试表明它也可以工作,但有一些警告 - 例如,必须存在 NetBIOS,并且根据我一直在阅读的内容,适配器的顺序必然会改变。我们的替代方法 - 使用 SNMPExtensionQuery - 在 Vista 下似乎被破坏了。
问题是:您知道在 Vista 机器上获取本地 MAC 地址列表的可靠方法吗?与 XP 的向后兼容性是一个优点(我宁愿有一个单一的方法而不是许多丑陋的#ifdef)。谢谢!
【问题讨论】:
***.com/questions/823553/…这些需要链接 【参考方案1】:老问题,已经回答,但这是更安全的代码 - 以防 WMI 无法完全初始化。
为了访问有关您的系统的信息,这里有一个极简类,它试图保持安全:
注意:ToString
、convertToUtf8
和 convertFromUtf8
留给读者作为练习。 :)
注意:我刚刚展示了 WMI 系统的安全初始化和拆卸,以及从 WMI 获取值和获取 MAC 地址的基础知识(OP 中的问题) .
这来自 来自工作代码,但在我将其粘贴到此处时进行了修改。因此,有可能其他本应包括在内的东西被遗漏了。哎呀。
class WmiAccessor
public:
WmiAccessor()
: _pWbemLocator(NULL)
, _pWbemServices(NULL)
, _com_initialized(false)
, _com_need_uninitialize(false)
, _svc_initialized(false)
, _loc_initialized(false)
, _all_initialized(false)
, _errors("")
, m_mutex()
HRESULT hr;
hr = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
switch (hr)
case S_OK:
// The COM library was initialized successfully on this thread.
_com_initialized = true;
_com_need_uninitialize = true;
break;
case S_FALSE:
// The COM library is already initialized on this thread.
_com_initialized = true;
_com_need_uninitialize = true;
break;
case RPC_E_CHANGED_MODE:
// A previous call to CoInitializeEx specified the concurrency model
// for this thread as multithread apartment (MTA).
// This could also indicate that a change from neutral-threaded apartment to
// single-threaded apartment has occurred.
_com_initialized = true;
_com_need_uninitialize = false;
break;
default:
_com_initialized = false;
_com_need_uninitialize = false;
_errors += "Failed to initialize COM.\r\n";
return;
hr = ::CoInitializeSecurity(NULL, -1, NULL, NULL,
0 /*RPC_C_AUTHN_LEVEL_DEFAULT*/,
3 /*RPC_C_IMP_LEVEL_IMPERSONATE*/,
NULL, EOAC_NONE, NULL);
// RPC_E_TOO_LATE == Security must be initialized before!
// It cannot be changed once initialized. I don't care!
if (FAILED(hr) && (hr != RPC_E_TOO_LATE))
_errors += "Failed to initialize COM Security.\r\n";
if (_com_need_uninitialize)
::CoUninitialize();
_com_need_uninitialize = false;
return;
hr = _pWbemLocator.CoCreateInstance(CLSID_WbemLocator);
if (FAILED(hr) || (_pWbemLocator == nullptr))
_errors += "Failed to initialize WBEM Locator.\r\n";
return;
_loc_initialized = true;
hr = _pWbemLocator->ConnectServer(
CComBSTR(L"root\\cimv2"), NULL, NULL, 0, NULL, 0, NULL, &_pWbemServices);
if (FAILED(hr) || (_pWbemServices == nullptr))
_errors += "Failed to connect WBEM Locator.\r\n";
_pWbemLocator.Release();
_loc_initialized = false;
return;
else
_svc_initialized = true;
// Set security Levels on the proxy
hr = CoSetProxyBlanket(_pWbemServices,
RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx
RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx
NULL, // Server principal name
RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx
RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
NULL, // client identity
EOAC_NONE // proxy capabilities
);
if (FAILED(hr))
_errors += "Failed to set proxy blanket.\r\n";
return;
_all_initialized = true;
~WmiAccessor()
std::unique_lock<std::mutex> slock(m_mutex);
if (_svc_initialized)
if (_pWbemServices)
_pWbemServices.Release();
_svc_initialized = false;
if (_loc_initialized)
if (_pWbemLocator)
_pWbemLocator.Release();
_loc_initialized = false;
if (_com_initialized)
if (_com_need_uninitialize)
::CoUninitialize();
_com_initialized = false;
_com_need_uninitialize = false;
_all_initialized = false;
// public: must lock
std::string get_and_clear_errors()
std::string result = "";
std::unique_lock<std::mutex> slock(m_mutex);
std::swap(result, _errors);
return result;
// public: must lock
std::string get_string(const std::string& name, const std::string& dflt /*= ""*/)
std::unique_lock<std::mutex> slock(m_mutex);
return _all_initialized ? _string(name) : dflt;
// public: must lock
uint32_t get_uint32(const std::string& name, uint32_t dflt /*= 0*/)
std::unique_lock<std::mutex> slock(m_mutex);
return _all_initialized ? _uint32(name) : dflt;
// similarly for other public accessors of basic types.
private:
CComPtr<IWbemLocator> _pWbemLocator;
CComPtr<IWbemServices> _pWbemServices;
volatile bool _com_initialized;
volatile bool _com_need_uninitialize;
volatile bool _svc_initialized;
volatile bool _loc_initialized;
volatile bool _all_initialized;
std::string _errors;
CComVariant _variant(const std::wstring& name);
std::string _string(const std::string& name);
uint32_t _uint32(const std::string& name);
uint16_t _uint16(const std::string& name);
uint8_t _uint8(const std::string& name);
std::vector<std::string> _macAddresses(bool forceReCalculate = false);
// to protect internal objects, public methods need to protect the internals.
//
mutable std::mutex m_mutex;
std::vector<std::string> _macs; // unlikely to change, so save them once found.
// internal: assumes inside a lock
CComVariant _variant(const std::wstring& name)
if (!_all_initialized)
return CComVariant();
CComPtr<IEnumWbemClassObject> pEnum;
CComBSTR cbsQuery = std::wstring(L"Select " + name + L" from Win32_OperatingSystem").c_str();
HRESULT hr = _pWbemServices->ExecQuery(
CComBSTR(L"WQL"), cbsQuery, WBEM_FLAG_FORWARD_ONLY, NULL, &pEnum);
CComVariant cvtValue;
if (FAILED(hr) || !pEnum)
std::wstring wquery(cbsQuery, SysStringLen(cbsQuery));
_errors += "Failed to exec WMI query: '" + convertToUtf8(wquery) + "'\r\n";
return cvtValue;
ULONG uObjectCount = 0;
CComPtr<IWbemClassObject> pWmiObject;
hr = pEnum->Next(WBEM_INFINITE, 1, &pWmiObject, &uObjectCount);
if (FAILED(hr) || !pWmiObject)
_errors
+= "Failed to get WMI Next result for: '" + convertToUtf8(name) + "'\r\n";
return cvtValue;
hr = pWmiObject->Get(name.c_str(), 0, &cvtValue, 0, 0);
if (FAILED(hr))
_errors
+= "Failed to get WMI result value for: '" + convertToUtf8(name) + "'\r\n";
return cvtValue;
// internal: assumes inside a lock
std::string _string(const std::string& name)
if (!_all_initialized)
return "";
CComVariant cvtValue = _variant(convertFromUtf8(name).c_str());
std::wstring wValue(cvtValue.bstrVal, SysStringLen(cvtValue.bstrVal));
std::string sValue = convertToUtf8(wValue);
return sValue;
// internal: assumes inside a lock
uint32_t _uint32(const std::string& name)
if (!_all_initialized)
return 0;
CComVariant cvtValue = _variant(convertFromUtf8(name).c_str());
uint32_t uValue = static_cast<uint32_t>(cvtValue.lVal);
return uValue;
// similarly for other internal access of basic types.
// internal: assumes inside a lock
std::vector<std::string> _macAddresses(bool forceReCalculate /*= false*/)
if (!_all_initialized)
return _macs; // it will still be empty at this point.
if (forceReCalculate)
_macs.clear();
if (_macs.empty())
// hr == 0x80041010 == WBEM_E_INVALID_CLASS
// hr == 0x80041017 == WBEM_E_INVALID_QUERY
// hr == 0x80041018 == WBEM_E_INVALID_QUERY_TYPE
CComBSTR cbsQuery = std::wstring(L"Select * from Win32_NetworkAdapter").c_str();
CComPtr<IEnumWbemClassObject> pEnum;
HRESULT hr = _pWbemServices->ExecQuery(
CComBSTR(L"WQL"), cbsQuery, WBEM_RETURN_IMMEDIATELY, NULL, &pEnum);
if (FAILED(hr))
_errors += "error: MacAddresses: ExecQuery('"
+ convertToUtf8((LPWSTR)cbsQuery) + "') returned "
+ ToString(hr) + "\r\n";
if (SUCCEEDED(hr))
ULONG fetched;
VARIANT var;
IWbemClassObject* pclsObj = NULL;
while (pEnum)
hr = pEnum->Next(WBEM_INFINITE, 1, &pclsObj, &fetched);
if (0 == fetched)
break;
std::string theMac = "";
VariantInit(&var);
hr = pclsObj->Get(L"MACAddress", 0, &var, 0, 0);
if (SUCCEEDED(hr))
switch (var.vt)
case VT_NULL: break;
case VT_BSTR:
theMac = (var.bstrVal == NULL)
? ""
: convertToUtf8(var.bstrVal);
break;
case VT_LPSTR:
theMac = (var.bstrVal == NULL)
? ""
: (const char*)var.bstrVal;
break;
case VT_LPWSTR:
theMac = (var.bstrVal == NULL)
? ""
: convertToUtf8((LPWSTR)var.bstrVal);
break;
// _could_ be array of BSTR, LPSTR, LPWSTR; unlikely, but ....
case VT_ARRAY | VT_BSTR:
case VT_ARRAY | VT_LPSTR:
case VT_ARRAY | VT_LPWSTR:
_errors += "warning: MacAddresses: unexpected array of addresses";
_errors += "\r\n";
// yet another exercise for the reader :)
break;
default:
_errors += "error: MacAddresses: unexpected VARIANT.vt = "
+ ToString(var.vt) + "\r\n";
break;
// local loopback has an empty address?
if (!theMac.empty())
_macs.push_back(theMac);
VariantClear(&var);
pclsObj->Release();
return _macs;
...
【讨论】:
【参考方案2】:#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <stdio.h>
#include <vector>
#include <Windows.h>
#include <Iphlpapi.h>
#include <Assert.h>
#include <string>
#pragma comment(lib, "iphlpapi.lib")
char* getdMacAddresses()
IP_ADAPTER_INFO AdapterInfo[32]; // Allocate information for up to 32 NICs
DWORD dwBufLen = sizeof(AdapterInfo); // Save memory size of buffer
DWORD dwStatus = GetAdaptersInfo( // Call GetAdapterInfo
AdapterInfo, // [out] buffer to receive data
&dwBufLen); // [in] size of receive data buffer
//Exit When Error
if (dwStatus != ERROR_SUCCESS)
return "ERROR";
PIP_ADAPTER_INFO pAdapterInfo = AdapterInfo;
char szBuffer[512];
while (pAdapterInfo)
if (pAdapterInfo->Type == MIB_IF_TYPE_ETHERNET)
sprintf_s(szBuffer, sizeof(szBuffer), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x"
, pAdapterInfo->Address[0]
, pAdapterInfo->Address[1]
, pAdapterInfo->Address[2]
, pAdapterInfo->Address[3]
, pAdapterInfo->Address[4]
, pAdapterInfo->Address[5]
);
return szBuffer;
pAdapterInfo = pAdapterInfo->Next;
return "ERROR";
【讨论】:
你能解释一下这将如何解决这个问题吗?【参考方案3】:GetAdaptersInfo() 是官方的方法,它会枚举所有适配器,即使是断开连接的适配器。 参见这篇文章,例如代码codeguru
【讨论】:
不正确。 GetAdaptersInfo() 无法枚举已禁用的适配器。代码大师文章甚至陈述了这个事实:“最后,如果你的 NIC 没有连接到有效的网络(例如,连线都没有连接),它也可以工作,但是 NIC 必须在 Windows 中“启用”” 此外,GetAdaptersInfo() 不会检测到禁用 TCP/IP 协议的卡。 GetAdaptersInfo 不枚举禁用的适配器【参考方案4】:这将为您提供计算机上所有 MAC 地址的列表。它也适用于所有版本的 Windows:
void getdMacAddresses(std::vector<std::string> &vMacAddresses;)
vMacAddresses.clear();
IP_ADAPTER_INFO AdapterInfo[32]; // Allocate information for up to 32 NICs
DWORD dwBufLen = sizeof(AdapterInfo); // Save memory size of buffer
DWORD dwStatus = GetAdaptersInfo( // Call GetAdapterInfo
AdapterInfo, // [out] buffer to receive data
&dwBufLen); // [in] size of receive data buffer
//No network card? Other error?
if(dwStatus != ERROR_SUCCESS)
return;
PIP_ADAPTER_INFO pAdapterInfo = AdapterInfo;
char szBuffer[512];
while(pAdapterInfo)
if(pAdapterInfo->Type == MIB_IF_TYPE_ETHERNET)
sprintf_s(szBuffer, sizeof(szBuffer), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x"
, pAdapterInfo->Address[0]
, pAdapterInfo->Address[1]
, pAdapterInfo->Address[2]
, pAdapterInfo->Address[3]
, pAdapterInfo->Address[4]
, pAdapterInfo->Address[5]
);
vMacAddresses.push_back(szBuffer);
pAdapterInfo = pAdapterInfo->Next;
【讨论】:
嗨,Brian,感谢您的提醒;与此同时,我找到了这个链接(适用于 XP 及更高版本);我想我会选择这个或 WMI 解决方案。 msdn.microsoft.com/en-us/library/aa365915(VS.85).aspx 我们已经在我们的主要产品中使用了上面的这种方法好几年了。在 Vista、2008、2003、XP、2000、...中运行良好。 还有 Windows 8 和 8.1 即使在 Win10 中也能完美运行。 :)【参考方案5】:你能用WMIService吗?不过,在 Vista 之前的日子里,我用它来获取机器的 mac 地址。
【讨论】:
谢谢,这似乎是我的问题最干净的解决方案。【参考方案6】:您可以在 XP 和 Vista 上使用 WMI,网上有很多示例。例如: Use Windows Management Instrumentation (WMI) to get a MAC Address
【讨论】:
OP 是针对C++
,而您的链接答案是VB .NET
——不是很接近。此外,在一些 XP 系统上,WMI 被禁用或根本没有安装。请务必测试从调用中返回的 HRESULTS,以便了解它是否已完全初始化。以上是关于C++:在 Vista 上获取网络适配器的 MAC 地址?的主要内容,如果未能解决你的问题,请参考以下文章