• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 2503
  • Last Modified:

Delphi - Checking instance of object in a record

As a follow on from Q_24323728 I have the following situation:
eg.
   TMyRecord = record
         RecID: Longint;
         RecType: TMyRecType;
         RecData: TMyObject;
    end;

    TMyRecType =(rtTypeOne, rtTypeTwo, etTypeThree);

...where TMyObject has its own constructors/destructors/properties/methods etc

I need to find a way of checking whether or not the RecData object has been created. If I declare the following:

MyRecord := TMyRecord;

...then subsequent calls to:

if assigned(Myrecord.RecData)

... are always true even though the RecData constructor has not been run.  How can I check if the actual object has been created?
0
brenlex
Asked:
brenlex
  • 5
  • 4
  • 2
  • +2
2 Solutions
 
MerijnBSr. Software EngineerCommented:
Where do you create your TMyRecord (before the constructor of RecData has been run).
0
 
brenlexAuthor Commented:
This is the dilema -- the RecData constructor has never actually been run, hence I want means to check if I need to call a RecData.Create.
0
 
MerijnBSr. Software EngineerCommented:
Please read the question, where in your code do you create the _record_.
Do you have a pointer to the record or do you have a variable which is of the type TMyRecord?
0
Technology Partners: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
HypoCommented:
When you allocate/create a record, the memory of that record is not always guaranteed to be set to zero, which is probably the reason why your Assigned call always returns true (Assigned returns true if the object pointer is not zero).

If you allocate a record (or an array of records) by just declaring it, then the data of that record will be whatever is in the records allocated memory at that point (See example 1).

If you allocate a record (or an array of records) using SetLength, then the SetLength function will zero the allocated memory for you (See example 2), and the pointers will be set to zero.

If you alloacte a record using New, then the data of that record will be undefined (See example 3).

