2

我终于硬着头皮买了 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;

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

4

3 回答 3

4

您的代码有几个问题:

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

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

  3. GetAdaptersInfo()不使用GetLastError(),所以你需要在打电话SetLastError()之前打电话RaiseLastOSError()

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

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

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

话虽如此,试试这个:

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;
于 2014-05-09T17:36:45.040 回答
1

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

解决方法是在适当的地方修复IpTypes使用。最好的方法是使用Delphi 附带的版本而不是第三方版本。AnsiCharCharIpTypes

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

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

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

于 2014-05-09T12:07:40.490 回答
0

只是为了结束这个话题。

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

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

于 2014-05-17T21:31:51.140 回答