From:http://blog.csdn.net/weiyumingwww/article/details/17554461

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

void GetMAC(BYTE mac[BUF_SIZE])
{
ULONG size_pointer;
PIP_ADAPTER_INFO pip_adapter_info = NULL; if(ERROR_BUFFER_OVERFLOW != GetAdaptersInfo(NULL, &size_pointer))
{
wsprintfA((LPSTR)mac, "GetMAC Failed! ErrorCode: %d", GetLastError());
return;
} pip_adapter_info = (PIP_ADAPTER_INFO)calloc(size_pointer, sizeof(BYTE));
if(NULL == pip_adapter_info)
{
lstrcpyA((LPSTR)mac, "GetMAC Failed! Because calloc failed!");
return;
} if(ERROR_SUCCESS != GetAdaptersInfo(pip_adapter_info, &size_pointer))
{
wsprintfA((LPSTR)mac, "GetMAC Failed! ErrorCode: %d", GetLastError());
free(pip_adapter_info);
return;
} for(int i = ; i < ; ++i)
{
wsprintfA((LPSTR)mac, "%02X", pip_adapter_info->Address[i]);
mac += ;
}
}

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

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

#include <windows.h>
#include <iphlpapi.h> // API GetAdaptersInfo 头文件
#include <shlwapi.h> // API StrCmpIA 头文件
#pragma comment(lib, "iphlpapi.lib")
#pragma comment(lib, "shlwapi.lib")
#include <Strsafe.h> // API StringCbPrintfA 头文件
#include <shellapi.h> // API lstrcpyA 头文件 //////////////////////////////////////
// 功能:获取适配器特性
// 参数:
// adapter_name 适配器 ID
// 返回值:成功则返回由参数指定的适配器的特性标志,是一个 DWORD 值,失败返回 0
//
UINT GetAdapterCharacteristics(char* adapter_name)
{
if(adapter_name == NULL || adapter_name[] == )
return ; HKEY root = NULL;
// 打开存储适配器信息的注册表根键
if(ERROR_SUCCESS != RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}", , KEY_READ, &root))
return ; DWORD subkeys = ;
// 获取该键下的子键数
if(ERROR_SUCCESS != RegQueryInfoKeyA(root, NULL, NULL, NULL, &subkeys, NULL, NULL, NULL, NULL, NULL, NULL, NULL))
subkeys = ; DWORD ret_value = ;
for(DWORD i = ; i < subkeys; i++)
{
// 每个适配器用一个子键存储,子键名为从 0 开始的 4 位数
char subkey[MAX_SIZE];
memset(subkey, , MAX_SIZE);
StringCbPrintfA(subkey, MAX_SIZE, "%04u", i); // 打开该子键
HKEY hKey = NULL;
if(ERROR_SUCCESS != RegOpenKeyExA(root, subkey, , KEY_READ, &hKey))
continue; // 获取该子键对应的适配器 ID,存于 name 中
char name[MAX_PATH];
DWORD type = ;
DWORD size = MAX_PATH;
if(ERROR_SUCCESS != RegQueryValueExA(hKey, "NetCfgInstanceId", NULL, &type, (LPBYTE)name, &size))
{
RegCloseKey(hKey);
continue;
} // 对比该适配器 ID 是不是要获取特性的适配器 ID
if(StrCmpIA(name, adapter_name) != )
{
RegCloseKey(hKey);
continue;
} // 读取该适配器的特性标志,该标志存储于值 Characteristics 中
DWORD val = ;
size = ;
LSTATUS ls = RegQueryValueExA(hKey, "Characteristics", NULL, &type, (LPBYTE)&val, &size);
RegCloseKey(hKey); if(ERROR_SUCCESS == ls)
{
ret_value = val;
break;
}
} RegCloseKey(root);
return ret_value;
} //////////////////////////////////////
// 功能:获取 Mac 地址的二进制数据
// 参数:
// mac 用于输出 Mac 地址的二进制数据的缓冲区指针
// 返回值:成功返回 mac 地址的长度,失败返回 0,失败时 mac 中保存一些简单的错误信息,可适当修改,用于调试
//
int GetMAC(BYTE mac[BUF_SIZE])
{
#define NCF_PHYSICAL 0x4
DWORD AdapterInfoSize = ;
if(ERROR_BUFFER_OVERFLOW != GetAdaptersInfo(NULL, &AdapterInfoSize))
{
StringCbPrintfA((LPSTR)mac, BUF_SIZE, "GetMAC Failed! ErrorCode: %d", GetLastError());
return ;
} void* buffer = malloc(AdapterInfoSize);
if(buffer == NULL)
{
lstrcpyA((LPSTR)mac, "GetMAC Failed! Because malloc failed!");
return ;
} PIP_ADAPTER_INFO pAdapt = (PIP_ADAPTER_INFO)buffer;
if(ERROR_SUCCESS != GetAdaptersInfo(pAdapt, &AdapterInfoSize))
{
StringCbPrintfA((LPSTR)mac, BUF_SIZE, "GetMAC Failed! ErrorCode: %d", GetLastError());
free(buffer);
return ;
} int mac_length = ;
while(pAdapt)
{
if(pAdapt->AddressLength >= && pAdapt->AddressLength <= )
{
memcpy(mac, pAdapt->Address, pAdapt->AddressLength);
mac_length = pAdapt->AddressLength; UINT flag = GetAdapterCharacteristics(pAdapt->AdapterName);
bool is_physical = ((flag & NCF_PHYSICAL) == NCF_PHYSICAL);
if(is_physical)
break;
}
pAdapt = pAdapt->Next;
}
free(buffer);
return mac_length;
} //////////////////////////////////////
// 功能:获取 Mac 地址,使用时直接调用此函数即可
// 参数:
// mac 用于存储 Mac 地址的缓冲区指针
// 返回值:无返回值,函数执行完后会把 Mac 地址以16进制的形式存于参数指定的缓冲区中,若有错误,缓冲区中保存的是错误信息
//
void GetMacAddress( char* mac )
{
BYTE buf[BUF_SIZE];
memset(buf, , BUF_SIZE); int len = GetMAC(buf);
if(len <= )
{
lstrcpyA(mac, (LPCSTR)buf);
return;
} if(len == )
StringCbPrintfA(mac, BUF_SIZE, "%02X-%02X-%02X-%02X-%02X-%02X", buf[], buf[], buf[], buf[], buf[], buf[]);
else
StringCbPrintfA(mac, BUF_SIZE, "%02X-%02X-%02X-%02X-%02X-%02X-%02X-%02X", buf[], buf[], buf[], buf[], buf[], buf[], buf[], buf[]);
}

