I have the following code:
Project.dpr
program Project2;
uses
madExcept,
madLinkDisAsm,
madListHardware,
madListProcesses,
madListModules,
Spring.Container,
Vcl.Forms,
uRegistrations in '..\Memory leak II\uRegistrations.pas',
Unit3 in 'Unit3.pas' {MainForm},
Unit4 in 'Unit4.pas' {SecondaryForm},
Unit5 in 'Unit5.pas';
{$R *.res}
begin
RegisterTypes(GlobalContainer);
Application.Initialize;
Application.MainFormOnTaskbar := True;
// MainForm:=TMainForm.Create(nil);
Application.CreateForm(TMainForm, MainForm);
MainForm.SecondaryForm := Globalcontainer.Resolve<ISecondaryForm>;
Application.Run;
end.
uRegistrations.pas that registers the interface
unit uRegistrations;
interface
uses
Spring.Container;
procedure RegisterTypes(Container: TContainer);
implementation
uses
Unit5,
Unit4;
procedure RegisterTypes(Container: TContainer);
begin
container.RegisterType<ISecondaryForm, TSecondaryForm>.DelegateTo(
function: TSecondaryForm
begin
result := TSecondaryForm.Create(nil);
end);
Container.Build;
end;
end.
Unit3.pas holding the main form
unit Unit3;
interface
uses
Winapi.Windows,
Winapi.Messages,
System.SysUtils,
System.Variants,
System.Classes,
Vcl.Graphics,
Unit5,
Vcl.Controls,
Vcl.Forms,
Vcl.Dialogs;
type
TMainForm = class(TForm)
private
{ Private declarations }
FSecondaryForm: ISecondaryForm;
public
{ Public declarations }
property SecondaryForm: ISecondaryForm read FSecondaryForm write FSecondaryForm;
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
end.
Unit4.pas with the secondary form
unit Unit4;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
unit5,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs;
type
TSecondaryForm = class(TForm, ISecondaryForm)
private
{ Private declarations }
public
{ Public declarations }
end;
//var
// SecondaryForm: TSecondaryForm;
implementation
{$R *.dfm}
end.
and finally Unit5.pas with the Interface declaration
{$M+}
unit Unit5;
interface
type
ISecondaryForm=interface
['{62D63E9A-A3AD-435B-8938-9528E70D78B1}']
end;
implementation
end.
It compiles and runs regularly but when I close the application i have three memory leaks.
allocation number: 8482 program up time: 721 ms type: Brush Handle
handle: $461027f5 style: BS_SOLID color: $f0f0f0
allocation number: 8318 program up time: 697 ms type: TSecondaryForm
address: $d51ac64 size: 924 access rights: read/write
allocation number: 8267 program up time: 693 ms type: Font Handle
handle: $1d0a28f1 face: Tahoma height: -11
Why does this happens and how can I solve it ?
EDIT
After the answer, I implemented the following solutions (the comments highlight the errors I got:
procedure RegisterTypes(Container: TContainer);
begin
container.RegisterType<ISecondaryForm, TSecondaryForm>.DelegateTo(
function: TSecondaryForm
begin
result := TSecondaryForm.Create(nil);
result.Owner:=Application.MainForm;//cannot assign to a read-only property
result.Parent:=Application; //incompatible types
result.Parent:=application.MainForm;//memory leak
end);
Container.Build;
end;
I have also tried to amend the OnClose method of TSecondaryForm in the following way:
procedure TSecondaryForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Action:=caFree; //memory leak
end;
but I got a memory leak.
What am I doing wrong with all the technique above ?
In the end I just made the two methods _AddRef and _Release manage the reference counting as suggested in the comments and I have no more memory leaks.
TSecondaryForm = class(TForm, ISecondaryForm)
private
{ Private declarations }
protected
FRefCount: Integer;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
{ Public declarations }
end;
function TSecondaryForm._AddRef: Integer;
begin
Result := InterlockedIncrement(FRefCount);
end;
function TSecondaryForm._Release: Integer;
begin
Result := InterlockedDecrement(FRefCount);
if Result=0 then
self.Free;
end
If you want a form (or any class that inherits from TComponent) to be handled by interface reference counting then you need to implement it yourself (look at System.TInterfacedObject as an example of how to do it).
You basically need to reimplement IInterface to the class you want to enable reference counting on:
type
TInterfacedForm = class(TForm, IInterface)
// look at System.TInterfacedObject
end;
If you are doing so, keep in mind that it then should not be handled by the owner mechanism. If you register it to the container and use its default creation mechanism it will pass nil to the owner as of Spring4D 1.2 - see Spring.Container.Resolvers.TComponentOwnerResolver). In any version before you need to explicitly create it with nil inside of DelegateTo.
If you are dealing with any controls over interfaces that are put onto other controls (like frames) via their parent property keep also in mind that in such case another memory management mechanism comes into play which might destroy such a component if its parent is getting destroyed - if you are just dealing with interfaced forms that is not a problem but I thought I mention it here for completeness.
TComponent descendants (like TForm) disable interfaces reference counting, hence nobody is freeing the secondary form. The memory model is owner based, that is, when the parent object that owns an object is freed, it frees all it's children.
So, you could either pass an owner to the form on the factory function (maybe Application, or Application.MainForm) and adhere to TComponent's memory model or add a hook on the OnClose event of the form and set Action to caFree. The former will destroy the form when the application is closed, and the latter will destroy it as soon as the secondary form is closed (as soon as possible)
Related
I'm backing up a database using Devart controls. Everything runs fine until I try to free the form having the backup component(TDump) and a progress bar on it. I moved the call to the finally portion of code and checked if it was assigned before trying to free it still same problem
procedure TfrmMain.CmdBackupExecute(Sender: TObject);
var
SaveDialog: TSaveDialog;
QRYString: String;
frmBackup: TfrmBackup;
Password: String;
BackupPassword: String;
MasterPassword: String;
CurrentFrame: TFrameType;
OldUser: String;
OldPassword: String;
begin
dmVintage.tblSettings.Open;
BackupPassword:= dmVintage.tblSettings.FieldByName('BackupRestorePWord').AsString;
MasterPassword:= dmVintage.tblSettings.FieldByName('MasterPWord').AsString;
InputPassword('Enter Backup Password', Password);
if Password = BackupPassword then
try
try
//close current frame and change to root
CurrentFrame:= FrameManager.CurrentFrameType;
FrameManager.Clear;
dmVintage.connMain.LoginPrompt:= False;
OldUser:= dmVintage.connMain.Username;
OldPassword:= dmVintage.connMain.Password;
dmVintage.connMain.Connected:= False;
dmVintage.connMain.Username:= 'root';
dmVintage.connMain.Password:= MasterPassword;
dmVintage.connMain.Connect;
SaveDialog:= TsaveDialog.Create(frmMain);
SaveDialog.Filter := 'SQL file|*.sql';
SaveDialog.DefaultExt:= '.sql';
SaveDialog.FileName:= 'VintageData';
if SaveDialog.Execute then
begin
frmBackup:= TfrmBackup.Create(frmMain);
frmBackup.Show;
frmBackup.mdVintage.BackupToFile(AddTimestampToFilename(SaveDialog.FileName), QryString);
//FreeAndNil(frmBackup);
//FreeAndNil(SaveDialog);
dlgI('Backup Seccessful');
end;
Except on E: Exception do
dlgW2('TfrmMain.CmdBackupExecute', E.Message);
end;
finally
//ShowMessage('Finally');
if Assigned(frmBackup) then
FreeAndNil(frmBackup);
if Assigned(SaveDialog) then
FreeAndNil(SaveDialog);
//reset connection and load old frame
dmVintage.connMain.Connected:= False;
dmVintage.connMain.Username:= OldUser;
dmVintage.connMain.Password:= OldPassword;
dmVintage.connMain.Connect;
dmVintage.connMain.LoginPrompt:= True;
FrameManager.LoadFrame(CurrentFrame);
end
else
dlgE('Invalid Backup Password');
end;
function TfrmMain.AddTimestampToFilename(Value: String): String;
var
Extension: String;
FileName: String;
FormattedDataTime: String;
begin
Extension:= ExtractFileExt(Value);
FileName:= ChangeFileExt(Value, '');
DateTimeToString(FormattedDataTime, 'yyyymmdd_hhmm', Now);
FileName:= FileName + '_' + FormattedDataTime;
Result:= ChangeFileExt(FileName, Extension);
end;
The Backup form is very simple with a few labels the TDump component and a progress bar.
unit uBackup;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, uDataVintage, DADump, MyDump,
Vcl.ComCtrls, Vcl.StdCtrls;
type
TfrmBackup = class(TForm)
mdVintage: TMyDump;
lblBackingUpTable: TLabel;
lblTable: TLabel;
Label3: TLabel;
pbBackup: TProgressBar;
procedure mdVintageBackupProgress(Sender: TObject; ObjectName: string;
ObjectNum, ObjectCount, Percent: Integer);
private
{ Private declarations }
public
{ Public declarations }
end;
implementation
{$R *.dfm}
procedure TfrmBackup.mdVintageBackupProgress(Sender: TObject;
ObjectName: string; ObjectNum, ObjectCount, Percent: Integer);
begin
Application.ProcessMessages;
if lblTable.Caption <> ObjectName then
lblTable.Caption:= ObjectName;
pbBackup.Position:= Percent;
end;
end.
Use frmBackup.Release when freeing (especially non-modal) forms.
Freeing forms
The form is not shown in a modal way, and you couldn't, because you're controlling it from the main form, which wouldn't work on a modal form.
I think you get the access violation because you 'bluntly' free the form, while the form itself is also still visible and handling messages. Because of that, the form code (the general TForm code) might at some point still try to do something to the form, even though your instance has already been cleaned up. Even when you call 'Close` in the code, you have this issue, because closing is also not a synchronous process, and requires the form to handle messages.
In general the solution is to call frmBackup.Release instead of frmBackup.Free. That way, the form queues a message for itself. It will first handle the other stuff it has to do, and at some point encounter this message and start a graceful clean-up procedure before it finally frees itself. This is typically the way to close a form from, say, a button-click event on the form itself, but I think it will get you out of this pickle as well.
General tips on Free and FreeAndNil
You don't need to call FreeAndNil in most cases, and especially not on a local variable for which you know exactly when it was assigned a value or not. The only thing FreeAndNil does, is make your reference nil, which is not needed at all for a variable that goes out of scope three lines later anyway.
There is no need at all to call if assigned before calling FreeAndNil, or even Free. Assigned is only checking if the reference is nil, which is what Free also does internally. That right: This is valid code that won't throw errors:
var
o: TObject;
begin
o := nil;
o.Free;
This VCL Form program generates the Invalid Pointer Operation notice:
Uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls,
DcadMenu_u;
type
TForm1 = class(TForm)
MenuTestRichEdit: TRichEdit;
LoadButton: TButton;
procedure ButtonLoadClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.ButtonLoadClick(Sender: TObject);
var
menu : TDcadMenu;
item1, item2 : TDcadMenuItem;
strlist :tstringlist;
i : integer;
begin
menu := tDcadMenu.Create();
item1 := TDcadMenuItem.create ('Option1', 'Do Option1', false, false, true);
menu.add (item1);
item2 := TDcadMenuItem.create ('Option2', 'Do Option2', false, false, true);
menu.add (item2);
strlist := tstringlist.Create;
Try
For i := 0 to Menu.Count - 1 DO
begin
item1 := menu.Items[i];
strlist.Add (Item1.lblset + ' | ' + Item1.lblmsg );
end;
Form1.MenuTestRichEdit.Lines := strlist;
finally
item1.free;
item2.Free;
menu.free;
strlist.Free;
end;
end;
The code works fine and generates the item list in the Richedit component. I suspect I am freeing an object that is already being handled, but not clear on what the cause is specifically. Can someone explain this?
We can't see the implementation of TDcadMenu, but normally adding items to a class gives the ownership of the items to that class, so there is no need to free the items outside of the class. As #Remy comments, it is normally safe to free them before before freeing the menu object, though.
In your code you are reassigning item1, and when freeing the items, Item1 and Item2 both shares the same instance as menu.Items[1]. This means that you have a double free, which gives your invalid pointer notice.
item1.free;
item2.Free; // <- Double free of same instance
I've been introduced to TObjectList, and I would like to make use of it, except that I can't seem to get even the sample code from Embarcadero's web site to work for me. Here is my code:
unit Test03Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
type
{ Declare a new object type. }
TNewObject = class
private
FName: String;
public
constructor Create(const AName: String);
destructor Destroy(); override;
end;
{ TNewObject }
var
Form1: TForm1;
implementation
{$R *.dfm}
constructor TNewObject.Create(const AName: String);
begin
FName := AName;
end;
destructor TNewObject.Destroy;
begin
{ Show a message whenever an object is destroyed. }
MessageDlg('Object "' + FName + '" was destroyed!', mtInformation, [mbOK], 0);
inherited;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
List: TObjectList<TNewObject>;
Obj: TNewObject;
begin
{ Create a new List. }
{ The OwnsObjects property is set by default to true -- the list will free the owned objects automatically. }
List := TObjectList<TNewObject>.Create();
{ Add some items to the List. }
List.Add(TNewObject.Create('One'));
List.Add(TNewObject.Create('Two'));
{ Add a new item, but keep the reference. }
Obj := TNewObject.Create('Three');
List.Add(Obj);
{
Remove an instance of the TNewObject class. Destructor
is called for the owned objects, because you have set the OwnsObjects
to true.
}
List.Delete(0);
List.Extract(Obj);
{ Destroy the List completely--more message boxes will be shown. }
List.Free;
end;
end.
When trying to compile this, I get the error [DCC Error] Test03Unit1.pas(51): E2003 Undeclared identifier: 'TObjectList<>'. Line 51 is the line which says:
List: TObjectList<TNewObject>;
I have never seen < > used in the Pascal language before, so this is completely new territory for me. Doing a google search for "Delphi and < >" doesn't seem to tell me what I need to know about this. From other examples I can find on the internet it seems to be the correct way to use it.
Using Delphi XE2.
What am I doing wrong?
You must add System.Generics.Collections to your uses clause. That is the unit which declares TObjectList<T>.
I've added documentation links to the answer. When you cannot find a class, look for it in the documentation. That will tell you which unit you need to use.
As well as TObjectList<T> there is TList<T>. It makes sense to use TObjectList<T> when you want the list to own its members. Otherwise, there is no benefit in using TObjectList<T> and you may as well use TList<T>. As well as the built-in Delphi generic containers, there are many excellent third-party containers that are often superior. For instance, the Delphi Spring Framework has a fine collection of containers.
Currently when i click on a button it will create some shapes on a new form. Once i close the new form how can i destroy the shapes it made.
I can add more info if needed but was hopeing there was a simple way to destroy all TMachine instances when the form closed.
TMachine is a TShape Class
procedure TFLayout1.GetClick(Sender: TObject);
var
azone: string;
adept: string;
machine : TMachine;
begin
fdb.count := 0; //keeps track of number of machines in zone
azone := MyDataModule.fDB.GetZone(Name); //gets name of zone
adept := TButton(Sender).Name; //gets name of dept
fdeptlayout.ListBox1.Clear;
fdeptlayout.show;
with fdeptlayout.ADOQuery1 do
begin
sql.Clear;
sql.BeginUpdate;
sql.Add('SELECT');
sql.Add(' *');
sql.Add('FROM');
sql.Add(' `MList`');
sql.Add('WHERE `Zone` = :myzone ');
sql.Add(' AND `Dept` = :mydept');
sql.EndUpdate;
parameters.ParamByName('myzone').Value := azone;
parameters.ParamByName('mydept').Value := adept;
open;
end;
//gets number of machines in total
while not fdeptlayout.ADOQuery1.Eof do
begin
fdb.count := fdb.count+1;
fdeptlayout.ADOQuery1.Next;
end;
//restarts back at first query
fdeptlayout.ADOQuery1.First;
//clears the last x value
fdb.LastX :=0;
//creates the shape
while not fdeptlayout.ADOQuery1.Eof do
begin
machine := MachineShape.TMachine.Create(self);
machine.Parent := fdeptlayout;
machine.PlaceShape(44,44,'CM402','first','123/33/123');
fdeptlayout.ListBox1.Items.Add(fdeptlayout.ADOQuery1.FieldByName('Name').AsString);
fdeptlayout.ADOQuery1.Next;
end;
end;
TMachine Class
unit MachineShape;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, extctrls,myDataModule,Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TMachine = class(TShape)
private
{ Private declarations }
public
{ Public declarations }
procedure PlaceShape(sizeW,sizeH :integer; name, order,asset : string);
end;
implementation
Procedure TMachine.PlaceShape(sizeW,sizeH :integer; name, order,asset : string);
begin
self.width := sizeW;
self.height := sizeH;
self.top := 136;
self.left := MyDataModule.fDB.LastX +2;//set left
MyDataModule.fDB.lastx := left + sizeW;
showmessage(inttostr(mydatamodule.fDB.LastX));
end;
end.
FDeptLayout
unit DeptLayout;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls,mydatamodule, Vcl.Forms, Vcl.Dialogs, Data.DB, Data.Win.ADODB, Vcl.StdCtrls,
Vcl.ExtCtrls;
type
TfDeptLayout = class(TForm)
ADOQuery1: TADOQuery;
ListBox1: TListBox;
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
public
{ Public declarations }
end;
var
fDeptLayout: TfDeptLayout;
implementation
{$R *.dfm}
procedure TfDeptLayout.FormClose(Sender: TObject; var Action: TCloseAction);
begin
end;
end.
The shown code is taking advantage of the VCL ownership model and the form will free it for you, as you just pass the form itself as the owner of your components when you create it:
machine := MachineShape.TMachine.Create(self);
as this is called from the TFLayout1 class, when the particular instance of the form is destroying itself, it will free all the owned components.
For a little more info, you can read the article: Owner vs. Parent in Delphi.
Edit
From comments, it resulted you create the TMachine instances on a class different of the form on which you show it, and you don't destroy the form instance when you close it, so, you can reach what you want making this changes:
Make the form in which the shapes are shown the owner, changing your code to create them to this:
//don't use self, now the parent is the instance referenced by fdeptlayout
machine := MachineShape.TMachine.Create(fdeptlayout);
On your Tfdeptlayout class, add a OnClose handler with this code:
begin
for I := ComponentCount - 1 downto 0 do
if Components[I] is TMachine then
Components[I].Free;
end;
That said, you really have to read the documentation and referenced articles to gain some understanding of what's going on behind the scenes in your Delphi application.
You are assigning an Owner to your TMachine objects. The shapes will be freed automatically when the Owner itself is freed.
Assuming TFLayout1 is your Form class, then by default it will not be freed automatically when it is closed. A closed Form is hidden by default so you can re-show when needed. To actually free it on close, you have to either set the Action parameter in the TForm.OnClose event to caFree, or call TForm.Free() directly sometime after the form is closed (such as if you are displaying the Form with ShowModal(), then you can call Free() after ShowModal() exits).
If you want to free the shapes yourself without relying on the behavior of an Owner, then set the Owner to nil when you create the shapes, and store your TMachine pointers in a TList that you can loop through when needed to free each shape, or a TObjectList with its OwnsObjects property set to true that you can Clear() when needed. Such as in the Form's OnClose event.
Below is a very simple bit of code that mimics a class structure in some code I have (the form just contains a single button attached to the click event). I am using Delphi XE and XE II and see nasty crashes when destroying objects in my production code that this class is based on. These crashes only occur if I allow the members of the array item in TMyClass to be initialised in the Clear() method.
Unfortunately the crash is not repeatable so far in this sample code, but it does mimic some very weird behaviour with instance sizing that I suspect might be the cause of the problem.
If I place a break point in the TMyClass.Clear function and look at the InstanceSize member of the class reports 1288. Which is odd, as the size of the array member alone is really 12kb. I can change the type being provided from TRecord2 to Integer and I get the same InstanceSize result. If I remove the array entirely from the class I get an Instance size of ~264 bytes, which seems over the top for a class that contains only a single 128 byte record instance. This indicates that the compiler has allocated 1024 bytes to the array storage for the V (of type TT) member in TMyClass.
I am suspicious that the weird instance size is causing bad things to happen when I write 12kb of data into an object that the compiler thinks is 1kb in size.
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
type
TRecord = record A : Array[1..128] of byte; end;
TRecord2 = packed record S : Single; T : TDateTime; end;
TBase = class(TObject)
public
R : TRecord;
end;
TBase2<T> = class(TBase)
public
type
TT = packed array [0..31, 0..31] of T;
var
V : TT;
R2 : TRecord;
end;
TMyClass = class(TBase2<TRecord2>)
public
procedure Clear;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
O : TMyClass;
begin
O := TMyClass.Create;
O.Clear;
O.Free;
end;
{ TMyClass }
procedure TMyClass.Clear;
var
i, j : integer;
begin
for i := 0 to 31 do
for j := 0 to 31 do
begin
V[I, J].S := 0;
V[I, J].T := 0;
end;
end;
end.
It's a bug, as is shown by this minimal reproduction:
program InstanceSizeBug;
{$APPTYPE CONSOLE}
type
TMyClass<T> = class
V: array [0..255] of T;
end;
TMyClassSingle = class(TMyClass<Single>);
begin
Writeln(TMyClass<Single>.InstanceSize);
Writeln(TMyClassSingle.InstanceSize);
Readln;
end.
Output:
1032
264
Note that the same behaviour can be observed in Delphi 2010, the only other Delphi version i have to hand.
Trace through under the debugger and you find yourself in _GetMem with the Size parameter equal to 264 so clearly not enough memory is being allocated.
And just in case there is any doubt, this version fails with an AV.
program InstanceSizeBug;
{$APPTYPE CONSOLE}
type
TMyClass<T> = class
V: array [1..256*256] of T;
end;
TMyClassSingle = class(TMyClass<Single>);
begin
TMyClassSingle.Create.V[256*256] := 0;
end.
I have submitted this to Quality Central, issue #100561.
As a workaround I suggest you use a dynamic array which you allocate using SetLength in the constructor. I rather imagine that it is the presence of a fixed sized generic array that is making the compiler misbehave.