Go Premium for a chance to win a PS4. Enter to Win

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 3546
  • Last Modified:

How can I copy a record ?

Hi!

How can I copy a pointer to a record into another variable of the same record type ?

For example, I have the following code which obviously doesn't work but might give you an idea of what I want to accomplish.

PSOME_RECORD = ^TSOME_RECORD;
TSOME_RECORD = record
  a:PChar;
  b:PChar;
  c: Cardinal;
end;

And then I have the following

function getRecord(offset: integer) : PSOME_RECORD;
begin
  //This function works correctly
  Result := PSOME_RECORD_Func(offset);
end;

var
  FRecords : array of TSOME_RECORD;
begin
  SetLength(FRecords, 10);
  for i=0 to 9 do
    FRecords[I] := GetRecord(i)^;  <- this is the part I'm having problems with ...
end;

I know I can easily do this by copying every member explicitly, but since TSOME_RECORD might change at some time, I want a solution that will require the least maintenance possible.  I.E. copy the record-pointers directly into a records-array.

If the solution is simpler, FRecords can be switched to
FRecords : array of PSOME_RECORD, but the pointers must not point to the original result of GetRecord();

Thanks.
 

0
delphi112497
Asked:
delphi112497
  • 5
  • 3
  • 2
  • +2
4 Solutions
 
swift99Commented:
FRecord can be an array of PSomeRecord as easily as an array of Records

Then your code becomes


var
  FRecords : array of PSOME_RECORD; <- this is the easy part ...   :o)
begin
  SetLength(FRecords, 10);
  for i=0 to 9 do
    FRecords[I] := GetRecord(i);  <- this is the easy part ...   :o)
end;
0
 
snehanshuCommented:
delphi,
  If you want to "Copy" the "Contents" of the pointer to a record type, you could use
 FRecords[I] := TSOME_RECORD(GetRecord(i)^);

Here is my test case which shows that the contents of the pointer and the array are independent of each other:
Note: Record modified slightly for my convenience ;-)

  PSOME_RECORD = ^TSOME_RECORD;
  TSOME_RECORD = record
    a:integer;
    b:integer;
    c:integer;
  end;


procedure TForm1.Button2Click(Sender: TObject);
Var
  i: Integer;
  MyPtr: PSOME_RECORD;
begin
  For i := 1 to 10 do
  begin
    MyPtr := GetRecord(i);
    RecAry[i] := TSOME_RECORD(MyPtr^);//copy values contained in the pointer
    RecAry[i].a := RecAry[i].a * 10;//change array

    //pointer contents remain unchanged:
    Showmessage(Format('ary: %d, Ptr: %d',[RecAry[i].a, MyPtr^.a]));

  end;

end;

function TForm1.GetRecord(i: integer): PSOME_RECORD;
begin
//create a new record for now
 Result :=  AllocMem(Sizeof(TSOME_RECORD));
 Result^.a := i;
 result^.b := i*100;
 result^.c := i*1000;
end;


Hope that helps.
...Shu
0
 
snehanshuCommented:
Well, here's the complete code:

unit EnumUnit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, TypInfo;

type
  PSOME_RECORD = ^TSOME_RECORD;
  TSOME_RECORD = record
    a:integer;
    b:integer;
    c:integer;
  end;

  TForm1 = class(TForm)
    Button2: TButton;
    procedure Button2Click(Sender: TObject);
    function GetRecord(i:integer) : PSOME_RECORD;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  RecAry: Array [1 .. 10] of TSOME_RECORD;//I had missed this in the earlier post :-)

implementation

{$R *.dfm}

procedure TForm1.Button2Click(Sender: TObject);
Var
  i: Integer;
  MyPtr: PSOME_RECORD;
begin
  For i := 1 to 10 do
  begin
    MyPtr := GetRecord(i);
    RecAry[i] := TSOME_RECORD(MyPtr^);
    RecAry[i].a := RecAry[i].a * 10;
    Showmessage(Format('ary: %d, Ptr: %d',[RecAry[i].a, MyPtr^.a]))

  end;

end;

function TForm1.GetRecord(i: integer): PSOME_RECORD;
begin
//
 Result :=  AllocMem(Sizeof(TSOME_RECORD));
 Result^.a := i;
 result^.b := i*100;
 result^.c := i*1000;
