Ivan Prodanov
Ivan Prodanov

Reputation: 35532

How to assign a byte[] to a record

In C++ it's done like that:

tPacket * packet = (tPacket *)data; //data is byte[] array; tPacket is a structure

In C#:

tPacket t = new tPacket();
GCHandle pin = GCHandle.Alloc(data, GCHandleType.Pinned);
t = (tPacket)Marshal.PtrToStructure(pin.AddrOfPinnedObject(), typeof(tPacket));
pin.free();

Data is a byte array used as a receive buffer after a packet is received over TCP. That code puts data in an instance of tPacket (a structure) so I can access the structure later.

How is it done in Delphi?

Upvotes: 5

Views: 3673

Answers (3)

skamradt
skamradt

Reputation: 15548

You can also use the absolute keyword to force both structures to share the same memory address:

var
  Data: array[1..SizeOf(TMyStruct)] of byte;
  s : TMyStruct absolute Data;

Any data written to S is also available as Data without having to perform a move or pointer casting.

Upvotes: 4

Rob Kennedy
Rob Kennedy

Reputation: 163357

You can do in Delphi exactly what you would do in C++. Define a named type for the record pointer, though, since Delphi doesn't let you define types in statements.

type
  PPacket = ^TPacket;
var
  packet: PPacket;

packet := PPacket(@data[0]);
  • The reason I've used @data[0] is that it works regardless of whether data is a dynamic array. If it's a dynamic array, then data is really a pointer to the first byte of the packet, so this would work:

    packet := PPacket(data); // for dynamic array only
    

    But if data is not a dynamic array, then that type-cast won't be valid. You'd instead need to type-cast a pointer to data, like this:

    packet := PPacket(@data); // for static array only
    

    That won't work if it's a dynamic array. The first code will work in both cases. But if you have range checking enabled (and you probably should), then I think the first code will raise an exception if data is a zero-length dynamic array, so be careful.


If you want to go the C# route instead, where you copy the bytes from the array into a separate TPacket variable, then I'd use this:

var
  packet: TPacket;

// Source param comes first. Params are passed by
// reference automatically.
Move(data[0], packet, SizeOf(packet));

You'll need to ensure that data contains enough bytes to fill an entire TPacket value. TPacket had better not contain any compiler-managed types, like string, Variant, IUnknown, or dynamic-array types. Neither pointer type-casting nor Move behave well when that's the case.

Upvotes: 3

Jon Benedicto
Jon Benedicto

Reputation: 10582

This can be tricky as you have to make sure your source and destination structure layouts are identical. If they are, then you have 2 choices, either use a pointer to the Data array, or use a memory copy:

Pointer:

type
  // Declare a pointer type for your struct.
  PMyStruct = TMyStruct^;

...

var
  ptr: PMyStruct;
begin
  ptr := PMyStruct(Cardinal(@Data));
  // use ptr...
end;

Memory copy:

var
  Data: array of Byte;
  s: TMyStruct;
begin
  // fill Data...
  if SizeOf(s) <> Length(Data) then
    raise Exception.Create('Input size is not the same size as destination structure.');
  CopyMemory(@s, @Data, Length(Data));
  // use s...
end;

Upvotes: 2

Related Questions