【问题标题】:GetAdaptersInfo not working on Delphi XE6GetAdaptersInfo 在 Delphi XE6 上不起作用
【发布时间】:2014-06-27 03:41:01
【问题描述】:

我终于硬着头皮买了 XE6,不出所料,Unicode 转换变成了一场噩梦。因此,如果有人能告诉我为什么这个简单的 Windows API 调用失败,那将不胜感激。该函数不返回错误,第一次调用得到正确的缓冲区长度,第二次调用用垃圾填充记录。

这在 Delphi 2007 下工作正常,但在 XE6 上失败,pAdapterinfo 返回记录中包含 unicode 垃圾,即使它在 IpTypes.pas 中使用 AnsiString 显式声明

系统是 Win7(64) 但编译为 32 位。

uses iphlpapi, IpTypes;

function GetFirstAdapterMacAddress:AnsiString;
var pAdapterInfo:PIP_ADAPTER_INFO;
    BufLen,Status:cardinal; i:Integer;
begin
  result:='';
  BufLen:= sizeof(IP_ADAPTER_INFO);
  GetAdaptersInfo(nil, BufLen);
  pAdapterInfo:= AllocMem(BufLen);
  try
    Status:= GetAdaptersInfo(pAdapterInfo,BufLen);
    if (Status <> ERROR_SUCCESS) then
    begin
      case Status of
        ERROR_NOT_SUPPORTED: raise exception.create('GetAdaptersInfo is not supported by the operating ' +
                                     'system running on the local computer.');
        ERROR_NO_DATA: raise exception.create('No network adapter on the local computer.');
      else
        raiselastOSerror;
      end;
      Exit;
    end;
    while (pAdapterInfo^.AddressLength=0) and (pAdapterInfo^.next<>nil) do
     pAdapterInfo:=pAdapterInfo.next;
    if pAdapterInfo^.AddressLength>0 then
    for i := 0 to pAdapterInfo^.AddressLength - 1 do
      result := result + IntToHex(pAdapterInfo^.Address[I], 2);
  finally
    Freemem(pAdapterInfo);
  end;
end;

更新:

我做了更多检查。我创建了一个新的简单应用程序,其中包含一个表单和一个按钮,并在按下按钮时调用该例程并且它起作用了。

不同之处在于...在工作形式中,IP_ADAPTER_INFO 的大小为 640 字节。

在更复杂的应用程序中使用此例程时,它会失败,并且 IP_ADAPTER_INFO 的大小显示为 1192 字节。

此时,编译器似乎单方面决定将结构中的 ansi 字符的类型更改为 unicode 字符。调试器以 unicode 形式显示 AdapterName 和描述字段。我对系统源代码做了 grep,除了 Indy 库之外,库代码中没有声明此数据类型的其他版本,这只是重复。

这里是IPtypes中的数据结构定义

  PIP_ADAPTER_INFO = ^IP_ADAPTER_INFO;
  {$EXTERNALSYM PIP_ADAPTER_INFO}
  _IP_ADAPTER_INFO = record
    Next: PIP_ADAPTER_INFO;
    ComboIndex: DWORD;
    AdapterName: array [0..MAX_ADAPTER_NAME_LENGTH + 3] of AnsiChar;
    Description: array [0..MAX_ADAPTER_DESCRIPTION_LENGTH + 3] of AnsiChar;
    AddressLength: UINT;
    Address: array [0..MAX_ADAPTER_ADDRESS_LENGTH - 1] of BYTE;
    Index: DWORD;
    Type_: UINT;
    DhcpEnabled: UINT;
    CurrentIpAddress: PIP_ADDR_STRING;
    IpAddressList: IP_ADDR_STRING;
    GatewayList: IP_ADDR_STRING;
    DhcpServer: IP_ADDR_STRING;
    HaveWins: BOOL;
    PrimaryWinsServer: IP_ADDR_STRING;
    SecondaryWinsServer: IP_ADDR_STRING;
    LeaseObtained: time_t;
    LeaseExpires: time_t;
  end;

看起来像一个编译器错误。

【问题讨论】:

  • GetAdaptersInfo(nil, BufLen);这个电话的目的是什么?
  • 在 XE2、W7 上工作正常。
  • 第一次调用获取propoer缓冲区长度。在 XE2 中工作?所以这是 XE6 特有的一些问题。
  • 好的,谢谢。它真的只是 API,我猜不出它是怎么搞砸的。检查_IP_ADAPTER_INFO是否有任何不同。
  • 1192 是所有 AnsiChars 都是 Chars 时的大小,包括来自 IP_ADDR_STRING 的那些。多么奇怪!...

标签: delphi delphi-xe6


【解决方案1】:

解释是你的第三方IpTypes单元中声明的类型使用Char。这是 pre-Unicode Delphi 中 AnsiChar 的别名,也是 Unicode Delphi 中 WideChar 的别名。这可以解释您在检查记录内容时看到非 ANSI 文本的事实。

解决方案是修复IpTypes 以在适当的情况下使用AnsiChar 代替Char。最好的方法是使用 Delphi 附带的 IpTypes 而不是您的第三方版本。

最重要的是,第一次调用GetAdaptersInfo 是错误的。您不仅没有检查返回值,而且还为缓冲区传递了nil,并且还传递了一个非零长度。我认为应该是这样的:

BufLen := 0;
if GetAdaptersInfo(nil, BufLen) <> ERROR_BUFFER_OVERFLOW then
  raise ....

当然,你的方法可行,但我在这里只是有点迂腐。调用 API 函数时始终检查错误。

