在 Delphi 中通过 GetAdaptersInfo 获取有线物理网卡IP、MAC地址

在 Delphi 中通过 GetAdaptersInfo 获取有线物理网卡(排除虚拟网卡、WiFi、蓝牙、VPN 等),核心是精准过滤网卡类型和特征。以下是优化后的完整实现,解决多网卡场景下精准筛选有线物理网卡的问题:

核心思路

  1. 基础过滤:筛选 Type = MIB_IF_TYPE_ETHERNET(以太网类型),排除 WiFi(IF_TYPE_IEEE80211=71)、蓝牙、隧道 / 虚拟网卡;
  2. 深度过滤:排除虚拟网卡(如 VMware/VirtualBox/Hyper-V 等虚拟化网卡、VPN 适配器);
  3. 优先级处理:多有线网卡时,优先选择非虚拟、有有效 MAC(6 字节)、已启用的物理网卡。

完整 Delphi 实现

unit WiredNicHelper;

interface

uses
  Windows, SysUtils, WinSock2;

type
  // 适配 GetAdaptersInfo 的结构体定义
  PIP_ADAPTER_INFO = ^TIP_ADAPTER_INFO;
  TIP_ADAPTER_INFO = record
    Next: PIP_ADAPTER_INFO;
    ComboIndex: DWORD;
    AdapterName: array[0..MAX_ADAPTER_NAME_LENGTH + 3] of Char;
    Description: array[0..MAX_ADAPTER_DESCRIPTION_LENGTH + 3] of Char;
    AddressLength: UINT;
    Address: array[0..MAX_ADAPTER_ADDRESS_LENGTH - 1] of Byte;
    Index: DWORD;
    Type: UINT;
    DhcpEnabled: BOOL;
    CurrentIpAddress: PIP_ADDR_STRING;
    IpAddressList: TIP_ADDR_STRING;
    GatewayList: TIP_ADDR_STRING;
    DhcpServer: TIP_ADDR_STRING;
    HaveWins: BOOL;
    PrimaryWinsServer: TIP_ADDR_STRING;
    SecondaryWinsServer: TIP_ADDR_STRING;
    LeaseObtained: DWORD;
    LeaseExpires: DWORD;
  end;

const
  // 常量定义(匹配 Windows 官方值)
  MAX_ADAPTER_NAME_LENGTH = 256;
  MAX_ADAPTER_DESCRIPTION_LENGTH = 128;
  MAX_ADAPTER_ADDRESS_LENGTH = 8;
  MIB_IF_TYPE_ETHERNET = 6;        // 有线以太网类型
  IF_TYPE_IEEE80211 = 71;          // WiFi 无线类型
  ERROR_BUFFER_OVERFLOW = 111;     // 缓冲区不足错误码

// 声明 GetAdaptersInfo API(来自 iphlpapi.dll)
function GetAdaptersInfo(pAdapterInfo: PIP_ADAPTER_INFO; var pOutBufLen: ULONG): DWORD; stdcall; 
  external 'iphlpapi.dll' name 'GetAdaptersInfo';

// 核心函数:获取有线物理网卡信息(返回第一个有效网卡的MAC和描述)
function GetPhysicalWiredNic(out MacAddr, NicDesc: string): Boolean;

implementation

// 辅助函数:判断是否为虚拟网卡(过滤虚拟化/虚拟专用网络适配器)
function IsVirtualNic(const Desc: string): Boolean;
const
  // 虚拟网卡特征关键词(可根据需要扩展)
  VirtualKeywords: array[0..7] of string = (
    'VMware', 'VirtualBox', 'Hyper-V', 'VPN', 'TAP-Win32', 
    'Microsoft Loopback', 'SoftEther', 'WireGuard'
  );
var
  i: Integer;
begin
  Result := False;
  for i := Low(VirtualKeywords) to High(VirtualKeywords) do
  begin
    if Pos(VirtualKeywords[i], Desc) > 0 then
    begin
      Result := True;
      Break;
    end;
  end;
end;

function GetPhysicalWiredNic(out MacAddr, NicDesc: string): Boolean;
var
  pAdapterInfo: PIP_ADAPTER_INFO;
  ulOutBufLen: ULONG;
  dwRet: DWORD;
  pAdapter: PIP_ADAPTER_INFO;
  Desc: string;
