简体   繁体   中英

Delphi Memory copy with Record to another Record

I am having problem with the logic. I have no idea how to copy record to another record in Delphi.

TypeA = record
  value1 : word;
  value2 : word;
  value3 : word;
  end;

TypeB = record
  b1 : byte;
  b2 : byte;    
  end;

I have my two records TypeA and TypeB. For example, I found out that data in TypeA record is belong to TypeB records. Note: TypeA has longer Data Length.

Question: How do i copy TypeA memory and place it in TypeB record?

CopyMemory(@TypeA, @TypeB, Length(TypeB))

When i tried CopyMemory and got an error (Incompaible types) .

PS: I don't want to copy or assign it like below.

TypeB.b1 := TypeA.value1 && $FF;

TypeA and TypeB are just example records. Most of thecases, record TypeA and TypeB may contain multple records and it will be harder to allocate form TypeA and assign to TypeB record.

Thanks in advance

----Addition question:

Is there a way to copy Delphi record to Byte array and how? If there is,

  • TypeA record to Byte Array
  • Byte Array to Type B

Will this logic work?

CopyMemory(@a, @b, SizeOf(TypeB))

如果aTypeA类型而bTypeB类型。

Variant records:

TypeA = packed record
  value1 : word;
  value2 : word;
  value3 : word;
  end;

TypeB = packed record
  b1 : byte;
  b2 : byte;
  end;

TypeAB = packed record
  case boolean of
    false:(a:TypeA);
    true:(b:TypeB);
end;
..
..
var someRec:TypeAB;
    anotherRec:TypeAB;
..
..
  anotherRec.b:=someRec.b
procedure CopyAtoB( const A: TypeA; var B: TypeB);
begin
// Assume A is bigger than B.
Move( A, B, SizeOf( TypeB))
end;

or (with Math unit)

procedure CopyAtoB( const A: TypeA; var B: TypeB);
begin
// No assumptions about A, B sizes.
FillChar( B, SizeOf( B), 0);
Move( A, B, Min( SizeOf( TypeA), SizeOf( TypeB)))
end;

to copy A record to B record there is a good way - operator overloading; in this case you can simply overload Implicit and Explicit operations and write code like b := a :

program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses System.SysUtils;

type
    TTypeA = record
      value1 : integer;
      value2 : integer;
      value3 : integer;
    end;

    TTypeB = record
      b1 : byte;
      b2 : byte;
      class operator Implicit(value : TTypeA):TTypeB;
    end;

class operator TTypeB.Implicit(value: TTypeA): TTypeB;
begin
    result.b1 := Hi(value.value1);
    result.b2 := Lo(value.value1);
end;


var a : TTypeA;
    b : TTypeB;
begin
    b := a;
end.

you can use TBytesStream to copy record to byte array:

var a : TTypeA;
    bs : TBytesStream;
    bArr : TArray<byte>;//array of byte;
begin
    bs := TBytesStream.Create();
    bs.Write(a, sizeof(a));
    bArr := bs.Bytes;
end;

or simply set size of byte array to sizeof(A) and then copyMemory

All the other answers are variations of copying memory directly, but I think that is the wrong approach. It's low-level and you're using a high-level language, it's easy to make mistakes, and it is risky if your records contain managed data types.

If you're trying to get the first two bytes of one element of a record, try using a variant record (in C, a union.) It is a section of a record that can be addressed several ways, and here you want to address it either as a word or series of bytes.

Try something like this (untested, just typed in the SO editor):

type
  TTwoBytes : packed record
    ByteA, ByteB : Byte;
  end;

  TValueRecord : packed record:
    case Boolean of
      True: (Value: SmallInt);
      False: (ValueBytes : TTwoBytes);
    end;
  end;

  TMyRecord = packed record
    Value1, Value2, Value3 : TValueRecord;
  end;

Then for code:

var
  MyRecord: TMyRecord;
  MyBytes: TTwoBytes;
begin
  MyRecord := ...; // Fill it with data here
  // Access the words / smallints by something like: MyRecord.Value1.Value
  MyBytes := MyRecord.ValueBytes; // The key bit: type safe assignment
  // Do something with MyBytes.ByteA or MyBytes.ByteB
end;

What does this give you that's better than directly copying memory?

  • It's safe: if you have more complicated records containing strings, interfaces etc, it will still work without breaking reference counts.
  • It's type safe: no direct copying of memory: you and the compiler both know what you have and are using.
  • It's canonical: this is probably 'the Delphi way' to do it. (Although there are several ways to structure the records - if you look in the answer history I had a worse one originally. Also I think the ' case ... of ' syntax for this is silly, but that's another discussion... :))

Some notes:

You can omit CopyMemory() (Windows unit) use by:

type
  PTypeA = ^TTypeA;
  TTypeA = record
    value1 : word;
    value2 : word;
    value3 : word;
  end;
  PTypeB = ^TTypeB;
  TTypeB = record
    b1 : byte;
    b2 : byte;
  end;
var
  A: TTypeA = (value1 : 11; value2 : 22; value3 : 33);
  B: TTypeB;
  B1: TTypeB;
  C: {packed?} array of Byte;
begin
  Assert(SizeOf(TTypeA) >= SizeOf(TTypeB));
  //...
  B:= PTypeB(@A)^;
  //...
  SetLength(C, SizeOf(A));
  // TTypeA record to Byte Array
  PTypeA(@C[0])^:= A;
  // Byte Array to TTypeB
  B1:= PTypeB(@C[0])^
end;

Use MOVE instead of CopyMemory if you want to simply copy the bytes from one place to another.

And to get the size of a record use SizeOf instead of Length.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM