memory overflow using the object list with generics - delphi

Step 1:
write an application with the code:
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, System.Generics.Collections,
FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls;
type
TObjChild = class;
TObjTest = class
private
FName: string;
FChilds: TList<TObjChild>;
public
property Name: string read FName write FName;
property Childs: TList<TObjChild> read FChilds write FChilds;
constructor Create;
destructor Destroy; override;
end;
TObjChild = class
private
FAdress: string;
FPostalCode: string;
public
property Adress: string read FAdress write FAdress;
property PostalCode: string read FPostalCode write FPostalCode;
end;
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.fmx}
{ TObjTeste }
constructor TObjTest.Create;
begin
FChilds := TObjectList<TObjChild>.Create;
end;
destructor TObjTest.Destroy;
var
i: integer;
begin
for i := 0 to FChilds.count -1 do
begin
FChilds[I].Free;
end;
FreeAndNil(FChilds);
inherited;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
I: Integer;
ListObjs: TList<TObjTest>;
lObjTeste: TObjTest;
lObjChild: TObjChild;
J: Integer;
begin
ListObjs := TList<TObjTest>.Create;
for I := 0 to 5000 do
begin
lObjTeste := TObjTest.Create;
for J := 0 to 2000 do
begin
lObjChild := TObjChild.Create;
lObjTeste.FChilds.Add(lObjChild)
end;
ListObjs.Add(lObjTeste);
end;
if MessageDlg('Delete objects?', TMsgDlgType.mtConfirmation, [TMsgDlgBtn.mbOK], 0) = idOK then
begin
for I := 0 To ListObjs.Count - 1
begin
ListObjs[I].Free;
end;
FreeAndNil(ListObjs);
end;
end;
end.
Step 2: Run application and press button1
After pressing the OK button messagedlg of the application does not release the memory
Step 3: Repeat steps sometimes the application returns a memory low

The problem is here:
constructor TObjTest.Create;
begin
FChilds := TObjectList<TObjChild>.Create;
end;
destructor TObjTest.Destroy;
var
i: integer;
begin
for i := 0 to FChilds.count - 1 do
begin
FChilds[i].Free;
end;
FreeAndNil(FChilds);
inherited;
end;
By default TObjectList<T> takes ownership of its members. So you do not need to, and indeed should not, free the members in the destructor.
So here:
for i := 0 to FChilds.count - 1 do
begin
FChilds[i].Free;
end;
you free the members. But then here:
FreeAndNil(FChilds);
The object list also frees the members. Who have already been freed. That double free leads to your runtime errors.
The fix is to remove the explicit freeing of the object list members and rely on the list to do the work:
destructor TObjTest.Destroy;
begin
FChilds.Free;
inherited;
end;
This ownership of its members is the single reason for the existence of TObjectList<T>. That is the only functionality that it offers beyond that provided by TList<T>. Read about it here: http://docwiki.embarcadero.com/Libraries/en/System.Generics.Collections.TObjectList
Finally, the plural of child is children.

Related

custom managed record and memory leak