begin
  Result := False;
  MacAddr := '';
  NicDesc := '';
  ulOutBufLen := 0;
  pAdapterInfo := nil;

  try
    // 第一步:调用 GetAdaptersInfo 获取所需缓冲区大小
    dwRet := GetAdaptersInfo(nil, ulOutBufLen);
    if dwRet <> ERROR_BUFFER_OVERFLOW then
    begin
      // 无网卡信息或其他错误
      Exit;
    end;

    // 分配缓冲区内存
    GetMem(pAdapterInfo, ulOutBufLen);
    dwRet := GetAdaptersInfo(pAdapterInfo, ulOutBufLen);
    if dwRet <> NO_ERROR then
    begin
      Exit;
    end;

    // 第二步:遍历所有网卡,筛选有线物理网卡
    pAdapter := pAdapterInfo;
    while pAdapter <> nil do
    begin
      Desc := StrPas(pAdapter^.Description); // 转换为 Delphi 字符串

      // 过滤条件1:仅保留以太网类型,排除 WiFi、蓝牙
      if (pAdapter^.Type <> MIB_IF_TYPE_ETHERNET) or 
         (pAdapter^.Type = IF_TYPE_IEEE80211) or 
         (Pos('Bluetooth', Desc) > 0) then
      begin
        pAdapter := pAdapter^.Next;
        Continue;
      end;

      // 过滤条件2:排除虚拟网卡
      if IsVirtualNic(Desc) then
      begin
        pAdapter := pAdapter^.Next;
        Continue;
      end;

      // 过滤条件3:MAC地址必须是6字节(物理网卡标准长度)
      if pAdapter^.AddressLength <> 6 then
      begin
        pAdapter := pAdapter^.Next;
        Continue;
      end;

      // 格式化MAC地址(XX:XX:XX:XX:XX:XX)
      MacAddr := Format('%.2X:%.2X:%.2X:%.2X:%.2X:%.2X', [
        pAdapter^.Address[0], pAdapter^.Address[1], pAdapter^.Address[2],
        pAdapter^.Address[3], pAdapter^.Address[4], pAdapter^.Address[5]
      ]);
      NicDesc := Desc;

      Result := True;
      Break; // 找到第一个符合条件的有线物理网卡即退出
    end;
  finally
    // 释放内存(无论是否成功,都要释放)
    if Assigned(pAdapterInfo) then
      FreeMem(pAdapterInfo);
  end;
end;

end.

使用示例

program GetWiredNicDemo;

uses
  Windows, SysUtils, WiredNicHelper;

begin
  try
    var Mac, Desc: string;
    if GetPhysicalWiredNic(Mac, Desc) then
    begin
      Writeln('找到有线物理网卡:');
      Writeln('网卡描述:', Desc);
      Writeln('MAC地址:', Mac);
    end
    else
    begin
      Writeln('未找到符合条件的有线物理网卡');
    end;
  except
    on E: Exception do
      Writeln('错误:', E.Message);
  end;
  Readln; // 暂停查看结果
end.

关键优化点(解决多网卡筛选问题)

  1. 虚拟网卡过滤

    通过 IsVirtualNic 函数过滤含虚拟化关键词(VMware、VirtualBox、Hyper-V 等)的网卡,避免误取虚拟网卡。

  2. 精准类型判断

    仅保留 Type = MIB_IF_TYPE_ETHERNET(以太网),排除 WiFi(71)、蓝牙适配器。

  3. MAC 地址校验

    物理网卡的 MAC 地址长度固定为 6 字节,过滤非标准长度的无效网卡。

  4. 内存安全

    用 try...finally 确保 GetMem 分配的内存必释放,避免内存泄漏。

扩展说明

  • 多有线网卡优先级:若需优先选择 “已联网” 的有线网卡,可额外判断 pAdapter^.IpAddressList.IpAddress.String 是否非空;
  • 关键词扩展:若有其他虚拟网卡(如 Cisco AnyConnect、OpenVPN 等),可在 VirtualKeywords 数组中添加对应关键词;
  • 权限要求:部分系统需以管理员权限运行,否则可能无法获取完整网卡信息;
  • 64 位兼容:编译时需将项目目标平台设为 64位(若需运行在 64 位系统),Delphi 会自动适配指针长度。

常见问题排查

  • 若返回 “未找到网卡”:检查是否有物理有线网卡、是否禁用、是否为虚拟网卡;
  • 若 MAC 地址为空:确认网卡驱动正常,且未被系统隐藏;
  • 若报错 “找不到 iphlpapi.dll”:该 DLL 是 Windows XP 及以上系统自带,无需额外部署。
THE END