首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC 教學

VC++ 獲取實體網卡 MAC 位址的最佳實踐:GetAdaptersInfo 方案詳解

     編輯:VC 教學

前言

在 Windows 開發中,獲取網卡 MAC 位址是常見需求。常見方法包括 NetBIOS、SNMP、GetAdaptersInfo 等。經實測,NetBIOS 在網線拔除時無法獲取 MAC;SNMP 易產生重複資料;而 GetAdaptersInfo 表現最佳——即使網線斷開也能準確獲取,且不重複。本文分享基於此 API 的完整實作,並解決物理/虛擬、有線/無線網卡的區分問題。

核心問題與解決策略

1. 區分物理網卡與虛擬網卡

觀察 pAdapter->Description 字串,若包含 "PCI" 關鍵字,判定為物理網卡(經 3 台機器驗證有效)。

2. 區分無線與有線網卡

依據 pAdapter->Type 欄位,值為 71 (IF_TYPE_IEEE80211) 者為無線網卡(經 2 張無線網卡驗證)。

3. 已知限制

  • 「已停用」的網卡無法獲取(API 行為限制)。
  • 上述啟發式規則非微軟官方標準,僅供參考,實際部署建議結合註冊表 HKLM\SYSTEM\CurrentControlSet\Control\Class\{4D36E972-E325-11CE-BFC1-08002BE10318} 下的 PnPInstanceID 或 NetCfgInstanceId 做進一步驗證。

完整範例程式碼

#include <windows.h>
#include <iphlpapi.h>
#include <stdio.h>
#include <stdlib.h>

#pragma comment(lib, "Iphlpapi.lib")

int main()
{
    PIP_ADAPTER_INFO pAdapterInfo = (IP_ADAPTER_INFO*)malloc(sizeof(IP_ADAPTER_INFO));
    if (!pAdapterInfo) return -1;

    ULONG ulOutBufLen = sizeof(IP_ADAPTER_INFO);
    DWORD dwRetVal = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen);

    if (dwRetVal == ERROR_BUFFER_OVERFLOW) {
        free(pAdapterInfo);
        pAdapterInfo = (IP_ADAPTER_INFO*)malloc(ulOutBufLen);
        if (!pAdapterInfo) return -1;
        dwRetVal = GetAdaptersInfo(pAdapterInfo, &ulOutBufLen);
    }

    if (dwRetVal == NO_ERROR) {
        PIP_ADAPTER_INFO pAdapter = pAdapterInfo;
        while (pAdapter) {
            // 篩選:物理網卡(含 PCI) 或 無線網卡(Type==71)
            bool isPhysical = (strstr(pAdapter->Description, "PCI") != NULL);
            bool isWireless = (pAdapter->Type == 71); // IF_TYPE_IEEE80211

            if (isPhysical || isWireless) {
                printf("------------------------------------------------------------\n");
                printf("Adapter Name : %s\n", pAdapter->AdapterName);
                printf("Description  : %s\n", pAdapter->Description);
                printf("MAC Address  : ");
                for (UINT i = 0; i < pAdapter->AddressLength; i++) {
                    printf("%02X%c", pAdapter->Address[i],
                           (i == pAdapter->AddressLength - 1) ? '\n' : '-');
                }
                printf("Adapter Type : %u\n", pAdapter->Type);
                printf("IP Address   : %s\n", pAdapter->IpAddressList.IpAddress.String);
                printf("IP Mask      : %s\n", pAdapter->IpAddressList.IpMask.String);
            }
            pAdapter = pAdapter->Next;
        }
    } else {
        printf("GetAdaptersInfo 失敗,錯誤碼: %lu\n", dwRetVal);
    }

    if (pAdapterInfo) free(pAdapterInfo);
    return 0;
}

關鍵點說明

  • 緩衝區兩次呼叫:首次呼叫取得所需大小,再分配記憶體第二次呼叫,避免緩衝區不足。
  • 記憶體管理:使用 malloc/free,避免 MFC 專用 GlobalAlloc,提高可移植性。
  • MAC 格式化:以 %02X 輸出,並以連字號分隔,符合慣例。
  • 錯誤處理:檢查 ERROR_BUFFER_OVERFLOW 並給予明確錯誤訊息。

進階建議

若需更穩健的網卡分類,建議改用 GetAdaptersAddresses (Windows XP SP1+) 並搭配 IF_OPER_STATUS、PhysicalMediumType 等欄位,或直接查詢註冊表 NetCfgInstanceId 對應的 PnPInstanceID 判斷是否為 PCI\\ 開頭。對於現代專案,優先考慮 GetAdaptersAddresses 以支援 IPv6 並獲得更豐富的屬性。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved