64 位相当于 X86 图像处理例程中的 BSWAP

Gor*_*ner 5 delphi assembly x86-64

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

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=$4D4D; // 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;
Run Code Online (Sandbox Code Playgroud)

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

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

Che*_*yDT 10

存在此问题是因为内联程序集假定第一个参数以及返回值使用 register eax,根据 Delphi 的调用约定,这对于 32 位模式下的 Delphi 来说是正确的(尽管内联程序集文档指出不应该有可以对除ebp和以外的寄存器做出任何假设esp,即使在内联汇编语句内部(当它们被放置在函数顶部时)也始终如此)。

但是,64 位模式使用不同的调用约定,其中第一个参数是 in rcx,返回值是 using rax。因此,在这里您将获得随机的未初始化垃圾作为返回值,该值恰好位于该寄存器中(其字节已交换),因为它从未显式设置。

最好的、可移植的解决方案是在纯 Pascal 中实现字节交换,而无需内联汇编:

function SwapLong(Value: Cardinal): Cardinal;
begin
  Result := Swap(Value shr 16) or (Cardinal(Swap(Value)) shl 16);
end;
Run Code Online (Sandbox Code Playgroud)

这使用了数十年历史的Swap函数,该函数交换值的低 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;
Run Code Online (Sandbox Code Playgroud)

  • 不幸的是,Delphi 编译器从这个 purepascal 代码中生成了一些冗长的汇编代码:( (2认同)