64 位等效于 X86 图像处理例程中的 BSWAP

64-bit Equivalent to BSWAP in X86 Image Handling Routine

我有一个 Delphi Firemonkey EXIF 实现,我在例程中使用它来加载图像文件。我试图确定图像是否已旋转,因此我可以在显示图像之前更正图像的方向。该例程部分调用执行 BSWAP 的汇编代码以确定 header 信息在图像文件中的位置。这是代码的一部分:

type
  TMarker = packed record
    Marker  : Word;      //Section marker
    Len     : Word;      //Length Section
    Indefin : Array [0..4] of Char; //Indefiner - "Exif" 00, "JFIF" 00 and ets
    Pad     : Char;      //0x00
  end;

  TIFDHeader = packed record
    pad       : Byte; //00h
    ByteOrder : Word; //II (4D4D) or MM
    i42       : Word; //2A00 (magic number from the 'Hitchhikers Guide'
    Offset    : Cardinal; //0th offset IFD
    Count     : Word;     // number of IFD entries
  end;

function SwapLong(Value: Cardinal): Cardinal;
asm bswap eax end;

procedure TExif.ReadFromFile(const FileName: string);
var
  j:      TMarker;
  ifd:    TIFDHeader;
  off0:   Cardinal; //Null Exif Offset
  SOI:    Word; //2 bytes SOI marker. FF D8 (Start Of Image)
  f:      File;
begin
  if not FileExists(FileName) then exit;
  Init;

  System.FileMode:=0; //Read Only open
  AssignFile(f,FileName);
  reset(f,1);

  BlockRead(f,SOI,2);
  if SOI=$D8FF then begin //Is this Jpeg
    BlockRead(f,j,9);

    if j.Marker=$E0FF then begin //JFIF Marker Found
      Seek(f,20); //Skip JFIF Header
      BlockRead(f,j,9);
    end;

    //Search Exif start marker;
    if j.Marker<>$E1FF then begin
      i:=0;
      repeat
        BlockRead(f,SOI,2); //Read bytes.
        inc(i);
      until (EOF(f) or (i>1000) or (SOI=$E1FF));
      //If we find maker
      if SOI=$E1FF then begin
        Seek(f,FilePos(f)-2); //return Back on 2 bytes
        BlockRead(f,j,9);     //read Exif header
      end;
    end;

    if j.Marker=$E1FF then begin //If we found Exif Section. j.Indefin='Exif'.
      FValid:=True;
      off0:=FilePos(f)+1;   //0'th offset Exif header
      BlockRead(f,ifd,11);  //Read IDF Header
      FSwap := ifd.ByteOrder=D4D; // II or MM  - if MM we have to swap
      if FSwap then begin
        ifd.Offset := SwapLong(ifd.Offset);
        ifd.Count  := Swap(ifd.Count);
      end;
      if ifd.Offset <> 8 then begin
        Seek(f, FilePos(f)+abs(ifd.Offset)-8);
      end;

当应用程序是为 32 位 Windows 构建时,这工作正常,但在 64 位 Windows 下调用 SwapLong 时失败。我对汇编语言一无所知,所以我正在寻找在构建程序的 64 位版本时如何处理相同的功能。请注意,在两个版本中,传递给 SwapLong 函数的 idf.OffSet 值都是 134217728 ($08000000)。在 32 位版本中,SwapLong returns 的值为 8,但 64 位版本 returns 的值为 2694969615,因为输入似乎相同。

我需要 64 位版本才能工作,因为我希望使用相同的代码定位 64 位 MAC OSX。任何帮助将不胜感激。

问题存在是因为内联汇编假定第一个参数以及 return 值使用寄存器 eax,这对于 32 位模式下的 Delphi 是正确的根据 Delphi 的 calling convention (and although the inline assembly documentation 声明,除了 ebpesp 之外,不应对寄存器做出任何假设,即使在内联汇编语句中,这也始终成立当它们被放置在函数的顶部时)。

但是,64 位模式使用 a different calling convention,其中第一个参数在 rcx 中,return 值使用 rax。所以在这里你得到随机未初始化的垃圾作为 return 恰好在那个寄存器中的值(交换了它的字节)因为它从未明确设置。

最好的、可移植的解决方案是在没有内联汇编的情况下用纯 Pascal 实现字节交换:

function SwapLong(Value: Cardinal): Cardinal;
begin
  Result := Swap(Value shr 16) or (Cardinal(Swap(Value)) shl 16);
end;

这使用几十年前的 Swap function 交换值的低 2 个字节。这本身不再有多大用处,但可以使用两次(连同一些位移和屏蔽)来缩短用于交换 32 位值的所有 4 个字节的代码。

另一种具有更多源代码但可以生成较少复杂的汇编代码的方法是使用字节指针访问 Cardinal 中的各个字节:

function SwapLong(Value: Cardinal): Cardinal; inline;
begin
  PByte(@Result)^ := PByte(NativeUInt(@Value) + 3)^;
  PByte(NativeUInt(@Result) + 1)^ := PByte(NativeUInt(@Value) + 2)^;
  PByte(NativeUInt(@Result) + 2)^ := PByte(NativeUInt(@Value) + 1)^;
  PByte(NativeUInt(@Result) + 3)^ := PByte(@Value)^;
end;

64 位程序集在与 32 位程序集不同的寄存器中传递参数。在这种情况下,参数将在 ECX 寄存器中,并且 return 值需要在 EAX 中。

32 位和 64 位汇编需要不同的代码。

function SwapLong(Value: Cardinal): Cardinal;
{$IFDEF ASSEMBLER}
{$IFDEF CPUX86}
asm
  bswap eax
end;
{$ENDIF CPUX86}

{$IFDEF CPUX64}
asm
  mov eax, ecx
  bswap eax
end;
{$ENDIF CPUX64}
{$ELSE}
begin
  // pascal version
end;
{$ENDIF}

由于内联汇编仅在 Windows 上可用,其他平台需要纯 pascal 代码,如

所示