【讨论】:

  • IPtypes 是一个 Delphi 系统文件,它定义了字符串字段为 ansistring。
  • 这不对。它们是 char 数组而不是 Delphi 字符串。
  • 如果你真的看,IpTypes 已经用AnsiChar 数组声明了IP_ADAPTER_INFO,而不是Char 数组。 MSDN documentation 和 Win32 API IpTypes.h 头文件确认 IP_ADAPTER_INFO 确实使用 char 值而不是 TCHAR 值。 IP_ADAPTER_INFO 没有 Unicode 版本。
  • @remy 用户正在使用他们自己的第三方 IpTypes
  • @DavidHeffernan:我在回答中的单位名称中添加了Winapi 以避免这种情况。
【解决方案2】:

你的代码有几个问题:

  1. 在第一次调用计算缓冲区长度时,您根本没有进行任何错误处理。你甚至不需要那个调用,所以摆脱它。

  2. 您没有对后续调用进行足够的错误处理,特别是当GetAdaptersInfo() 需要您分配比您已有的更多内存时,您没有处理ERROR_BUFFER_OVERFLOW 条件。您只为一个适配器分配了足够的内存,但 GetAdaptersInfo() 返回所有适配器的信息,因此需要足够的缓冲区来一次容纳所有适配器。

  3. GetAdaptersInfo()不使用GetLastError(),所以在调用RaiseLastOSError()之前需要先调用SetLastError()

  4. 您正在使用用于分配列表的原始指针循环访问适配器列表,因此如果第一个适配器没有 MAC 地址,则会导致内存泄漏。您需要使用单独的变量作为循环迭代器,以便保留原始指针,以便正确释放它。

  5. 您没有考虑到任何适配器都没有 MAC 地址的可能性,因此在您的 while 循环退出后,您最终将访问一个 nil 指针。

  6. 您的机器上似乎有多个版本的 IpTypes 单元,并且编译器正在寻找一个恰好在 IP_ADAPTER_INFO 记录中使用 Char 而不是 AnsiChar 的版本,因此它的大小和字段偏移量错误。

话虽如此,试试这个:

uses
  Winapi.iphlpapi, Winapi.IpTypes;

function GetFirstAdapterMacAddress: String;
var
  pAdapterList, pAdapter: PIP_ADAPTER_INFO;
  BufLen, Status: DWORD;
  I: Integer;
begin
  Result := '';
  BufLen := 1024*15;
  GetMem(pAdapterList, BufLen);
  try
    repeat
      Status := GetAdaptersInfo(pAdapterList, BufLen);
      case Status of
        ERROR_SUCCESS:
        begin
          // some versions of Windows return ERROR_SUCCESS with
          // BufLen=0 instead of returning ERROR_NO_DATA as documented...
          if BufLen = 0 then begin
            raise Exception.Create('No network adapter on the local computer.');
          end;
          Break;
        end;
        ERROR_NOT_SUPPORTED:
        begin
          raise Exception.Create('GetAdaptersInfo is not supported by the operating system running on the local computer.');
        end;
        ERROR_NO_DATA:
        begin
          raise Exception.Create('No network adapter on the local computer.');
        end;
        ERROR_BUFFER_OVERFLOW:
        begin
          ReallocMem(pAdapterList, BufLen);
        end;
      else
        SetLastError(Status);
        RaiseLastOSError;
      end;
    until False;

    pAdapter := pAdapterList;
    while pAdapter <> nil do
    begin
      if pAdapter^.AddressLength > 0 then
      begin
        for I := 0 to pAdapter^.AddressLength - 1 do begin
          Result := Result + IntToHex(pAdapter^.Address[I], 2);
        end;
        Exit;
      end;
      pAdapter := pAdapter^.next;
    end;
  finally
    FreeMem(pAdapterList);
  end;
end;

【讨论】:

  • 您的第 2 点看起来不对。他确实要求缓冲区长度,然后分配那么多内存。文档涵盖了这一点。
  • @DavidHeffernan:但他没有检查以确保在使用它分配内存之前实际返回了有效的缓冲区长度。如果GetAdaptersInfo()ERROR_BUFFER_OVERFLOW 以外的任何错误而失败,则返回的BufLen 无效。你自己也做了同样的观察。
  • 是的,就是这样。我在回答中提到了这一点。不过你的更好,所以我会删除。就我个人而言,我宁愿做一个分配而不是使用 realloc。
  • MSDN 推荐以下(至少对于GetAdaptersAddresses()):“调用 GetAdaptersAddresses 函数的推荐方法是预先分配由AdapterAddresses 参数。在典型的计算机上,这大大降低了 GetAdaptersAddresses 函数返回 ERROR_BUFFER_OVERFLOW 的机会,这需要多次调用 GetAdaptersAddresses 函数。但是如果PC安装了很多适配器,ERROR_BUFFER_OVERFLOW就需要处理了。
  • 为了清楚起见,我没有显示所有代码,还有一个分配给根节点的第二个指针,用于处理内存。没有泄漏。本来我只是用了第一个网卡的mac地址,但是很快就意识到有些网卡比如Wifi可以关掉。它是我的版权保护代码的一部分。
【解决方案3】:

只是为了结束这个话题。

将 IPtypes 更改为 winapi.IPtypes 为我解决了这个问题。

我认为第三方组件正在做一些事情来混淆编译器并提供完整链接来修复它。

【讨论】:

    猜你喜欢
    • 2014-10-09
    • 2014-08-22
    • 2023-03-09
    • 2014-10-17
    • 2014-10-30
    • 1970-01-01
    • 1970-01-01
    • 2014-09-25
    • 2016-12-11
    相关资源
    最近更新 更多