编译环境: VS2008 + Windows SDK 7.1

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

 

【转载】 用 Windows API “GetAdaptersInfo” 获取 MAC 时遇到的问题的更多相关文章

  1. 【转载】Windows api数据类型

    最近在接触windows api函数,看到了很多之前没有看到过的数据类型,发现“个人图书馆”中有个帖子说的挺详细的,特地搬运过来 Windows 数据类型 Delphi 数据类型 描述 LPSTR P ...

  2. Windows API的消息处理机制

    上个学期找实习失利,让我觉得自己基础打得不够牢固,所以在华为实习的这三个月里,每天下班都在复习理论课的知识,顺便刷了一个月的 LeetCode.本来以为找工作是势在必得了,结果这个学期秋季校招的坑爹经 ...

  3. Windows API 磁盘

    这里的磁盘就是你的C,D,E,F,G盘啦 那么来看看吧windows Api来获取信息的呢 (1)DWORD GetLogicalDrives(void) 返回值是一个32位整形 32位每一位表示一个 ...

  4. Windows API 错误码

    在多数情况下,windows API在发生错误时很少抛出异常,多数是通过函数返回值进行处理.(windows api中无返回值的函数很少.) windows api错误处理通常按照以下方式:首先api ...

  5. 获取mac地址方法之一 GetAdaptersInfo()

    GetAdaptersInfo -20151116 防止返回的mac出现null 20151116 From:http://blog.csdn.net/weiyumingwww/article/det ...

  6. c++ windows 获取mac地址

    c++ windows 获取mac地址 GetAdaptersInfo 用windows api获取mac地址和硬盘id编号 aa

  7. 获取信息的有关Windows API(最有意思是OpenProcess和GetProcessMemoryInfo)

    1.窗口信息MS为我们提供了打开特定桌面和枚举桌面窗口的函数.hDesk = OpenDesktop(lpszDesktop, 0, FALSE, DESKTOP_ENUMERATE);// 打开我们 ...

  8. 使用 SendARP 获取 MAC 地址(使用SendARP API函数,很多相关文章)

    ARP 协议地址解析协议(ARP)是通过解析网路层地址来找寻数据链路层地址的一个在网络协议包中极其重要的网络传输协议.ARP 最初在 1982 年的 RFC 826 中提出并纳入互联网标准 STD 3 ...

  9. 【转载】获取MAC地址方法大全

    From:http://blog.csdn.net/han2814675/article/details/6223617 Windows平台下用C++代码取得机器的MAC地址并不是一件简单直接的事情. ...

