如果 Windows/Delphi/IDE 意味着 little-endian 顺序,我如何从 big-endian 二进制文件中读取整数?
How do I read integers from big-endian binary file if Windows/Delphi/IDE implies little-endian order?
我很困惑。我需要读取二进制文件(Applied Biotechnology 的 .fsa 扩展名,又名 ABIF,FASTA 文件)并且我 运行 遇到读取有符号整数的问题。我正在按照本手册做所有事情 https://drive.google.com/file/d/1zL-r6eoTzFIeYDwH5L8nux2lIlsRx3CK/view?usp=sharing
因此,例如,让我们看一下文件 https://drive.google.com/file/d/1rrL01B_gzgBw28knvFit6hUIA5jcCDry/view?usp=sharing
的 header 中的 fDataSize 字段
我知道应该是2688(根据手册是32位的有符号整数),二进制形式是00000000 00000000 00001010 10000000。
实际上,当我将这 32 位作为 4 个字节的数组读取时,我得到 [0, 0, 10, -128],这与二进制形式完全相同。
但是,如果我将其读取为整数,则结果为 16809994,即 00000001 00000000 10000000 00001010 位。
正如我从多个论坛了解到的那样,他们使用 Swap 和 htonl 函数将整数从 little-endian 顺序转换为 big-endian。他们还建议对 32 位整数使用 BSWAP EAX 指令。但在这种情况下,它们以一种错误的方式工作,具体来说:
Swap,应用于16809994,returns 16779904 or 00000001 00000000 00001010 10000000,BSWAP指令将16809994转换为176160769,即00001010 10000000=0000000[0000000]0000010
如我们所见,built-in 函数做了一些与我需要的不同的事情。交换可能 return 正确的结果,但是,出于某种原因,将这些位作为整数读取会更改 left-most 字节。
那么,出了什么问题,我该怎么办?
更新。 1
为了存储 header 数据,我使用以下记录:
type
TFasMainHeader = record
fFrmt : array[1..4] of ansiChar;
fVersion : Word;
fDir : array[1..4] of ansiChar;
fNumber : array[1..4] of Byte; //
fElType : Word;
fElSize : Word;
fNumEls : array[1..4] of Byte; //
fDataSize : Integer;
fDataOffset : Integer;
fDO : word;
fDataHandle : array[1..98] of Byte;
end;
然后在单击按钮时执行以下操作:
aFileStream.Read(fas_main_header, SizeOf(TFasMainHeader));
with fas_main_header do begin
if fFrmt <> 'ABIF' then raise Exception.Create('Not an ABIF file!');
fVersion := Swap(fVersion);
fElType := Swap(fElType);
fElSize := Swap(fElSize);
...
接下来需要正确的交换Int32变量,但此时fDataSize,例如16809994,调试时详细查看记录状态:
这对我来说没有意义,因为 fDataSize 值的二进制表示中不应该有 one-bit(它还会破坏 BSWAP 结果)。
查看文件开头的二进制结构(fDataSize 字节高亮显示):
这是一个使用纯 pascal 的实现示例:
program FasDemo;
{$APPTYPE CONSOLE}
uses
System.SysUtils, System.Classes;
type
TFasInt16 = packed record
B0, B1 : Byte;
function ToUInt32 : UInt32;
function ToInt32 : Int32;
class operator Implicit(A: TFasInt16): Integer; // Implicit conversion of TFasInt16 to Integer
class operator Implicit(A: Integer) : TFasInt16; // Implicit conversion of Integer to TFasInt16
end;
TFasInt32 = packed record
W0, W1 : TFasInt16;
function ToUInt32 : UInt32;
function ToInt32 : Int32;
class operator Implicit(A: TFasInt32): Integer; // Implicit conversion of TFasInt32 to Integer
class operator Implicit(A: Integer) : TFasInt32; // Implicit conversion of Integer to TFasInt32
end;
function TFasInt16.ToUInt32: UInt32;
begin
Result := (B0 shl 8) + B1;
end;
function TFasInt16.ToInt32: Int32;
begin
Result := Int16(B0 shl 8) + B1;
end;
class operator TFasInt16.Implicit(A: Integer): TFasInt16;
begin
Result.B1 := Byte(A);
Result.B0 := Byte(A shr 8);
end;
class operator TFasInt16.Implicit(A: TFasInt16): Integer;
begin
Result := A.ToInt32;
end;
function TFasInt32.ToUInt32: UInt32;
begin
Result := (W0.ToUInt32 shl 16) + W1.ToUInt32;
end;
function TFasInt32.ToInt32: Int32;
begin
Result := (W0.ToUInt32 shl 16) + W1.ToUInt32;
end;
class operator TFasInt32.Implicit(A: TFasInt32): Integer;
begin
Result := A.ToInt32;
end;
class operator TFasInt32.Implicit(A: Integer): TFasInt32;
begin
Result.W1 := Int16(A);
Result.W0 := Int16(A shr 16);
end;
var
Stream : TFileStream;
FasInt32 : TFasInt32;
FasInt16 : TFasInt16;
AInteger : Integer;
begin
Stream := TFileStream.Create('C:\Users\fpiette\Downloads\A02-RD12-0002-35-0.5PP16-001.5sec.fsa', fmOpenRead);
try
Stream.Position := ;
Stream.Read(FasInt32, SizeOf(FasInt32));
WriteLn(FasInt32.W1.ToUInt32, ' 0x', IntToHex(FasInt32.W1.ToUInt32, 8));
WriteLn(FasInt32.W1.ToInt32, ' 0x', IntToHex(FasInt32.W1.ToInt32, 8));
WriteLn(FasInt32.ToUInt32, ' 0x', IntToHex(FasInt32.ToUInt32, 8));
WriteLn(FasInt32.ToInt32, ' 0x', IntToHex(FasInt32.ToInt32, 8));
WriteLn;
WriteLn('Test implicit conversion 16 bits to integer ');
AInteger := FasInt32.W1;
WriteLn(AInteger, ' 0x', IntToHex(AInteger, 8));
WriteLn;
WriteLn('Test implicit conversion 32 bits to integer ');
AInteger := FasInt32;
WriteLn(AInteger, ' 0x', IntToHex(AInteger, 8));
WriteLn;
WriteLn('Test implicit conversion 16 bits from integer');
FasInt16 := 1234;
WriteLn(FasInt16.ToInt32, ' 0x', IntToHex(FasInt16.ToInt32, 8));
FasInt16 := -1234;
WriteLn(FasInt16.ToInt32, ' 0x', IntToHex(FasInt16.ToInt32, 8));
WriteLn;
WriteLn('Test implicit conversion 32 bits from integer');
FasInt32 := 12345678;
WriteLn(FasInt32.ToInt32, ' 0x', IntToHex(FasInt32.ToInt32, 8));
FasInt32 := -12345678;
WriteLn(FasInt32.ToInt32, ' 0x', IntToHex(FasInt32.ToInt32, 8));
ReadLn;
finally
FreeAndNil(Stream);
end;
end.
你可以添加,如果你的 Delphi 版本支持它,添加内联指令。
我使用运算符重载进行了隐式转换 to/from 整数。使用它可以在不调用转换例程的情况下使用类型:编译器为我们完成了工作!
当然可以添加其他运算符重载,你懂的。
要访问 FAS header 和其他结构,您可以使用类型 TFasInt32 和 TFasInt16 而不是 Word 和 Integer。其余的代码只是有它不是 big-endian!编译器会自动来回转换为本机整数 (little-endian)。
问题与字节顺序无关,而是与 Delphi records.
你有
type
TFasMainHeader = record
fFrmt : array[1..4] of ansiChar;
fVersion : Word;
fDir : array[1..4] of ansiChar;
fNumber : array[1..4] of Byte; //
fElType : Word;
fElSize : Word;
fNumEls : array[1..4] of Byte; //
fDataSize : Integer;
fDataOffset : Integer;
fDO : word;
fDataHandle : array[1..98] of Byte;
end;
并且您希望此记录覆盖文件中的字节,fDataSize
“位于”00 00 0A 80
。
但是 Delphi 编译器会在记录的字段之间添加填充以使它们正确对齐。因此,您的 fDataSize
将不会处于正确的偏移量。
要解决此问题,请使用 packed
关键字:
type
TFasMainHeader = packed record
fFrmt : array[1..4] of ansiChar;
fVersion : Word;
fDir : array[1..4] of ansiChar;
fNumber : array[1..4] of Byte; //
fElType : Word;
fElSize : Word;
fNumEls : array[1..4] of Byte; //
fDataSize : Integer;
fDataOffset : Integer;
fDO : word;
fDataHandle : array[1..98] of Byte;
end;
然后字段将位于预期位置。
然后——当然——你可以使用任何你喜欢的方法来交换字节顺序。
最好是 BSWAP
指令。
我很困惑。我需要读取二进制文件(Applied Biotechnology 的 .fsa 扩展名,又名 ABIF,FASTA 文件)并且我 运行 遇到读取有符号整数的问题。我正在按照本手册做所有事情 https://drive.google.com/file/d/1zL-r6eoTzFIeYDwH5L8nux2lIlsRx3CK/view?usp=sharing 因此,例如,让我们看一下文件 https://drive.google.com/file/d/1rrL01B_gzgBw28knvFit6hUIA5jcCDry/view?usp=sharing
的 header 中的 fDataSize 字段我知道应该是2688(根据手册是32位的有符号整数),二进制形式是00000000 00000000 00001010 10000000。 实际上,当我将这 32 位作为 4 个字节的数组读取时,我得到 [0, 0, 10, -128],这与二进制形式完全相同。
但是,如果我将其读取为整数,则结果为 16809994,即 00000001 00000000 10000000 00001010 位。
正如我从多个论坛了解到的那样,他们使用 Swap 和 htonl 函数将整数从 little-endian 顺序转换为 big-endian。他们还建议对 32 位整数使用 BSWAP EAX 指令。但在这种情况下,它们以一种错误的方式工作,具体来说: Swap,应用于16809994,returns 16779904 or 00000001 00000000 00001010 10000000,BSWAP指令将16809994转换为176160769,即00001010 10000000=0000000[0000000]0000010
如我们所见,built-in 函数做了一些与我需要的不同的事情。交换可能 return 正确的结果,但是,出于某种原因,将这些位作为整数读取会更改 left-most 字节。 那么,出了什么问题,我该怎么办?
更新。 1 为了存储 header 数据,我使用以下记录:
type
TFasMainHeader = record
fFrmt : array[1..4] of ansiChar;
fVersion : Word;
fDir : array[1..4] of ansiChar;
fNumber : array[1..4] of Byte; //
fElType : Word;
fElSize : Word;
fNumEls : array[1..4] of Byte; //
fDataSize : Integer;
fDataOffset : Integer;
fDO : word;
fDataHandle : array[1..98] of Byte;
end;
然后在单击按钮时执行以下操作:
aFileStream.Read(fas_main_header, SizeOf(TFasMainHeader));
with fas_main_header do begin
if fFrmt <> 'ABIF' then raise Exception.Create('Not an ABIF file!');
fVersion := Swap(fVersion);
fElType := Swap(fElType);
fElSize := Swap(fElSize);
...
接下来需要正确的交换Int32变量,但此时fDataSize,例如16809994,调试时详细查看记录状态:
这对我来说没有意义,因为 fDataSize 值的二进制表示中不应该有 one-bit(它还会破坏 BSWAP 结果)。
查看文件开头的二进制结构(fDataSize 字节高亮显示):
这是一个使用纯 pascal 的实现示例:
program FasDemo;
{$APPTYPE CONSOLE}
uses
System.SysUtils, System.Classes;
type
TFasInt16 = packed record
B0, B1 : Byte;
function ToUInt32 : UInt32;
function ToInt32 : Int32;
class operator Implicit(A: TFasInt16): Integer; // Implicit conversion of TFasInt16 to Integer
class operator Implicit(A: Integer) : TFasInt16; // Implicit conversion of Integer to TFasInt16
end;
TFasInt32 = packed record
W0, W1 : TFasInt16;
function ToUInt32 : UInt32;
function ToInt32 : Int32;
class operator Implicit(A: TFasInt32): Integer; // Implicit conversion of TFasInt32 to Integer
class operator Implicit(A: Integer) : TFasInt32; // Implicit conversion of Integer to TFasInt32
end;
function TFasInt16.ToUInt32: UInt32;
begin
Result := (B0 shl 8) + B1;
end;
function TFasInt16.ToInt32: Int32;
begin
Result := Int16(B0 shl 8) + B1;
end;
class operator TFasInt16.Implicit(A: Integer): TFasInt16;
begin
Result.B1 := Byte(A);
Result.B0 := Byte(A shr 8);
end;
class operator TFasInt16.Implicit(A: TFasInt16): Integer;
begin
Result := A.ToInt32;
end;
function TFasInt32.ToUInt32: UInt32;
begin
Result := (W0.ToUInt32 shl 16) + W1.ToUInt32;
end;
function TFasInt32.ToInt32: Int32;
begin
Result := (W0.ToUInt32 shl 16) + W1.ToUInt32;
end;
class operator TFasInt32.Implicit(A: TFasInt32): Integer;
begin
Result := A.ToInt32;
end;
class operator TFasInt32.Implicit(A: Integer): TFasInt32;
begin
Result.W1 := Int16(A);
Result.W0 := Int16(A shr 16);
end;
var
Stream : TFileStream;
FasInt32 : TFasInt32;
FasInt16 : TFasInt16;
AInteger : Integer;
begin
Stream := TFileStream.Create('C:\Users\fpiette\Downloads\A02-RD12-0002-35-0.5PP16-001.5sec.fsa', fmOpenRead);
try
Stream.Position := ;
Stream.Read(FasInt32, SizeOf(FasInt32));
WriteLn(FasInt32.W1.ToUInt32, ' 0x', IntToHex(FasInt32.W1.ToUInt32, 8));
WriteLn(FasInt32.W1.ToInt32, ' 0x', IntToHex(FasInt32.W1.ToInt32, 8));
WriteLn(FasInt32.ToUInt32, ' 0x', IntToHex(FasInt32.ToUInt32, 8));
WriteLn(FasInt32.ToInt32, ' 0x', IntToHex(FasInt32.ToInt32, 8));
WriteLn;
WriteLn('Test implicit conversion 16 bits to integer ');
AInteger := FasInt32.W1;
WriteLn(AInteger, ' 0x', IntToHex(AInteger, 8));
WriteLn;
WriteLn('Test implicit conversion 32 bits to integer ');
AInteger := FasInt32;
WriteLn(AInteger, ' 0x', IntToHex(AInteger, 8));
WriteLn;
WriteLn('Test implicit conversion 16 bits from integer');
FasInt16 := 1234;
WriteLn(FasInt16.ToInt32, ' 0x', IntToHex(FasInt16.ToInt32, 8));
FasInt16 := -1234;
WriteLn(FasInt16.ToInt32, ' 0x', IntToHex(FasInt16.ToInt32, 8));
WriteLn;
WriteLn('Test implicit conversion 32 bits from integer');
FasInt32 := 12345678;
WriteLn(FasInt32.ToInt32, ' 0x', IntToHex(FasInt32.ToInt32, 8));
FasInt32 := -12345678;
WriteLn(FasInt32.ToInt32, ' 0x', IntToHex(FasInt32.ToInt32, 8));
ReadLn;
finally
FreeAndNil(Stream);
end;
end.
你可以添加,如果你的 Delphi 版本支持它,添加内联指令。
我使用运算符重载进行了隐式转换 to/from 整数。使用它可以在不调用转换例程的情况下使用类型:编译器为我们完成了工作!
当然可以添加其他运算符重载,你懂的。
要访问 FAS header 和其他结构,您可以使用类型 TFasInt32 和 TFasInt16 而不是 Word 和 Integer。其余的代码只是有它不是 big-endian!编译器会自动来回转换为本机整数 (little-endian)。
问题与字节顺序无关,而是与 Delphi records.
你有
type
TFasMainHeader = record
fFrmt : array[1..4] of ansiChar;
fVersion : Word;
fDir : array[1..4] of ansiChar;
fNumber : array[1..4] of Byte; //
fElType : Word;
fElSize : Word;
fNumEls : array[1..4] of Byte; //
fDataSize : Integer;
fDataOffset : Integer;
fDO : word;
fDataHandle : array[1..98] of Byte;
end;
并且您希望此记录覆盖文件中的字节,fDataSize
“位于”00 00 0A 80
。
但是 Delphi 编译器会在记录的字段之间添加填充以使它们正确对齐。因此,您的 fDataSize
将不会处于正确的偏移量。
要解决此问题,请使用 packed
关键字:
type
TFasMainHeader = packed record
fFrmt : array[1..4] of ansiChar;
fVersion : Word;
fDir : array[1..4] of ansiChar;
fNumber : array[1..4] of Byte; //
fElType : Word;
fElSize : Word;
fNumEls : array[1..4] of Byte; //
fDataSize : Integer;
fDataOffset : Integer;
fDO : word;
fDataHandle : array[1..98] of Byte;
end;
然后字段将位于预期位置。
然后——当然——你可以使用任何你喜欢的方法来交换字节顺序。
最好是 BSWAP
指令。