end;

end.

Cheers!
...Shu
0
What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

 
delphi112497Author Commented:
swift99,
your code stores pointers ... not the actual values.  If I later free all the memory involved in GetRecord(), FRecords will be affected also :(

snehanshu,
your code is basically what I ended up with, and everything works except for the null terminated PChars :(  ... is there any way I can copy the PChars also ?  Besides Cardinal and PChar, I also have an enum member ... the real record looks something more as:

type
  my_enum_values = (type1, type2, type3 ... typen);

TSOME_RECORD = record
  a: Cardinal;
  b: PChar;
  c: my_enum_values;
end;

I don't think there's any problem with 'c', but the PChar just messes things up :(

The reason for all this is TSOME_RECORD and GetRecord come from a DLL and the DLL uses too much memory;  I must free it as soon as I'm done with it and FRecords[] is used all through out the application.

Thanks for helping me out with this!
0
 
delphi112497Author Commented:
I am willing to increase the points to 500 if somebody gives me a good solution ...

I.E.  copies the records as specified above
frees FRecords from memory (including all of its PChar members) without explicitly making any name reference to them.
0
 
snehanshuCommented:
delphi,
  Handling pointer record members is a tough one! I am not sure I know any workaround for that :-(
  I shall try to find out, meanwhile, hopefully the better experts might help.
...Shu
0
 
delphi112497Author Commented:
snehanshu, after thinking the problem for a little longer I came to the conclusion it will require A LOT of memory handling and will only make things much harder to debug unless perfect code is provided.

I don't beleive this question will ever be answered to my satisfaction, but I do appreciate the effort and code you provided in your posts.

For the time being I will "hard-copy" each element of the record and will keep the question open.

Thanks for your help anyways.
0
 
snehanshuCommented:
delphi,
  I am not sure what's happening here, but it seems that Delphi makes the pointer thing would perhaps work just as you like :-)
  Check this:

  PSOME_RECORD = ^TSOME_RECORD;
  TSOME_RECORD = record
    a:integer;
    b:integer;
    c:integer;
    MyProb: PChar;
  end;

...

procedure TForm1.Button2Click(Sender: TObject);
Var
  i: Integer;
  MyPtr: PSOME_RECORD;
begin
  For i := 1 to 5 do
  begin
    MyPtr := GetRecord(i);
    RecAry[i] := TSOME_RECORD(MyPtr^);
    RecAry[i].a := RecAry[i].a * 10;
    RecAry[i].MyProb := 'Snehanshu';//Array says Snehanshu

//Snehanshu and Hello both still remain
    Showmessage(String(RecAry[i].MyProb) + String(MyPtr^.MyProb));
    FreeMem(MyPtr);
    Showmessage(String(RecAry[i].MyProb){ + String(MyPtr^.MyProb)});

//    Showmessage(Format('ary: %d, Ptr: %d',[RecAry[i].a, MyPtr^.a]))

  end;

end;

function TForm1.GetRecord(i: integer): PSOME_RECORD;
begin
//
 Result :=  AllocMem(Sizeof(TSOME_RECORD));
 Result^.a := i;
 result^.b := i*100;
 result^.c := i*1000;
 result^.MyProb := AllocMem(10);
 result^.MyProb := 'Hello';//DLL says Hello
end;

Cheers!
...Shu
0
 
snehanshuCommented:
Sorry, that wasn't a proper example.
So, the answer is that you cannot get RTTI for records in Delphi so you cannot do what you want.
the only solution would be to write a wrapper class: but your code would have to be recompiled every time your record structure changes.
...Shu

procedure TForm1.Button2Click(Sender: TObject);
Var
  i: Integer;
  MyPtr: PSOME_RECORD;
begin
  For i := 1 to 5 do
  begin
    MyPtr := GetRecord(i);
    RecAry[i] := TSOME_RECORD(MyPtr^);
    RecAry[i].a := RecAry[i].a * 10;
    Showmessage(String(RecAry[i].MyProb) + String(MyPtr^.MyProb));//Shows SHUSHU
    RecAry[i].MyProb[0] := 'U';
    RecAry[i].MyProb[1] := char(0);
    Showmessage(String(RecAry[i].MyProb) + String(MyPtr^.MyProb));//Shows UU
  end;
end;

function TForm1.GetRecord(i: integer): PSOME_RECORD;
begin
//
 Result :=  AllocMem(Sizeof(TSOME_RECORD));
 Result^.a := i;
 result^.b := i*100;
 result^.c := i*1000;
 result^.MyProb := AllocMem(10);
 result^.MyProb[0] := 'S';
 result^.MyProb[1] := 'H';
 result^.MyProb[2] := 'U';
 result^.MyProb[3] := char(0);

end;
0
 
Lee_NoverCommented:
well if your structures have pointers and you want to copy the "contents" of those pointers then there's no other way than 'hard copying' the values
if you used a class it'd be easier coz of RTTI
in your base class you'd just implement some general field copying method and that's it
note that all of your fields need to be published and the class must descend from TPersistent
0
 
Slick812Commented:
hello delphi, I am puzzled by your methods, it seems that for information exchange between a DLL, you'd use more Pointer oriented code. .
I would declare an Array Type of your TSOME_RECORD, and a pointer type to that array type, then you would just get a Pointer to the first Member of the DLL array and then you can access the data from your PMyRecAry variable

PSOME_RECORD = ^TSOME_RECORD;
TSOME_RECORD = record
  a:PChar;
  b:PChar;
  c: Cardinal;
end;

   PMyRecAry = ^TMyRecAry;
   TMyRecAry = Array[Word] of TSOME_RECORD;

Maybe some code like this

var
  PMRAry : PMyRecAry; // this is a Global variable that you can use in several procedures
 High1: Cardinal;  // this is the High (max index) of the array in the DLL, you do not say how you get this

procedure SetArrayPointer;
begin
  PSOME_RECORD( PMRAry) := GetRecord(0);
// now  PMRAr will have the CURRENT DATA in it, even if the DLL changes it, I am assuming that the DLL array is at a static address
end;

procedure UsePMRAry;
var
Amt1: Cardinal;
Str1: String;
begin
// you can easily access the data, and it will be upto date if DLL change it
Amt1 := 0;
for i := 0 to High1 do
  begin
  Str1 := Str1 + ' ' + PMRArray1^[i].a;
  Amt1 := Amt1 +  PMRArray1^[i].c;
  end;
end;

- - - - - - - - - - - - - - - - - - - - - - -
now if you need to KEEP this data after the DLL frees it, then you will need to just copy it, I will guess that the DLL will have some way to let you know when the Data Memory is released (maybe you FreeLibrary)

procedure WhateverFreeMemoryWarning;
var
PMRAry002 : PMyRecAry;
begin
PMRAry002 := AllocMem((High1+1)*SizeOf(TSOME_RECORD));
CopyMemory(PMRAry002, PMRAry,  (High1+1)*SizeOf(TSOME_RECORD));
// now just change the original pointer and you can keep using the code in other procedures
PMRAry := PMRAry002;
end;

you will need to FREE THE MEMORY if you ALLOCMEM, maybe when you app closes With FreeMem(PMRAry, (High1+1)*SizeOf(TSOME_RECORD));, be sure NOT to call FreeMem(PMRAry); if you are still using the DLL memory

this procedure will now still work even after you change the Pointer PMRAry

procedure UsePMRAry;
var
Amt1: Cardinal;
Str1: String;
begin
Amt1 := 0;
for i := 0 to High1 do
  begin
  Str1 := Str1 + ' ' + PMRArray1^[i].a;
  Amt1 := Amt1 +  PMRArray1^[i].c;
  end;
end;

0
 
Slick812Commented:
Str1 := Str1 + ' ' + PMRArray1^[i].a;

should be
Str1 := Str1 + ' ' + PMRArray^[i].a;
0

Featured Post

What does it mean to be "Always On"?

Is your cloud always on? With an Always On cloud you won't have to worry about downtime for maintenance or software application code updates, ensuring that your bottom line isn't affected.

  • 5
  • 3
  • 2
  • +2
Tackle projects and never again get stuck behind a technical roadblock.
Join Now