随机推荐

  1. org.springframework-jdbc

    Spring JDBC模板类—org.springframework.jdbc.core.JdbcTemplate 博客分类: spring JDBCSpringSQL编程数据结构  今天看了下Spr ...

  2. cmd命令 启动 和关闭sql服务

    (1)在操作系统的任务栏中单击“开始”菜单,选择“运行”命令,在下拉列表框中输入“cmd”命令,单击“确定”按钮. (2)输入如下命令,即可通过SQL Server命令行启动.停止或暂停的服务. SQ ...

  3. cordova 5.0版本说明

    2015/04/21发布Cordova 5.0.0! 1)插件从Cordova plugins registry(CPR)全部移到npm,并且重新命名 ***org.apache.cordova.* ...

  4. 20145219 《Java程序设计》第16周课程总结

    20145219 <Java程序设计>第16周课程总结 每周读书笔记(即学习总结)链接汇总 第0周问卷调查 第1周读书笔记 第2周读书笔记 第3周读书笔记 第4周读书笔记 第5周读书笔记 ...

  5. Book Review of "The Practice of Programming" (Ⅰ)

    The Practice of Programming In the preface, the author illustrates four basic principles of programm ...

  6. [SCOI2005]扫雷Mine

    1088: [SCOI2005]扫雷Mine Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 2028  Solved: 1187[Submit][St ...

  7. PHP正则表达式 /i, /is, /s, /isU等 都是些什么东西呢?

    PHP正则表达式 /i, /is, /s, /isU等 都是些什么东西呢? i 不区分大小写 s 模式中的圆点元字符(.)匹配所有的字符,包括换行符 x 模式中的空白字符除了被转义的或在字符类中的以外 ...

  8. Bellman-Ford算法 - 有向图单源最短路径

    2017-07-27  08:58:08 writer:pprp 参考书目:张新华的<算法竞赛宝典> Bellman-Ford算法是求有向图单源最短路径的,dijkstra算法的条件是图中 ...

  9. Hibernate -- 项目结构模型改造, 加 Utils 和 Dao层

    示例代码: App.java 模拟客户端 /** * 模拟客户端 */ public class App { @Test public void saveCustomer(){ CustomerDao ...

  10. dataframe 列名重新排序

    在用list包含多个dict的模式生成dataframe时,由于dict的无序性,而uci很多数据的特征名直接是1,2,3...,生成的dataframe和原生的不一样, 为了方便观看和使用,我们将其 ...