As far as I know (and someone may please correct me if I'm wrong), this applies to all data types in records that are not reference counted, (reference counted objects are always set to zero). Examples of Reference counted objects are Strings and interfaces, but not normal class objects.

regards
Hypo
  TMyRec = record
   A : TObject;
  end;
 
...
 
procedure TForm1.FormCreate(Sender: TObject);
var i : Integer;
    aArray1 : Array[0..10] of TMyRec;
    aArray2 : Array of TMyRec;
    aArray3 : Array[0..10] of ^TMyRec;
begin
  Memo1.Lines.Add('Example 1');
  // Example 1,
  for i := 0 to 9 do
    Memo1.Lines.Add(IntToHex(Integer(aArray1[i].A), 8));
 
  Memo1.Lines.Add('Example 2');
  // Example 2,
  SetLength(aArray2, 10);
  for i := 0 to 9 do
    Memo1.Lines.Add(IntToHex(Integer(aArray2[i].A), 8));
 
  Memo1.Lines.Add('Example 3');
  // Example 3,
  for i := 0 to 9 do
    New(aArray3[i]);
  for i := 0 to 9 do begin
    Memo1.Lines.Add(IntToHex(Integer(aArray3[i].A), 8));
    Dispose(aArray3[i]);
  end;
end;

Open in new window

0
 
Geert GruwezOracle dbaCommented:
if Assigned(RecData) then // has been called

and use
FreeAndNil(RecData);

FreeAndNil is from unit SysUtils;

Also works when RecData is nil
0
 
brenlexAuthor Commented:
A local method variable declaration eg. MyRecord := TMyRecord, and then passed as a parameter (pointer) to another method. The check for nil takes place in the local method though is never nil.
0
 
MerijnBSr. Software EngineerCommented:
You should set MyRecord.RecData to nil when your application starts, then your check will work.

procedure TForm1.FormCreate(Sender: TObject);
begin
 // ... lots of stuff
 MyRecord.RecData := nil;
end;

Open in new window

0
 
brenlexAuthor Commented:
I tried this initially -- leaks all over the place.  If I set MyRecord.MyData to nil initially, I then need to create it within the repeatedly called method 'X' (in which MyRecord := TMyRecord is declared).  As I pass the MyRecord.MyData as a (not var) parameter to a secondary method, I cannot free/nil it at the end of the initial 'X' method.
0
 
MerijnBSr. Software EngineerCommented:
Can you please show this with some code? To me it seems what you say does not make any sense...
0
 
HypoCommented:
From the objects point of view, it doesn't matter if you pass MyRecord.MyData as a var parameter or not... i.e. The object itself isn't duplicated just because you pass it to another function. Maybe that is where yo go wrong? You have to consider that the object variable, is simply just a pointer to some memory; and when you free this memory from the initial function X, then the second function can no longer use the object that was passed to it, since it now points to a memory that has been released.

regards
Hypo
0
 
developmentguruCommented:
I set up an example.  The important part you need to understand is that when you declare a local variable as a record, the size of the record is reserved on the stack, but it is not cleared.  When you declare a record in this fashion you will need to clear it manually like this.

  FillChar(Rec, SizeOf(Rec), 0);

I put my test on the doubleclick event of my form.  Now, when my ParamTest is called the RecData will show as nil.

  You need to be aware of some other things though.  The record you create in this manner will only be valid until the procedure where it is declared is finished executing.  At that point the stack space will be reallocated the next time more is required and the record would be overwritten.

  If you need to be able to dynamically create these records then you need to keep them in a list somewhere else.  This gives you the ability to look them up, and be sure you dispose of them when they are no longer needed.

Let me know if you need more.
unit Unit3;
 
interface
 
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, DB, DBClient, SOAPConn;
 
type
  TMyObject = class
  private
  protected
  public
    constructor Create;
  end;
 
  TMyRecType =(rtTypeOne, rtTypeTwo, etTypeThree);
  TMyRecord =
    record
      RecID: Longint;
      RecType: TMyRecType;
      RecData: TMyObject;
    end;
 
  TForm3 = class(TForm)
    SoapConnection1: TSoapConnection;
    procedure SoapConnection1AfterConnect(Sender: TObject);
    procedure FormDblClick(Sender: TObject);
  private
    { Private declarations }
  public
    procedure ParamTest(Rec : TMyRecord);
    { Public declarations }
  end;
 
var
  Form3: TForm3;
 
implementation
 
{$R *.dfm}
 
procedure TForm3.FormDblClick(Sender: TObject);
var
  Rec : TMyRecord;
 
begin
  FillChar(Rec, SizeOf(Rec), 0);
  ParamTest(Rec);
end;
 
procedure TForm3.ParamTest(Rec: TMyRecord);
begin
  if Assigned(Rec.RecData) then ;
 
end;
 
procedure TForm3.SoapConnection1AfterConnect(Sender: TObject);
begin
  SoapConnection1.RIO.HTTPWebNode.ConnectTimeout := 150000;
  SoapConnection1.RIO.HTTPWebNode.SendTimeout := 150000;
  SoapConnection1.RIO.HTTPWebNode.ReceiveTimeout := 150000;
end;
 
{ TMyObject }
 
constructor TMyObject.Create;
begin
 
end;
 
end.

Open in new window

0
 
brenlexAuthor Commented:
Hypo - thanks for raising the point about reference counting.  In my example (simplfied ref to some actual commercial code) TMyObject is a derived class from TXMLToDOMParser (XDOM 2.4 being used in Delphi 5).

I am beginning to wonder if I need to explicitly take further steps to ensure all references within the parent class are freed when I nil MyRecord.MyData.

MerijnB - I am going to write a standalone test app to check the above. I shall post findings as soon as possible.
0
 
brenlexAuthor Commented:
I have managed to resolve the problem.  I moved the declaration of my record (MyRecord) to a unit global (as opposed to the legacy code containing multiple local declarations of MyRecord in local methods).  It looks like multiple declaration of MyRecord (with the integrated uncreated object but with pointer) was causing problems on the stack -- and consequently a memory leak. I don't believe that reference counting (Delphi's GC) was taking place appropriately when the record was declared locally over and over again in any of the many methods using their own declaration of MyRecord.  It was better to re-use the same record instance (set to nil in initialization section) and manipulate only the ever changing MyObject.  As I know once MyRecord has been created (once) I can use a new exposed method in the TMyObject class to ensure all of its members are cleared appropriately.

Many thanks for your guidance.
0

Featured Post

Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

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