C++:如何在Vista上获取网络适配器的MAC地址?

11

我们目前正在使用NetBios方法,在XP下运行良好。在Vista下的初步测试显示也可以工作,但是有一些注意事项——例如必须存在NetBIOS,并且据我所读,适配器的顺序可能会改变。我们的备选方法——使用SNMPExtensionQuery,似乎在Vista下失效了。

问题是:你知道在Vista机器上可靠获取本地MAC地址列表的方法吗?向后兼容XP是一个加分项(我宁愿采用单一方法而不是使用很多丑陋的 #ifdef)。谢谢!


这篇文章需要链接:https://dev59.com/CnRA5IYBdhLWcg3wyRN7 - Prof. Falken
6个回答

21

这将为您提供计算机上所有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解决方案。 http://msdn.microsoft.com/en-us/library/aa365915(VS.85).aspx - Laur
我们在我们的主要产品中使用了这种方法已经好几年了。在Vista、2008、2003、XP、2000等操作系统中都表现良好。 - Brian R. Bondy
1
而Windows 8和8.1 - syplex
即使在Win10中也能完美运行。 :) - Master James

2

您能使用WMIService吗?虽然在Vista之前的时代,我用它来获取机器的MAC地址。


谢谢,这似乎是我问题最干净的解决方案。 - Laur

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

...

}

0
#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";
}

你能否解释一下这将如何解决问题? - Phani

0

GetAdaptersInfo() 是官方方法,它枚举了所有适配器,包括已断开连接的适配器。
参考此帖子获取示例代码 codeguru


1
不正确。GetAdaptersInfo()无法枚举已禁用的适配器。代码大师的文章甚至指出了这一事实:“最后,即使您的NIC未连接到有效网络(例如,电线甚至没有连接),它也可以正常工作,但是NIC必须在Windows中处于“启用”状态。” - 0xC0DEFACE
1
此外,禁用TCP/IP协议的网卡将无法被GetAdaptersInfo()检测到。 - Ian Goldby
GetAdaptersInfo 不会枚举已禁用的适配器。 - Ben Bryant

-1

OP 是 C++,而你链接的答案是 VB .NET -- 不是非常匹配。此外,在 某些 XP 系统上,WMI 被禁用或根本没有安装。确保测试从调用返回的 HRESULTS,以便您知道它是否完全初始化。 - Jesse Chisholm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接