Question about Record Pointer as a Function Variable

181 Views Asked by At

If I define the following procedure:

procedure get_String(var aStringPtr: PAnsiChar);
  function set_ReturnValue(aInputValue: AnsiString) : PAnsiChar; overload;
  var
    iLen: Integer;
  begin
    iLen   := Length(aInputValue);
    Result := AnsiStrAlloc(iStringLen + 1);
    System.AnsiStrings.StrPLCopy(Result, aInputValue, iLen);
  end;

var
  iData: AnsiString;
begin
  iData := 'Hello World';
  aStringPtr:= set_ReturnValue(iData);
end;

I am able to call the 'get_String()' procedure and get a string back by executing the following code:

var
  iMyStr: PAnsiChar;
begin
  get_String(iMyStr);

I am trying, unsuccessfully, to create a 'get_Record()' procedure that returns a properly filled record pointer:

  TMyDataRec = record
    ProductID: Integer;
    DownloadURL: WideString;
    MaintenanceDate: AnsiString;
  end;

type
  TMyDataRecPtr = ^TMyDataRec;

procedure get_Record(var aRecordPtr: TMyDataRecPtr);
var
  iData: TMyDataRec;
begin
  iData.ProductID       := 1;
  iData.DownloadURL     := 'www.thisisatest.com';
  iData.MaintenanceDate := '02/29/2023';

  aRecordPtr := <...>
end;

How do I populate 'aRecordPtr' so that a call to the 'get_Record()' procedure returns the proper data?

So far, all my attempts result in an access violation.

2

There are 2 best solutions below

3
Tom Brunberg On BEST ANSWER

Typically records and record pointers are declared together, as follows:

type
  PMyDataRec = ^TMyDataRec;
  TMyDataRec = record
    ProductID: Integer;
    DownloadURL: WideString;
    MaintenanceDate: AnsiString;
  end;

// Note that the `PMyDataRec` pointer can be defined before the 
// `TMyDataRec` record, if done in the same 'type' block.

You get the access violation because you rely on stack memory, which doesn't exist anymore when the procedure exits. You need to allocate memory for the record with the New() method.

procedure get_Record(var aRecordPtr: PMyDataRec);
var
//  iData: TMyDataRec;
  iData: PMyDataRec;
begin
  new(iData); // this is missing
  iData.ProductID       := 1;
  iData.DownloadURL     := 'www.thisisatest.com';
  iData.MaintenanceDate := '02/29/2023';

  aRecordPtr := iData; //<...>
end;

Note also, you don't need the iData record pointer, you can operate directly on the aRecordPtr parameter.

1
dummzeuch On

Why make it a procedure in the first place, if you want to return a record pointer? Just allocate the memory and return the pointer as the function result:

function get_Record: PMyDataRec;
begin
  New(Result);
  Result^.ProductID       := 1;
  Result^.DownloadURL     := 'www.thisisatest.com';
  Result^.MaintenanceDate := '02/29/2023';
end;

Call it like this:

var
  Data: PMyDataRec;
begin
  Data := get_Record;
  try
    // data now points to a TMyDataRec allocated on the heap
    WriteLn(Data^.DownloadURL);
  finally
    // Don't forget to free the memory when you no longer need it
    Dispose(Data);
  end;
end;

You can leave out the ^ most of the time as the compiler automatically assumes it where it is safe to do so.