6

I want to know how to convert big-endian numbers to native numbers in Delphi. I am porting some C++ code in that I came across:

unsigned long blockLength  = *blockLengthPtr++ << 24;
blockLength |= *blockLengthPtr++ << 16;
blockLength |= *blockLengthPtr++ << 8;
blockLength |= *blockLengthPtr;

unsigned long dataLength  = *dataLengthPtr++ << 24;
dataLength |= *dataLengthPtr++ << 16;
dataLength |= *dataLengthPtr++ << 8;
dataLength |= *dataLengthPtr;

I am not familiar with C++, so I don't understand what those operators do.

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
steve0
  • 721
  • 3
  • 11
  • 19
  • possible duplicate of [How to convert Big Endian and how to flip the highest bit?](http://stackoverflow.com/questions/2882434/how-to-convert-big-endian-and-how-to-flip-the-highest-bit) – Jeroen Wiert Pluimers Jun 17 '10 at 20:31

2 Answers2

22

Andreas's answer is a pretty good example of how to do it in pure pascal, but it still looks kinda awkward, just like the C++ code. This can actually be done in a single assembly instruction, though which one depends on whether you're using 32-bit or 16-bit integers:

function SwapEndian32(Value: integer): integer; register;
asm
  bswap eax
end;

function SwapEndian16(Value: smallint): smallint; register;
asm
  rol   ax, 8
end;
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Mason Wheeler
  • 82,511
  • 50
  • 270
  • 477
  • You don't need to specify the calling convention, at least not for Delphi (don't know about Free Pascal). `register` is the default. – Michael Madsen Jun 17 '10 at 21:07
  • 2
    This is *the* solution, then! – Andreas Rejbrand Jun 17 '10 at 21:08
  • 4
    You don't *need* to specify the calling convention, @Michael, but it's useful to include it in assembler functions like this since they *require* a specific calling convention in order to work properly. – Rob Kennedy Jun 17 '10 at 21:12
  • 1
    Yeah, basically what Rob said. I know it's the default calling convention, and putting `register;` there is optional, but stating it explicitly makes it clear why you're operating on the (E)AX register. – Mason Wheeler Jun 17 '10 at 21:31
  • 6
    This is a bad example of using the overload directive that should be avoided. The Integer and SmallInt types are very close and converted implicitely by the compiler. You can keep SmallInt value in Integer variable (that is a common practice on 32-bit platform), and you are very likely to make a bug that is difficult to find with the overloaded SwapEndian. The dedicated names (ex SwapEndian16 and SwapEndian32) are more safe and better. – kludg Jun 18 '10 at 10:21
  • 1
    `rol ax, 8` is much more efficient than `xchg` on modern CPUs. It's only 1 uop, and avoids a partial-register stall (or merge) from reading AX after writing AH and AL. Especially on Intel P6-family CPUs, this was a big problem, but [even Skylake isn't great with writing AH](https://stackoverflow.com/questions/45660139/how-exactly-do-partial-registers-on-haswell-skylake-perform-writing-al-seems-to). – Peter Cordes Mar 19 '18 at 21:09
  • 1
    You should probably use Int32 instead of integer in the 32-bit version, as - theoretically - integer type could be any type (it was 16-bit in Delphi 1.0, and may be 64 bit in a future version), whereas Int32 always will be 32-bit. Likewise, smallint should be Int16 for future compatibility. – HeartWare Mar 20 '18 at 10:10
  • 1
    This work only on x86, but `bswap eax` not works on x86_64, because x86_64 has other ABI. I tried to find documentation but couldn't find it. Because of this, I disassembled the code, and determined that the first parameter is passed in ECX and return passed in EAX. So, `SwapEndian32` function may be `bswap Value` and `mov eax, Value`. It may work on both x86 and x86_64 – chabapok Mar 18 '23 at 10:54
4

To reverse the order of the bits:

procedure SwapEndiannessOfBits(var Value: cardinal);
var
  tmp: cardinal;
  i: Integer;
begin
  tmp := 0;
  for i := 0 to 8*sizeof(Value) - 1 do
    inc(tmp, ((Value shr i) and $1) shl (8*sizeof(Value) - i - 1));
  Value := tmp;
end;

To reverse the order of the bytes:

procedure SwapEndiannessOfBytes(var Value: cardinal);
var
  tmp: cardinal;
  i: Integer;
begin
  tmp := 0;
  for i := 0 to sizeof(Value) - 1 do
    inc(tmp, ((Value shr (8*i)) and $FF) shl (8*(sizeof(Value) - i - 1)));
  Value := tmp;
end;

I think the last one is what you are looking for. Most likely there are faster and more elegant solutions, though.

Disclaimer: I might be totally wrong. I feel a bit confused at the moment. Hopefully someone else will see this question and provide a more definite answer!

Andreas Rejbrand
  • 105,602
  • 8
  • 282
  • 384