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 无法完全初始化。

为了访问有关您的系统的信息,这里有一个极简类,它试图保持安全:

注意:ToStringconvertToUtf8convertFromUtf8 留给读者作为练习。 :)

注意:我刚刚展示了 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 地址?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 linux/Mac OSX 中获取网络适配器统计信息?

如何获取当前使用的网络适配器

网站能否通过用户浏览获取用户的mac地址?

从 ipconfig 获取 MAC 和 DHCP 服务器

电脑mac地址查询方法

计算机网络要点总结