【问题标题】:How to use ResolveIpNetEntry2 with Delphi?如何在 Delphi 中使用 ResolveIpNetEntry2?
【发布时间】:2016-11-14 17:49:31
【问题描述】:

我想从同一本地网络中主机的 IP 获取 MAC 地址。我宁愿从本地缓存中获取此信息,而不是发送新的 ARP ARP 请求。我发现ResolveIpNetEntry2 应该是我需要的。

不幸的是,我没有找到任何具有该功能的 Delphi 代码示例。更糟糕的是,我什至没有找到该函数及其数据类型的任何 Delphi 标头。所以我尝试自己转换它们。好吧,它编译了,但我得到 ERROR_INVALID_PARAMETER (87),所以显然我转换了一些错误。

谁能告诉我如何改正它?

const
  IF_MAX_PHYS_ADDRESS_LENGTH = 32;
type
  NET_LUID = record
    case Word of
     1: (Value: Int64;);
     2: (Reserved: Int64;);
     3: (NetLuidIndex: Int64;);
     4: (IfType: Int64;);
  end;

  NL_NEIGHBOR_STATE = (
    NlnsUnreachable=0,
    NlnsIncomplete,
    NlnsProbe,
    NlnsDelay,
    NlnsStale,
    NlnsReachable,
    NlnsPermanent,
    NlnsMaximum);
  PMIB_IPNET_ROW2 = ^MIB_IPNET_ROW2;
  MIB_IPNET_ROW2 = record
    Address: LPSOCKADDR; //SOCKADDR_INET
    InterfaceIndex: ULONG; //NET_IFINDEX
    InterfaceLuid: NET_LUID;
    PhysicalAddress: array [0..IF_MAX_PHYS_ADDRESS_LENGTH - 1] of  UCHAR;
    PhysicalAddressLength: ULONG;
    State: NL_NEIGHBOR_STATE;
    Union: record
      case Integer of
        0: (IsRouter: Boolean;
            IsUnreachable: Boolean);
        1: (Flags: UCHAR);
      end;
    ReachabilityTime: record
      case Integer of
        0: (LastReachable: ULONG);
        1: (LastUnreachable: ULONG);
    end;
  end;

function ResolveIp(const AIp: String; AIfIndex: ULONG): String;
type
  TResolveIpNetEntry2Func = function (Row: PMIB_IPNET_ROW2; const SourceAddress: LPSOCKADDR): DWORD; stdcall; //NETIOAPI_API
const
  IphlpApiDll = 'iphlpapi.dll';
var
  hIphlpApiDll: THandle;
  ResolveIpNetEntry2: TResolveIpNetEntry2Func;
  dw: DWORD;
  Row: PMIB_IPNET_ROW2;
  SourceAddress: LPSOCKADDR;
  IpAddress: LPSOCKADDR;
begin
  hIphlpApiDll := LoadLibrary(IphlpApiDll);
  if hIphlpApiDll = 0 then
    Exit;
  ResolveIpNetEntry2 := GetProcAddress(hIphlpApiDll, 'ResolveIpNetEntry2');
  if (@ResolveIpNetEntry2 = nil) then
    Exit;

  IpAddress := AllocMem(SizeOf(IpAddress));
  IpAddress.sa_family := AF_INET;
  IpAddress.sa_data := PAnsiChar(AIp);
  Row := AllocMem(SizeOf(Row));
  Row.Address := IpAddress;
  Row.InterfaceIndex := AIfIndex;
  SourceAddress := 0;
  dw := ResolveIpNetEntry2(Row, SourceAddress);
  //...
end;

【问题讨论】:

  • 为什么不直接获取 ARP -A IPNUMBER 输出并从中提取 MAC 地址?
  • 这里你可以看到一个如何捕捉ARP命令stackoverflow.com/questions/9119999/…的输出的例子
  • 我知道如何通过命令行发送 ARP 请求,甚至更好地通过 API (SendArp())。在这种情况下,出于多种原因,我更喜欢获取缓存信息而不是发送新请求。
  • 建议的 ARP -A 命令返回缓存的内容。 technet.microsoft.com/en-us/library/cc940107.aspx
  • 你是对的:arp.exe 执行缓存查找而不是发送新查找。但最后它只是在内部执行一些 API 调用,很可能是 ResolveIpNetEntry2。我总是更喜欢直接使用 API 而不是解析命令行输出。

标签: delphi winapi


【解决方案1】:

根据ResolveIpNetEntry2() documentation

如果函数失败,返回值为以下错误码之一。

...

ERROR_INVALID_PARAMETER
向函数传递了无效参数。如果在 Row 参数中传递了 NULL 指针,Row 参数指向的 MIB_IPNET_ROW2 的 Address 成员未设置为有效的 IPv4 或 IPv6 地址,或者未将 InterfaceLuid 或 InterfaceIndex 成员设置为有效的 IPv4 或 IPv6 地址,则返回此错误。 Row 参数指向的 MIB_IPNET_ROW2 未指定。如果在 Address 成员中传递了环回地址,也会返回此错误。

您对 API 结构的翻译通常不正确。您有几个字段错误,这会影响字段偏移量和大小。比如位域。但更重要的是,您对MIB_IPNET_ROW2.Address 字段的声明是完全错误的。它根本不是指向外部SOCKADDR 记录的指针。这是一个SOCKADDR_INET 记录,存在于MIB_IPNET_ROW2 本身内部,而不是外部。

您也没有正确分配或初始化内存块,这也适用于上述文档。

而且,即使您正确翻译了 API,您也没有正确地将您的 AIp 字符串转换为 SOCKADDR。您不能简单地对其进行类型转换,您需要使用inet_addr()InetPton() 或其他等效函数将其从字符串字符实际转换为网络地址整数。所以这也适用于上述文档。

