用 Windows API “GetAdaptersInfo” 获取 MAC 时遇到的问题

前段时间有个项目需要获取客户端的 MAC 地址,用作统计去重的参考数据。从网上查到的获取 MAC 地址的代码,大多是用同一段代码修改的。于是我也用了那段代码。代码如下:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. void GetMAC(BYTE mac[BUF_SIZE])  
  2. {  
  3.     ULONG size_pointer;  
  4.     PIP_ADAPTER_INFO pip_adapter_info = NULL;  
  5.   
  6.     if(ERROR_BUFFER_OVERFLOW != GetAdaptersInfo(NULL, &size_pointer))  
  7.     {  
  8.         wsprintfA((LPSTR)mac, "GetMAC Failed! ErrorCode: %d", GetLastError());  
  9.         return;  
  10.     }  
  11.   
  12.     pip_adapter_info = (PIP_ADAPTER_INFO)calloc(size_pointer, sizeof(BYTE));  
  13.     if(NULL == pip_adapter_info)  
  14.     {  
  15.         lstrcpyA((LPSTR)mac, "GetMAC Failed! Because calloc failed!");  
  16.         return;  
  17.     }  
  18.   
  19.     if(ERROR_SUCCESS != GetAdaptersInfo(pip_adapter_info, &size_pointer))  
  20.     {  
  21.         wsprintfA((LPSTR)mac, "GetMAC Failed! ErrorCode: %d", GetLastError());  
  22.         free(pip_adapter_info);  
  23.         return;  
  24.     }  
  25.   
  26.     for(int i = 0; i < 6; ++i)  
  27.     {  
  28.         wsprintfA((LPSTR)mac, "%02X", pip_adapter_info->Address[i]);  
  29.         mac += 2;  
  30.     }  
  31. }  

        在自己的电脑上、虚拟机上测试了一番,没有发现问题,觉得一切 OK 之后将产品发布出去,结果发现许多机器返回的 MAC 为 NULL!!!

        问题严重,又重新研究了一番,发现之所以 MAC 返回 NULL 是因为这段代码并不完整:用 GetAdaptersInfo 获取到的不一定是网卡信息,有可能是别的适配器信息,不是网卡就没有 MAC,结果当然为 NULL!花了点时间把代码完善了一下,加了个检测适配器特性的函数,完成了该功能,最终的代码如下:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #include <windows.h>  
  2. #include <iphlpapi.h>       // API GetAdaptersInfo 头文件  
  3. #include <shlwapi.h>        // API StrCmpIA 头文件  
  4. #pragma comment(lib, "iphlpapi.lib")  
  5. #pragma comment(lib, "shlwapi.lib")  
  6. #include <Strsafe.h>        // API StringCbPrintfA 头文件  
  7. #include <shellapi.h>       // API lstrcpyA 头文件  
  8.   
  9. //  
  10. // 功能:获取适配器特性  
  11. // 参数:   
  12. //   adapter_name 适配器 ID  
  13. // 返回值:成功则返回由参数指定的适配器的特性标志,是一个 DWORD 值,失败返回 0  
  14. //  
  15. UINT GetAdapterCharacteristics(char* adapter_name)  
  16. {  
  17.     if(adapter_name == NULL || adapter_name[0] == 0)  
  18.         return 0;  
  19.   
  20.     HKEY root = NULL;  
  21.     // 打开存储适配器信息的注册表根键  
  22.     if(ERROR_SUCCESS != RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}", 0, KEY_READ, &root))  
  23.         return 0;  
  24.   
  25.     DWORD subkeys = 0;  
  26.     // 获取该键下的子键数  
  27.     if(ERROR_SUCCESS != RegQueryInfoKeyA(root, NULL, NULL, NULL, &subkeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL))  
  28.         subkeys = 100;  
  29.   
  30.     DWORD ret_value = 0;  
  31.     for(DWORD i = 0; i < subkeys; i++)  
  32.     {  
  33.         // 每个适配器用一个子键存储,子键名为从 0 开始的 4 位数  
  34.         char subkey[MAX_SIZE];  
  35.         memset(subkey, 0, MAX_SIZE);  
  36.         StringCbPrintfA(subkey, MAX_SIZE, "%04u", i);  
  37.   
  38.         // 打开该子键  
  39.         HKEY hKey = NULL;  
  40.         if(ERROR_SUCCESS != RegOpenKeyExA(root, subkey, 0, KEY_READ, &hKey))  
  41.             continue;  
  42.   
  43.         // 获取该子键对应的适配器 ID,存于 name 中  
  44.         char name[MAX_PATH];  
  45.         DWORD type = 0;  
  46.         DWORD size = MAX_PATH;  
  47.         if(ERROR_SUCCESS != RegQueryValueExA(hKey, "NetCfgInstanceId", NULL, &type, (LPBYTE)name, &size))  
  48.         {  
  49.             RegCloseKey(hKey);  
  50.             continue;  
  51.         }  
  52.   
  53.         // 对比该适配器 ID 是不是要获取特性的适配器 ID  
  54.         if(StrCmpIA(name, adapter_name) != 0)  
  55.         {  
  56.             RegCloseKey(hKey);  
  57.             continue;  
  58.         }  
  59.   
  60.         // 读取该适配器的特性标志,该标志存储于值 Characteristics 中  
  61.         DWORD val = 0;  
  62.         size = 4;  
  63.         LSTATUS ls = RegQueryValueExA(hKey, "Characteristics", NULL, &type, (LPBYTE)&val, &size);  
  64.         RegCloseKey(hKey);  
  65.   
  66.         if(ERROR_SUCCESS == ls)  
  67.         {  
  68.             ret_value = val;  
  69.             break;  
  70.         }  
  71.     }  
  72.   
  73.     RegCloseKey(root);  
  74.     return ret_value;  
  75. }  
  76.   
  77. //  
  78. // 功能:获取 Mac 地址的二进制数据  
  79. // 参数:  
  80. //   mac 用于输出 Mac 地址的二进制数据的缓冲区指针  
  81. // 返回值:成功返回 mac 地址的长度,失败返回 0,失败时 mac 中保存一些简单的错误信息,可适当修改,用于调试  
  82. //  
  83. int GetMAC(BYTE mac[BUF_SIZE])  
  84. {  
  85. #define NCF_PHYSICAL 0x4  
  86.     DWORD AdapterInfoSize = 0;  
  87.     if(ERROR_BUFFER_OVERFLOW != GetAdaptersInfo(NULL, &AdapterInfoSize))  
  88.     {  
  89.         StringCbPrintfA((LPSTR)mac, BUF_SIZE, "GetMAC Failed! ErrorCode: %d", GetLastError());  
  90.         return 0;  
  91.     }  
  92.   
  93.     void* buffer = malloc(AdapterInfoSize);  
  94.     if(buffer == NULL)  
  95.     {  
  96.         lstrcpyA((LPSTR)mac, "GetMAC Failed! Because malloc failed!");  
  97.         return 0;  
  98.     }  
  99.   
  100.     PIP_ADAPTER_INFO pAdapt = (PIP_ADAPTER_INFO)buffer;  
  101.     if(ERROR_SUCCESS != GetAdaptersInfo(pAdapt, &AdapterInfoSize))  
  102.     {  
  103.         StringCbPrintfA((LPSTR)mac, BUF_SIZE, "GetMAC Failed! ErrorCode: %d", GetLastError());  
  104.         free(buffer);  
  105.         return 0;  
  106.     }  
  107.   
  108.     int mac_length = 0;  
  109.     while(pAdapt)  
  110.     {  
  111.         if(pAdapt->AddressLength >= 6 && pAdapt->AddressLength <= 8)  
  112.         {  
  113.             memcpy(mac, pAdapt->Address, pAdapt->AddressLength);  
  114.             mac_length = pAdapt->AddressLength;  
  115.   
  116.             UINT flag = GetAdapterCharacteristics(pAdapt->AdapterName);  
  117.             bool is_physical = ((flag & NCF_PHYSICAL) == NCF_PHYSICAL);  
  118.             if(is_physical)  
  119.                 break;  
  120.         }  
  121.         pAdapt = pAdapt->Next;  
  122.     }  
  123.     free(buffer);  
  124.     return mac_length;  
  125. }  
  126.   
  127. //  
  128. // 功能:获取 Mac 地址,使用时直接调用此函数即可  
  129. // 参数:  
  130. //   mac 用于存储 Mac 地址的缓冲区指针  
  131. // 返回值:无返回值,函数执行完后会把 Mac 地址以16进制的形式存于参数指定的缓冲区中,若有错误,缓冲区中保存的是错误信息  
  132. //  
  133. void GetMacAddress( char* mac )  
  134. {  
  135.     BYTE buf[BUF_SIZE];  
  136.     memset(buf, 0, BUF_SIZE);  
  137.   
  138.     int len = GetMAC(buf);  
  139.     if(len <= 0)  
  140.     {  
  141.         lstrcpyA(mac, (LPCSTR)buf);  
  142.         return;  
  143.     }  
  144.   
  145.     if(len == 6)  
  146.         StringCbPrintfA(mac, BUF_SIZE, "%02X-%02X-%02X-%02X-%02X-%02X", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);  
  147.     else  
  148.         StringCbPrintfA(mac, BUF_SIZE, "%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X", buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]);  
  149. }  

        编译环境: VS2008 + Windows SDK 7.1

        函数功能在 Windows 2000、Windows XP、Windows 2003、Vista、 Win7 32位和 Win7 64 位下均测试通过。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值