方案選擇對比
獲取 MAC 位址常見方法:NetBIOS、SNMP、GetAdaptersInfo。實測比較:
| 方法 | 網線拔除時 | 重複 MAC | 備註 |
| NetBIOS | ❌ 失敗 | - | 需網路連線 |
| SNMP | ✅ 成功 | ⚠️ 易重複 | 依賴 SNMP 服務 |
| GetAdaptersInfo | ✅ 成功 | ✅ 無重複 | 推薦方案 |
GetAdaptersInfo 既有問題與解決方案
| 問題 | 解決策略 | 驗證環境 |
| 區分實體/虛擬網卡 | Description 含 "PCI" 字串 → 實體網卡 | 3 台機器測試通過 |
| 區分無線/有線網卡 | Type == 71 (IF_TYPE_IEEE80211) → 無線網卡 | 2 張無線網卡測試通過 |
| 「停用」的網卡無法獲取 | 系統行為限制,無完美解法 | - |
完整實作代碼
#include <atlbase.h>
#include <atlconv.h>
#include <iphlpapi.h>
#pragma comment(lib, "Iphlpapi.lib")
int main() {
PIP_ADAPTER_INFO pAdapterInfo = (IP_ADAPTER_INFO*)malloc(sizeof(IP_ADAPTER_INFO));
ULONG ulOutBufLen = sizeof(IP_ADAPTER_INFO);
// 第一次呼叫取得所需緩衝區大小
if (GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) != ERROR_SUCCESS) {
free(pAdapterInfo);
pAdapterInfo = (IP_ADAPTER_INFO*)malloc(ulOutBufLen);
}
DWORD dwRetVal = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen);
if (dwRetVal == NO_ERROR) {
PIP_ADAPTER_INFO pAdapter = pAdapterInfo;
while (pAdapter) {
// 過濾條件:實體網卡(含PCI) 或 無線網卡(Type=71)
if (strstr(pAdapter->Description, "PCI") != NULL || pAdapter->Type == 71) {
printf("------------------------------------------------------------\n");
printf("Adapter Name: \t%s\n", pAdapter->AdapterName);
printf("Adapter Desc: \t%s\n", pAdapter->Description);
printf("Adapter Addr: \t");
for (UINT i = 0; i < pAdapter->AddressLength; i++) {
printf("%02X%c", pAdapter->Address[i],
(i == pAdapter->AddressLength - 1) ? '\n' : '-');
}
printf("Adapter Type: \t%d\n", pAdapter->Type);
printf("IP Address: \t%s\n", pAdapter->IpAddressList.IpAddress.String);
printf("IP Mask: \t%s\n", pAdapter->IpAddressList.IpMask.String);
}
pAdapter = pAdapter->Next;
}
} else {
printf("GetAdaptersInfo 失敗, 錯誤碼: %lu\n", dwRetVal);
}
free(pAdapterInfo);
return 0;
}
關鍵結構體說明
typedef struct _IP_ADAPTER_INFO {
struct _IP_ADAPTER_INFO* Next; // 下一個介面卡
DWORD ComboIndex; // 介面卡索引
char AdapterName[MAX_ADAPTER_NAME_LENGTH + 4]; // 介面卡名稱
char Description[MAX_ADAPTER_DESCRIPTION_LENGTH + 4]; // 描述字串
UINT AddressLength; // MAC 位址長度 (通常為 6)
BYTE Address[MAX_ADAPTER_ADDRESS_LENGTH]; // MAC 位址位元組
DWORD Index; // 介面卡索引
UINT Type; // 介面卡類型 (MIB_IF_TYPE_*)
UINT DhcpEnabled; // 是否啟用 DHCP
PIP_ADDR_STRING CurrentIpAddress; // 目前 IP 位址
IP_ADDR_STRING IpAddressList; // IP 位址列表
IP_ADDR_STRING GatewayList; // 閘道列表
IP_ADDR_STRING DhcpServer; // DHCP 伺服器
BOOL HaveWins; // 是否有 WINS
IP_ADDR_STRING PrimaryWinsServer; // 主要 WINS
IP_ADDR_STRING SecondaryWinsServer; // 次要 WINS
time_t LeaseObtained; // 租約取得時間
time_t LeaseExpires; // 租約到期時間
} IP_ADAPTER_INFO;
常見網卡類型常數 (MIB_IF_TYPE_*)
| 常數值 | 名稱 | 說明 |
| 6 | IF_TYPE_ETHERNET_CSMACD | 乙太網路 |
| 71 | IF_TYPE_IEEE80211 | 無線網路 (WiFi) |
| 24 | IF_TYPE_SOFTWARE_LOOPBACK | 軟體回環 |
| 131 | IF_TYPE_TUNNEL | 通道介面 |
| 144 | IF_TYPE_IEEE1394 | FireWire |
進階應用建議
- 授權綁定:取得第一張實體網卡 MAC 作為軟體授權指紋
- 多網卡策略:優先選擇有效 IP、閘道非空、Type=6 的乙太網卡
- 跨平台考量:Linux 下讀取
/sys/class/net/*/address 或 ioctl(SIOCGIFHWADDR)
- 虛擬化環境:VMware/Hyper-V 虛擬網卡 Description 通常不含 "PCI",可有效過濾
此方案在實體機、虛擬機、筆電 (有線+無線) 等環境均表現穩定,是 Windows 平台獲取 MAC 位址的首選實作。