你很幸运,你的代码没有完全崩溃。

试试类似的方法:

{$MINENUMSIZE 4}

const
  IF_MAX_PHYS_ADDRESS_LENGTH = 32;

type
  NET_LUID_INFO = record
    Reserved: array [0..2] of UCHAR; // ULONG64:24
    NetLuidIndex: array [0..2] of UCHAR; // ULONG64:24
    IfType: array [0..1] of UCHAR; // ULONG64:16
    //
    // TODO: if you need to access these values, define
    // some property getters/setters to translate them
    // to/from UInt64...
  end;

  NET_LUID = record
    case Integer of
      0: (Value: UInt64);
      1: (Info: NET_LUID_INFO);
  end;

  NL_NEIGHBOR_STATE = (
    NlnsUnreachable = 0,
    NlnsIncomplete,
    NlnsProbe,
    NlnsDelay,
    NlnsStale,
    NlnsReachable,
    NlnsPermanent,
    NlnsMaximum);

  PSOCKADDR_INET = ^SOCKADDR_INET;
  SOCKADDR_INET = record
    case Integer of
      0: (Ipv4: SOCKADDR_IN);
      1: (Ipv6: SOCKADDR_IN6);
      2: (si_family: ADDRESS_FAMILY);
  end;

  NETIO_STATUS = DWORD;
  NET_IFINDEX = ULONG;

  PMIB_IPNET_ROW2 = ^MIB_IPNET_ROW2;
  MIB_IPNET_ROW2 = record
    Address: SOCKADDR_INET;
    InterfaceIndex: NET_IFINDEX;
    InterfaceLuid: NET_LUID;
    PhysicalAddress: array [0..IF_MAX_PHYS_ADDRESS_LENGTH - 1] of UCHAR;
    PhysicalAddressLength: ULONG;
    State: NL_NEIGHBOR_STATE;
    Flags: UCHAR;
    ReachabilityTime: record
      case Integer of
        0: (LastReachable: ULONG);
        1: (LastUnreachable: ULONG);
    end;

    function IsRouter: Boolean;
    function IsUnreachable;
  end;

function MIB_IPNET_ROW2.IsRouter: Boolean;
begin
  Result := (Flags and $01) <> 0;
end;

function MIB_IPNET_ROW2.IsUnreachable;
begin
  Result := (Flags and $02) <> 0;
end;

function ResolveIp(const AIp: String; AIfIndex: ULONG): String;
type
  TResolveIpNetEntry2Func = function (Row: PMIB_IPNET_ROW2; const SourceAddress: PSOCKADDR_INET): NETIO_STATUS; stdcall;
const
  IphlpApiDll = 'iphlpapi.dll';
var
  hIphlpApiDll: THandle;
  ResolveIpNetEntry2: TResolveIpNetEntry2Func;
  status: NETIO_STATUS;
  Row: PMIB_IPNET_ROW2;
begin
  Result := '';
  hIphlpApiDll := LoadLibrary(IphlpApiDll);
  if hIphlpApiDll = 0 then
    Exit;
  try
    @ResolveIpNetEntry2 := GetProcAddress(hIphlpApiDll, 'ResolveIpNetEntry2');
    if not Assigned(ResolveIpNetEntry2) then
      Exit;

    New(Row);
    try
      ZeroMemory(Row, SizeOf(MIB_IPNET_ROW2));

      if InetPton(AF_INET, PChar(AIp), @(Row.Address.Ipv4.sin_addr)) = 1 then
        Row.Address.Ipv4.sin_family := AF_INET
      else
      if InetPton(AF_INET6, PChar(AIp), @(Row.Address.Ipv6.sin6_addr)) = 1 then
        Row.Address.Ipv6.sin6_family := AF_INET6
      else
        Exit;

      Row.InterfaceIndex := AIfIndex;

      status := ResolveIpNetEntry2(Row, nil);
      //...
    finally
      Dispose(Row);
    end;
  finally
    FreeLibrary(hIphlpApiDll);
  end;
end;

另外,ResolveIp() 可以在完全不动态分配Row 的情况下编写:

function ResolveIp(const AIp: String; AIfIndex: ULONG): String;
...
var
  ...
  Row: MIB_IPNET_ROW2;
begin
  ...
  ZeroMemory(@Row, SizeOf(Row));
  ...
  status := ResolveIpNetEntry2(@Row, nil);
  ...
end;

【讨论】:

  • 非常感谢! ADDRESS_FAMILY (ULONG?) 和 InetPton 是否包含在某处?很遗憾,Delphi Jedi 没有包含任何适用于 Vista 及更高版本的新标头。
  • ADDRESS_FAMILY 实际上是一个USHORT,它是在 Delphi 的 ShellAPI 单元中定义的(不过放不下)。我认为没有任何 Delphi 单元声明 InetPton(),但您可以在代码中自己声明,它位于 Ws2_32.dll
  • 我可以让 Ansi 版本的 inet_pton 工作,但不幸的是,我不知道如何处理 WSAAPI 信息来获取其 InetPton Unicode 挂件。 function inet_pton(Family: Integer; const AddrString: PChar; PAddrBuf: Pointer): Integer; stdcall; external 'Ws2_32.dll';
  • 如果你read the documentation,你会看到Ansi版本是inet_pton()InetPtonA(),Unicode版本是InetPtonW()
  • 显然我将文档中的第一句话误解为“InetPton 是 Unicode 版本,inet_pton 是 Ansi 版本”,并且不知道如何处理声明中的 WSAAPI。是的,InetPtonW 成功了。非常感谢!
猜你喜欢
  • 2012-03-04
  • 2021-10-25
  • 2014-08-24
  • 2011-05-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多