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 = 'SYSTEMCurrentControlSetControlClass{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的说明,除了类型速率,也包含其他非常多的信息。