2

私は最終的に弾丸をかみ、XE6 を購入しました。したがって、この単純な Windows API 呼び出しが失敗する理由を誰かが教えてくれれば、非常にありがたいです。この関数はエラーを返しません。最初の呼び出しで正しいバッファ長が取得され、2 番目の呼び出しでレコードがガベージで埋められます。

これは Delphi 2007 では問題なく動作しますが、IpTypes.pas で AnsiString を使用して明示的に宣言されていても、pAdapterinfo 戻りレコードに Unicode ガベージが含まれているため、XE6 では失敗します。

システムは 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;

アップデート:

さらにチェックを行いました。1 つのフォームと 1 つのボタンを備えた新しい単純なアプリケーションを作成し、ボタンが押されて機能したときにルーチンを呼び出しました。

違いは...作業形式では、IP_ADAPTER_INFO のサイズは 640 バイトです。

このルーチンをより複雑なアプリケーションで使用すると失敗し、IP_ADAPTER_INFO のサイズが 1192 バイトと表示されます。

この時点で、構造体の ansi char の型を unicode char に変更することをコンパイラが一方的に決定しているようです。デバッガーは、AdapterName および説明フィールドを Unicode 形式で表示しています。システム ソース コードの 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()1 つのアダプターに十分なメモリしか割り当てていませんが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. これは、AnsiCharUnicode 以前の Delphi でのエイリアスであり、Unicode Delphi でのエイリアスWideCharです。これは、レコードの内容を調べたときに ANSI 以外のテキストが表示されるという事実を説明しています。

解決策は、適切な場所の代わりIpTypesに使用するように修正することです。これを行う最善の方法は、サード パーティのバージョンではなく、Delphi に同梱されている を使用することです。AnsiCharCharIpTypes

その上、最初の呼び出しGetAdaptersInfoが間違っています。戻り値のチェックに失敗するだけでなく、バ​​ッファをnil渡し、ゼロ以外の長さも渡します。私はそれが次のようになるべきだと思います:

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

もちろん、あなたのやり方でうまくいくでしょうが、私はここで少し衒学的なことを言っているだけです。API 関数を呼び出すときは、常にエラーを確認してください。

于 2014-05-09T12:07:40.490 に答える