Using Delphi 10.4.1 I tried Custom Managed record management to initialize a record but still get memory leaks.
unit Unit3;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, System.IOUtils, System.DateUtils, System.Character,
Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TForm3 = class(TForm)
Button1: TButton;
RadioGroup1: TRadioGroup;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form3: TForm3;
type
TMyRec = record
DateTime: TDateTime;
v, size: integer;
str: string;
class operator Initialize (out Dest: TMyRec);
end;
TMyREcHolder = class
data: TMyRec;
constructor Create(const e: TMyRec);
end;
TMyList = class(TList)
procedure Clear; override;
end;
implementation
{$R *.dfm}
class operator TmyRec.Initialize (out Dest: TMyRec);
begin
Dest.str := '';
end;
{ TMyREcHolder }
constructor TMyREcHolder.Create(const e: TMyRec);
begin
inherited Create;
data := e;
end;
{ TMyList }
procedure TMyList.Clear;
var
i: integer;
begin
for i := 0 to Count - 1 do
TMyREcHolder(Items[i]).Free;
inherited Clear;
end;
procedure TForm3.Button1Click(Sender: TObject);
var
lst: TMyList;
i: integer;
rec: TMyRec;
FI: TSearchrec;
begin
Initialize(rec);
lst := TMyList.Create;
try
if FindFirst(TPath.Combine('C:\temp', '*.txt'), faAnyFile, FI) = 0 then
begin
repeat
if (FI.FindData.dwFileAttributes and faDirectory = 0) and
(FI.FindData.dwFileAttributes and faArchive = faArchive) then
begin
Application.ProcessMessages;
case RadioGroup1.ItemIndex of
0: Initialize(rec);
1: rec.str := '';
2: fillchar(rec, sizeof(rec), 0);
end;
try
rec.DateTime := FI.TimeStamp;
except
rec.DateTime := EncodeDateDay(1970, 1);
end;
rec.size := FI.size;
rec.str := FI.Name;
lst.Add(TMyREcHolder.Create(rec));
end;
until (FindNext(FI) <> 0);
FindClose(FI);
end;
finally
lst.Free;
end;
end;
end.
The radiogroup offers three items, both 1 and 3 leak memory. Can anyone explain why the Initialize one does? I want a reliable way of clearing a record to help me wean myself off a 20 year fillchar habit.

Pointer to Control

I would like to create array of all listboxes and access them. I tried to do it using pointers, however my program crashes during runtime with error access violation at address...
type ControlsCount = 4;
type PLB = ^TListBox;
var listBoxes: array of PLB;
procedure TExport.FormCreate(Sender: TObject);
var i: word; n: integer;
begin
with FormExport do
begin
ListRowHeight := List_sex.height;
List_sex.items.add('---');
List_sex.items.add('Man');
List_sex.items.add('Woman');
List_sex.onmousemove:=ListMouseMove;
setLength(listBoxes, ControlsCount);
n := -1;
for i := 0 to ControlsCount - 1 do
if Components[i] is TWinControl then
if TWinControl(Components[i]).CanFocus then
begin
inc(n);
// mistake here: should be listBoxes[n] not listBoxes[i]
listBoxes[i] := PLB(Components[i]);
end;
realControlsCount := n;
end;
end;
procedure TExport.resetListBoxes;
var i: word;
begin
for i := 0 to realControlsCount-1 do
begin
TListBox(listBoxes[i]^).height := ListRowHeight;
end;
end;
So here I try to set the pointer of the control to listBoxes[i].
listBoxes[i] := PLB(Components[i]);
and here I try to access it:
TListBox(listBoxes[i]^).height := ListRowHeight;
this is the line where it generates error.
What am I doing wrong?
Just remove all pointer stuff and check whether control is really TListBox. Also you misused ControlsCount while accessed another list Components[i]
var listBoxes: array of TListBox;
...
for i := 0 to ControlsCount - 1 do
if Controls[i] is TListBox then //note strict constraint
listBoxes[n] := Controls[i] as TListBox;
...
listBoxes[i].height := ListRowHeight;
Aslso consider using TList<TListBox> instead of array
Regarding the answere MBo gave this is how I would use it with a TList
unit Unit1;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, Generics.Collections,
FMX.Layouts, FMX.ListBox, FMX.Controls.Presentation, FMX.StdCtrls;
type
TForm1 = class(TForm)
lst1: TListBox;
lst2: TListBox;
lst3: TListBox;
btn1: TButton;
pnl1: TPanel;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure btn1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
listboxes: TList<TListBox>; // Define list that will contain listboxes
implementation
{$R *.fmx}
procedure TForm1.btn1Click(Sender: TObject);
var
lstbx: TListBox;
begin
for lstbx in listboxes do
ShowMessage(lstbx.Height.ToString); Loop through all listboxes and show their height
end;
procedure TForm1.FormCreate(Sender: TObject);
var
control: TControl;
begin
listboxes := TList<TListBox>.Create; // Create the TList
for control in pnl1.Controls do
begin
if control is TListBox then
listboxes.Add(control as TListBox); // Loop through all listboxes on a panel and add then to the list if they are a listbox
end;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
listboxes.Free; // Free the list
end;
end.

Reference counted object within a record not destroyed when record goes out of scope

