views:

316

answers:

5

I have The following code for now.

type
  TByte4 = array[0..3] of Byte; // 32-bit

function CardinalToBytes(const Data: Cardinal): TByte4;
begin
  Result[0] := (Data shr 24) and 255;
  Result[1] := (Data shr 16) and 255;
  Result[2] := (Data shr 8) and 255;
  Result[3] := Data and 255;
end;

function BytesToCardinal(const Data: TByte4): Cardinal;
begin
  Result := (Data[0] * 16777216) +
            (Data[1] * 65536) +
            (Data[2] * 256) +
             Data[3];
end;

I am wondering if this is the fastest and most efficient way (I am sure it is not :) ). The catch is this has to work in compact framework under Delphi 2006 (don't even ask). I am using it in a TEA encryption algorithm that works with Ansi and Unicode versions of Delphi and also with .NET and .NET compact framework in Delphi 2006.

So no "Move" and similar functions that work with pointers are allowed (no i do not want unsafe code).

Edit:

I still haven't found a better way to do this. Some great suggestion were given, but they all fail in .NET. I am afraid I will not spend any more time on a dead road. VCL .NET is dead, so is CF in delphi I am afraid. So this will have to do for maintaining this project. I will stil wait for while if somebody proves me wrong and gets the code to compile in .NET

Edit2:

The solution was simple as it is in most cases. Somebody just had to look for the obvious solution. I just didn't know anymore that there is a BitConverter class in .NET

+1  A: 

resp.

result:=TByte4(Data);

and result:=cardinal(data)

This is not the same endianwise though (you are more or less implementing a host to little endian function and back above).

Alternately, there is the union trick:

  type 
   TSomeRecord = packed record
                case boolean of 
                    true : (data4:TByte4);
                    false :(dateabyte:cardinal);
                 end;

But I assume .NET will barf on that too.

Marco van de Voort
This will not work in Delphi 2006 .NET. That is the hard part here. It was coded originally just the way you showed, but I had to change it to made it .NET compatible.Result := TByte4(Data); throws "invalid typecast". It would be to easy that way ;)
Runner
The classic union trick then? It worked on old bytecode languages (UCSD Pascal)
Marco van de Voort
Great idea, but look at the answer Remko posted :) I am afraid .NET sucks here :) Now I know why I love native code.
Runner
Maybe with marshaling something could be done or with the unsafe code. But CF in 2006 is still in 1.0 version and believe me it drives you crayz. It feels like programming 10 years ago.
Runner
A: 

You didn't say no mnemonic, so...

type
  TByte4 = packed array[0..3] of Byte; // 32-bit

function CardinalToBytes(const Data: Cardinal): TByte4;
asm
  bswap  eax
end;

function BytesToCardinal(const Data: TByte4): Cardinal;
asm
  bswap  eax
end;
GJ
True I did not said "no mnemonic", but I did say .NET compatible. ASM is not allow in .NET. Compiler throws:Unsupported language feature: ASM :)There must be some other faster way, or did I really found an optimal solution?
Runner
That is the purpose of 'bswap' mnemonic, so your code is more or less optimal
GJ
Then I should have more faith in myself :))
Runner
+1  A: 

OK this is little bit faster...

And don't forget to switch off range checking!

function CardinalToBytes(const Data: Cardinal): TByte4;
begin
  Result[0] := Data shr 24;
  Result[1] := Data shr 16;
  Result[2] := Data shr 8;
  Result[3] := Data;
end;
GJ
Hm I am not sure it is worth the tradeoff. I am referring to the range checking turned off (event only for the portion of the code).But you did made it faster, so if no better solution is presented I will accept your answer.
Runner
Why wouldn't it be worth the trade-off? Are you worried that one of your bytes might exceed the range of a byte? That can never happen, so there's nothing to range-check. The compiler doesn't necessary know that, so it's liable to keep range-checking code there unless you tell it not to.
Rob Kennedy
Ok, true, but the correct solution was presented. Nonetheless this would be a viable improvement then if I stuck to my old solution.
Runner
+1  A: 

One way would be to use a case record:

type
  TTestRecord = record
    case Cardinal of
      0: (Card: Cardinal);
      1: (Arr: array[0..3] of Byte);
  end;

var
  TR: TTestRecord;
begin

  TR.Arr[0] := 0;
  TR.Arr[1] := 1;
  TR.Arr[2] := 2;
  TR.Arr[3] := 3;
  TR.Card := 0;
Remko
That is a great solution. It takes "100000000" iterations to assign cardinal to record, read it to bytes and assign it to another record, 436 ms on average. The same ammount of iterations take 2387 ms with my code, for the same task. But the darn thing still won't compile in .NET as dynamic structures are not allowed and the compiler chokes :( But thanks for the tip, it is a great and clean one.
Runner
+2  A: 

In Delphi.NET, use the BitConverter class from the .NET framework to do the conversion. See BitConverter Class (System).

For an example:

function CardinalToBytes(const Data: Cardinal): TBytes;
begin
  Result := BitConverter.ToBytes(Data);
end;

function BytesToCardinal(const Data: TBytes): Cardinal;
begin
  Result := BitConverter.ToUInt32(Data, 0);
end;
Jon Benedicto
Is there a way to tell the class to return the bytes in big-endian order? The documentation example shows the results in little-endian order, but that's not what the original function does.
Rob Kennedy
This works. Good thinking outside the box. I forgot about BitConverter. It was long while I did some .NET programming. Well thisi is the solution I need. Simple conditional defines and one line for each platform.The first function should be:Result := BitConverter.GetBytes(Data);
Runner
@RobHm I tried it and it works :) I will look in depth later, still have work to do for now :) But thanks for pointing that out.
Runner
It probably doesn't matter because I do encryption and decryption and in both cases I use the same endian order. There would be problems if some other TEA algorithm tried to read my data.
Runner
BitConverter doesn't support big-endian conversion. You can use the .NET function IPAddress.NetworkToHostOrder to convert between endians though.
Jon Benedicto