MAC地址作為硬件唯一標識,在很多時候會被使用,如在軟件授權方面,很多軟件在產生機器碼時會采用CPUID或MAC地址,或使用MAC地址來做一對一綁定。
相信很多人會碰到以下問題:
1)獲取的是VMWare的網卡MAC地址
2)獲取的是VPN的網卡MAC地址
VMWare或VPN軟件的安裝卸載都會導致獲取的MAC地址變化,所以我們需要正確獲取當前物理網卡的MAC地址。
下面是根據網卡實例ID判斷是否為物理網卡的Delphi代碼(RAD Studio 10.1版本的):
const NCF_VIRTUAL =$01; // 說明組件是個虛擬適配器 NCF_SOFTWARE_ENUMERATED = $02;// 說明組件是一個軟件模擬的適配器 NCF_PHYSICAL = $04; // 說明組件是一個物理適配器 NCF_HIDDEN = $08; //說明組件不顯示用戶接口 NCF_NO_SERVICE = $10; // 說明組件沒有相關的服務(設備驅動程序) NCF_NOT_USER_REMOVABLE = $20; // 說明不能被用戶刪除(例如,通過控制面板或設備管理器) NCF_MULTIPORT_INSTANCED_ADAPTER = $40; // 說明組件有多個端口,每個端 口作為單獨的設備安裝。 // 每個 端口有自己的hw_id(組件ID) 並可被單獨安裝, // 這只適合於 EISA適配器 NCF_HAS_UI = $80; // 說明組件支持用戶接口(例如,Advanced Page或Customer Properties Sheet) NCF_FILTER = $400; // 說明組件是一個過濾器 function IsPhysicalAdapter(sAdapterGUID: string; bOnlyPCI: Boolean=False):Boolean; const NET_CARD_KEY_PATH = 'SYSTEM\CurrentControlSet\Control\Class\{4d36e972-e325-11ce-bfc1-08002be10318}'; var Reg: TRegistry; sList: TStrings; vCharacteristics: DWORD; I: Integer; begin Result := False; sList:=TStringList.Create; Reg:=TRegistry.Create; try Reg.RootKey := HKEY_LOCAL_MACHINE; if not Reg.OpenKey(NET_CARD_KEY_PATH, False) then Exit; Reg.GetKeyNames(sList); Reg.CloseKey; if sList.Count>0 then for I := 0 to Pred(sList.Count) do if Reg.OpenKey(NET_CARD_KEY_PATH+'\'+sList.Strings[I], False) then begin try if sAdapterGUID.ToUpper.Trim.Equals(Reg.ReadString('NetCfgInstanceId').ToUpper.Trim) then begin vCharacteristics := DWORD(Reg.ReadInteger('Characteristics')); Result := (NCF_PHYSICAL and vCharacteristics) = NCF_PHYSICAL; Break; end; finally Reg.CloseKey; end; end; finally Reg.Free; sList.Free; end; end;
上面IsPhysicalAdapter的參數網卡實例ID通過GetAdaptersInfo即可得到,綜合起來的具體代碼如下:
注意:
調用 GetAdaptersInfo 可以直接引用下面2個單元
Winapi.IpTypes,
Winapi.IpHlpApi
function GetMacAddress(): string; var dwRet: DWORD; AI,Work : PIpAdapterInfo; sGUID, sTmp, sLastMAC, sFirstMAC: string; I: Integer; uSize: ULONG; begin Result := ''; sLastMAC := ''; sFirstMAC := ''; uSize := SizeOf(TIpAdapterInfo); GetMem(AI,uSize); dwRet := GetAdaptersInfo(AI,uSize); if (dwRet = ERROR_BUFFER_OVERFLOW)then begin FreeMem(AI); GetMem(AI,uSize); dwRet := GetAdaptersInfo(AI,uSize); end; try if (dwRet <> ERROR_SUCCESS)then Exit; Work := AI; while Work<>nil do begin try sGUID := string(AnsiString(Work.AdapterName)); sTmp := string(AnsiString(Work.Description)); // 名稱描述出現VMWare,直接忽略 if Pos('VMWare', sTmp)>0 then Continue; // 配置的ID地址不正常,忽略 if Work.AddressLength=0 then Continue; // 將網卡MAC地址轉成字符串 sLastMAC := ''; for I:=0 to Work.AddressLength-1 do begin sLastMAC := sLastMAC + Format('%.2x', [Work.Address[I]]); end; if sFirstMAC='' then sFirstMAC := sLastMAC; // 不是物理網卡,忽略 if not IsPhysicalAdapter(sGUID) then Continue; Result := sLastMAC; //找到第一個物理網卡后退出 Break; finally Work := Work.Next; end; end; // 找不到物理網卡MAC,返回第一個即可 if Result='' then Result := sFirstMAC; finally FreeMem(AI); end; end;
理論上從軟件層面是無法直接判斷一個網絡適配器硬件到底是有線還是無線的,因為不管是miniPCIE還是普通PCI接口的網卡,都可以是有線也可以是無線,
但是也有以下的方法判斷:
1)網卡可以通過名稱描述上判斷:一般有線網卡名稱上帶PCI,無線帶WLAN 或Wireless,此方法不依賴於網絡是否連接,但對那些自定義驅動名稱的高人當然無效
2)獲取網絡配置信息,也就是獲取網絡連接里的整個列表,包含物理網卡和寬帶連接,VPN等各種連接設備信息,再查看連接類型,根據MSDN的說明,類型包含ETHERNET/PPP/IEEE80211等等,甚至包含移動手機卡的類型。
3)獲取網絡速率,和2)一樣也是獲取網絡連接的配置信息,100/10Mbps一般是有線,54Mbps或其他一般為WLAN連接,雖說WLAN並不包含一個最高的速度標准,但是普通2.4G或5G的WIFI速度也快不到哪去。
注:
方法 2)和3)中的網絡類型和網絡速率均是通過 API GetIfEntry配合GetAdaptersInfo 獲取(或直接通過 GetIfTable獲取)網絡配置信息得到的,代碼MSDN也有
具體查看MSDN中對此函數返回的結構體MIB_IFROW字段dwType和dwSpeed的說明,除了類型速率,也包含其他非常多的信息。