I have a record that contains what I believe is a pointer to a reference counted object. I would expect that if I create the reference counted object within the record that when the record goes out of scope the reference count of the object would fall to zero, and the object would be destroyed. But this does not seem to be that case. Here is sample minimum code. My form happens to have some panels and a memo, but only the TButton (and specifically Button1Click) is important.
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls;
type
TUserData = class( TInterfacedObject )
public
AData : integer;
constructor Create;
destructor Destroy; override;
end;
TTestRec = Record
AField : integer;
UserData : TUserData;
End;
TForm4 = class(TForm)
Panel1: TPanel;
Panel2: TPanel;
Panel3: TPanel;
Memo1: TMemo;
Button1: TButton;
procedure FormShow(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form4: TForm4;
implementation
{$R *.dfm}
procedure TForm4.Button1Click(Sender: TObject);
var
iRec : TTestRec;
begin
iRec.UserData := TUserData.Create;
// stop too much optimisation
Button1.Caption := IntToStr( iRec.UserData.AData );
end; // I would expect TTestRec and hence TTestRec.UserData to go out of scope here
procedure TForm4.FormShow(Sender: TObject);
begin
// show leaks on exit
ReportMemoryLeaksOnShutdown := TRUE;
end;
{ TUserData }
constructor TUserData.Create;
begin
inherited Create;
AData := 4;
end;
destructor TUserData.Destroy;
begin
inherited;
end;
end.
I confess I don't really understand how reference counting works in detail, although I do understand the principle. What am I missing? Am I expecting too much and if so, is there any way to avoid memory leaks, not in this specific case (where obviously I could destroy UserData on exit) but in general, since records do not support destructors.
Automatic reference counting is performed through interface variables. You don't have any. Instead of a variable of type TUserData you need a variable that is an interface.
You could use IInterface here but that would be a little useless. So you should define an interface that exposes the public functionality you need the object to support and then have your class implement that interface.
This program demonstrates what I mean:
type
IUserData = interface
['{BA2B50F5-9151-4F84-94C8-6043464EC059}']
function GetData: Integer;
procedure SetData(Value: Integer);
property Data: Integer read GetData write SetData;
end;
TUserData = class(TInterfacedObject, IUserData)
private
FData: Integer;
function GetData: Integer;
procedure SetData(Value: Integer);
end;
function TUserData.GetData: Integer;
begin
Result := FData;
end;
procedure TUserData.SetData(Value: Integer);
begin
FData := Value;
end;
type
TTestRec = record
UserData: IUserData;
end;
procedure Main;
var
iRec: TTestRec;
begin
iRec.UserData := TUserData.Create;
end;
begin
Main;
ReportMemoryLeaksOnShutdown := True;
end.
This program does not leak. Change the variable declaration in the record type to UserData: TUserData and the leak returns.

Storing reference to object in TeeChart

I'm using TeeChart with Delphi XE5 and utilizing the BubbleSeries component to show X/Y/Radius bubbles in a chart.
I'm building the chart using an list of objects that I have, calculating X/Y/Radius values for these objects on the fly and inserting them using the TBubbleSeries.AddBubble method.
The problem is when I want to perform some action on the objects when the corresponding bubble is hovered/clicked/etc. I use the TChartSeries.Clicked method to find out which bubble is clicked, but the index I get returned is only usable for finding out the xy/radius values of the bubble, not which object originated it.
Maybe I'm missing something simple, because this seems to be something that any charting library should handle easily. I tried using the returned index from AddBubble method, but this index is only valid until another call to AddBubble is performed, at which point, the internal list seems to be re-ordered.
Edit: Was asked for some code, here it is!
procedure TBubbleReportForm.ChartMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
Index: Integer;
Device: TDevice;
begin
Index := BubbleSeries.Clicked(X,Y);
if Index = -1 then
begin
BubbleChart.ShowHint := False;
Exit;
end;
// This does not work as indexing does seems to correspond to the order which the bubbles was added.
Device := FDevices[Index];
BubbleChart.Hint := Device.Name;
BubbleChart.ShowHint := True;
end;
procedure TBubbleReportForm.FormCreate(Sender: TObject);
var
Device: TDevice;
begin
BubbleChart.OnMouseMove := ChartMouseMove;
// FDevices is of TObjectList type.
for Device in FDevices do
begin
BubbleSeries.AddBubble(Device.CalculateXVal,Device.CalculateYVal,Device.CalculateRadius);
end;
end;
I would use a a Generic TObjectList. Or an descendant og a TObjectList.
First Iimpelment your BoubleObject, and a list of them. In the following example I've just used a dummy implementation:
unit BubbleU;
interface
uses
System.Generics.Collections, System.SysUtils, Vcl.Graphics;
{$M+}
type
TBubble = class
private
FX: Double;
FRadius: Double;
FY: Double;
FLabelText: String;
FColor: TColor;
FIndex: Integer;
FChartIndex: Integer;
procedure SetChartIndex(const Value: Integer);
protected
procedure DoCalculation;
public
constructor Create(aIndex: Integer); reintroduce;
published
property X: Double read FX;
property Y: Double read FY;
property Radius: Double read FRadius;
property LabelText: String read FLabelText;
property Color: TColor read FColor;
property ChartIndex: Integer read FChartIndex write SetChartIndex;
end;
TBubbleList = class(TObjectList<TBubble>)
public
function ElementFormChartIndex(ChartIndex: Integer): TBubble; overload;
end;
implementation
{ TBubble }
constructor TBubble.Create(aIndex: Integer);
begin
inherited Create;
FIndex := aIndex;
DoCalculation;
end;
procedure TBubble.DoCalculation;
begin
FX := FIndex;
FY := FIndex;
FRadius := 1;
FColor := clRed;
FLabelText := 'Index: ' + FIndex.ToString;
end;
procedure TBubble.SetChartIndex(const Value: Integer);
begin
FChartIndex := Value;
end;
{ TBubbleList }
function TBubbleList.ElementFormChartIndex(ChartIndex: Integer): TBubble;
var
Element : TBubble;
begin
for Element in Self do
if Element.FChartIndex = ChartIndex then
Exit(element);
Exit(nil);
end;
end.
Next Extend your TBubbleSeries
unit BubbleSeriesExtention;
interface
uses
System.Classes, System.SysUtils,
VclTee.BubbleCh,
BubbleU;
type
TBubbleSeries = class(VclTee.BubbleCh.TBubbleSeries)
strict private
FBoubleList: TBubbleList;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function AddBubble(aBubble: TBubble): Integer; reintroduce;
published
property BoubleList : TBubbleList read FBoubleList;
end;
implementation
{ TBubbleSeries }
function TBubbleSeries.AddBubble(aBubble: TBubble): Integer;
begin
aBubble.ChartIndex := Inherited AddBubble(aBubble.X, aBubble.Y, aBubble.Radius, aBubble.LabelText, aBubble.Color);
FBoubleList.Add(aBubble);
Result := aBubble.ChartIndex;
end;
constructor TBubbleSeries.Create(AOwner: TComponent);
begin
inherited;
FBoubleList := TBubbleList.Create(True);
end;
destructor TBubbleSeries.Destroy;
begin
FreeAndNil(FBoubleList);
inherited;
end;
end.
Finally Use it in your from:
Add BubbleSeriesExtention toh the uses list AFTER VclTee.BubbleCh
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, VclTee.TeeGDIPlus, VclTee.TeEngine,
VclTee.Series, VclTee.BubbleCh, Vcl.ExtCtrls, VclTee.TeeProcs, VclTee.Chart,
BubbleU, BubbleSeriesExtention;
And use it:
type
TForm4 = class(TForm)
Chart1: TChart;
BubbleSeries: TBubbleSeries;
procedure FormCreate(Sender: TObject);
procedure Chart1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form4: TForm4;
implementation
{$R *.dfm}
procedure TForm4.Chart1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
Index: Integer;
Bouble: TBubble;
begin
Index := BubbleSeries.Clicked(X, Y);
if index < 0 then
exit;
Bouble := BubbleSeries.BoubleList.ElementFormChartIndex(Index);
Caption := Bouble.LabelText;
end;
procedure TForm4.FormCreate(Sender: TObject);
var
i: Integer;
begin
//Add dummy data
for i := 0 to 9 do
BubbleSeries.AddBubble(TBubble.Create(i));
end;
end.
this solution has this advantage that you have acces to your Object all the time and when your BubbleSeries are destroyes so is your objects for calculating elements in it. and gives you a kind of garbage collection
You can exploit unused AXLabel argument like this:
for DevIndex := 0 to DeviceCount - 1 do begin
Device := FDevices[DevIndex];
BubbleSeries.AddBubble(Device.CalculateXVal,Device.CalculateYVal, Device.CalculateRadius, IntToStr(DevIndex));
end;
// to avoid labels' text ox X-Axis:
Chart1.BottomAxis.LabelStyle := talValue;
//in Clicked:
DeviceIndex := StrToInt(BubbleSeries.Labels[Index]);

Impossible to call Binarysearch function for TObjectList

If we look into the online help of XE2 or XE3 for TObjectList methods
, we see that the binarysearch function is accessible for the TObjectList. But if we try into XE3 it doesn't even compile.
For the example, the sort function is available also, but this one compile.
Any idea is welcome.
Sample code :
unit FM_Main;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.Contnrs, Vcl.CheckLst, System.Generics.Collections;
type
TTPRODData = class
private
FData1 : String;
FData2 : String;
FCount : Integer;
public
constructor Create; overload;
destructor Destroy; override;
end;
TTPRODDataList = class(TObjectList)
function GetItem(Index: Integer): TTPRODData;
procedure SetItem(Index: Integer; const Value: TTPRODData);
public
constructor Create; overload;
destructor Destroy; override;
property Items[Index: Integer]: TTPRODData read GetItem write SetItem; default;
procedure SortOnProductCode;
end;
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
//
// Sort function.
//
function CompareProductCode(Item1, Item2: Pointer): Integer;
begin
Result := CompareStr(TTPRODData(Item1).FData1, TTPRODData(Item2).FData1);
end;
//
//
//
procedure TForm1.Button1Click(Sender: TObject);
var
aProdList : TTPRODDataList;
aDummy : TTPRODData;
aNdx : Integer;
begin
aProdList := TTPRODDataList.Create;
// This call works.
aProdList.Sort(CompareProductCode);
// This call doesn't even compile !
aProdList.BinarySearch(aDummy, aNdx);
end;
{ TTPRODData }
constructor TTPRODData.Create;
begin
inherited Create;
FData1 := '';
FData2 := '';
FCount := 0;
end;
destructor TTPRODData.Destroy;
begin
inherited;
end;
{ TTPRODDataList }
constructor TTPRODDataList.Create;
begin
inherited Create;
end;
destructor TTPRODDataList.Destroy;
begin
Clear;
inherited;
end;
function TTPRODDataList.GetItem(Index: Integer): TTPRODData;
begin
result := TTPRODData(inherited GetItem(index));
end;
procedure TTPRODDataList.SetItem(Index: Integer; const Value: TTPRODData);
begin
inherited setItem(index, value);
end;
procedure TTPRODDataList.SortOnProductCode;
begin
Sort(CompareProductCode);
end;
end.
As suggested by David Heffernan, here follow the code for the comparer for the sort function.
For those who are interested, here follow the code for the comparer method:
TTProdComparer = class(TComparer<TTPRODData>)
public
function Compare(const Item1, Item2: TTPRODData): Integer; override;
end;
And the code :
{ TTProdComparer }
function TTProdComparer.Compare(const Item1, Item2: TTPRODData): Integer;
begin
Result := CompareStr(Item1.FData1 , Item2.FData1 );
end;
The documentation that you have linked to is for the generic container TObjectList<T> from the Generics.Collections unit.
But the class that you have used in your code is the legacy non-generic container TObjectList from the Contnrs unit.
The BinarySearch method that you are trying to use only exists on the generic class.
If you switch to the generic container then you'll find that you can remove most of the boiler-plate code from your class. It becomes:
TTPRODDataList = class(TObjectList<TTPRODData>)
public
procedure SortOnProductCode;
end;
You don't need GetItem, SetItem and Items because the type-safe generic class already has that functionality sorted.
The only work you have to do is to adapt your sorting code to fit with the somewhat different interface used by the Delphi generic containers.

Resources