tags:

views:

301

answers:

3

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?

+2  A: 

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;
Jon Benedicto
The problem is that in the C++/C# code its not identical.the structure has size 10 bytes and the packet size could be over 2000 bytes.
John
Just use the first example then, the pointer method. It's identical to the C++ code.
Jon Benedicto
Personally, I would write to @data[0], since probably you don't want to overwrite the pointer value.
Marco van de Voort
The typecast to Cardinal is not needed and will result in a broken Pointer in Delphi 64 bit.ptr := PMyStruct(@Data);
Andreas Hausladen
+3  A: 

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.

skamradt
Of course, that WON'T work since dynamic arrays are reference types. The array needs to be non-dynamic.
Rob Kennedy
I don't declare Data as dynamic array.When calling Recv() - Data has size 8192 bytes. :)Thanks it worked.
John
Well, it's nice it worked for you, but could the answer please be edited to something usable then? Also, the absolute trick may be nice for simple cases like this one, but the idiomatic way to have S available as Data without move or typecasts would be to use a variant record, similar to a C union.
mghie
+3  A: 

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.

Rob Kennedy