vanatteveldt
asked on
Runtime error 216 on closing application (again)
Dear Experts,
[cross-posted to www.nldelphi.com]
I'm troubled by an annoying but otherwise harmless run-time error 216 that my application generates on closing. The source code is too long to post, and I am unable to locate the code that causes the error, so I will try to just give a short description of the program:
- DB application, uses MS Access and Delphi built-in data aware components and own db-aware components. One of these (the only 'non-cosmetic' descendant) is a data-aware twebbrowser descendant.
- Uses subforms by docking other forms into the mainform
- Uses gsapi (ghostscript dll) and wingraphvi (com component).
- Uses interfaces for most interobject communication
The vague thing is that the error seems to disappear and reappear as I continue to work on the application, even if I add or remove things that seem completely unrelated. However, if the error occurs restarting the application never changes that. The only thing that seems to have effect is not connecting to the access database, which (usually) causes the error not to appear. I am using a win2k machine with D6 and D7 installed, both versions generate the error.
The nasty thing is that if I trace my closing code (even with the VCL components in the search path) the error seems to occur *after* the last line of code, which is de finalization of ADODB. It traces through the whole Form.DoneApplication down to the last 'end;', then into ADODB. After hitting f7 on the last 'end;' there, it gives an Access Violation followed by a Runtime Error if I hit f7 again.
Most forums on the Net seem to hint that I might be trying to free an object that the VCL will want to free later, or free an object multiple times. However, commenting out all lines that free objects (except for the obvious StringLists, Inifiles etc.) does not solve the problem. Freeing or not freeing my datamodule also does not seem to do anyting. Actually, commenting out my entire 'onclose' code does not seem to change anything at all.
<edit> Since I suspected my own data-aware web browser (the only component not previuosly used in applications) I removed it from the form it was in. This did not change anythign </edit>
I realize that similar questions have been asked before, but I could not find any hints on these threads that solved my problem. I hope anybody can help me out here.
Kind regards,
Wouter van Atteveldt
[cross-posted to www.nldelphi.com]
I'm troubled by an annoying but otherwise harmless run-time error 216 that my application generates on closing. The source code is too long to post, and I am unable to locate the code that causes the error, so I will try to just give a short description of the program:
- DB application, uses MS Access and Delphi built-in data aware components and own db-aware components. One of these (the only 'non-cosmetic' descendant) is a data-aware twebbrowser descendant.
- Uses subforms by docking other forms into the mainform
- Uses gsapi (ghostscript dll) and wingraphvi (com component).
- Uses interfaces for most interobject communication
The vague thing is that the error seems to disappear and reappear as I continue to work on the application, even if I add or remove things that seem completely unrelated. However, if the error occurs restarting the application never changes that. The only thing that seems to have effect is not connecting to the access database, which (usually) causes the error not to appear. I am using a win2k machine with D6 and D7 installed, both versions generate the error.
The nasty thing is that if I trace my closing code (even with the VCL components in the search path) the error seems to occur *after* the last line of code, which is de finalization of ADODB. It traces through the whole Form.DoneApplication down to the last 'end;', then into ADODB. After hitting f7 on the last 'end;' there, it gives an Access Violation followed by a Runtime Error if I hit f7 again.
Most forums on the Net seem to hint that I might be trying to free an object that the VCL will want to free later, or free an object multiple times. However, commenting out all lines that free objects (except for the obvious StringLists, Inifiles etc.) does not solve the problem. Freeing or not freeing my datamodule also does not seem to do anyting. Actually, commenting out my entire 'onclose' code does not seem to change anything at all.
<edit> Since I suspected my own data-aware web browser (the only component not previuosly used in applications) I removed it from the form it was in. This did not change anythign </edit>
I realize that similar questions have been asked before, but I could not find any hints on these threads that solved my problem. I hope anybody can help me out here.
Kind regards,
Wouter van Atteveldt
ASKER
I'm definately interested!
I've never really found a bug that I simply had no idea of how to exterminate, but I've also never been inside the system.pas, so it should be quite interesting/helpful.
I've never really found a bug that I simply had no idea of how to exterminate, but I've also never been inside the system.pas, so it should be quite interesting/helpful.
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
I'm definately interested!
I've never really found a bug that I simply had no idea of how to exterminate, but I've also never been inside the system.pas, so it should be quite interesting/helpful.
I've never really found a bug that I simply had no idea of how to exterminate, but I've also never been inside the system.pas, so it should be quite interesting/helpful.
ASKER
(sorry for the duplicate post)
I occasionally get similar problems which invariably are associated with trying to manually free something that has already been automatically freed.
It sounds like Madshi's tracker may be of great help.
One technique that I use is to liberally sprinkle calls to GetHeapStatus throughout unit 'finalization' code and then watching the value of HeapErrorCode at each breakpoint.
This will often 'find' a heap deallocation error which does not report itself immediately but waits until you've gone well past the error site. This late error reporting is a by-product of Delphi memory-management strategy.
Regards
Roger Fedyk
It sounds like Madshi's tracker may be of great help.
One technique that I use is to liberally sprinkle calls to GetHeapStatus throughout unit 'finalization' code and then watching the value of HeapErrorCode at each breakpoint.
This will often 'find' a heap deallocation error which does not report itself immediately but waits until you've gone well past the error site. This late error reporting is a by-product of Delphi memory-management strategy.
Regards
Roger Fedyk
I had a similar issue which even madExcept couldn't help me with: my mistakes were causing a third-party out-of-process COM server to generate this error when I was inadvertently leaving some pointers uninitialised.
The COM server apparently was attempting to free the unallocated memory on exit.
Yes, this would appear and disappear without any visible reason until I figured out exactly what I was doing wrong...
Could be something similar since you mentioned COM...
Regards,
Alex.
The COM server apparently was attempting to free the unallocated memory on exit.
Yes, this would appear and disappear without any visible reason until I figured out exactly what I was doing wrong...
Could be something similar since you mentioned COM...
Regards,
Alex.
vanatteveldt, try Madshi's madExcept fisrt, and if you still cann't track down the problem, I'll post here my strategy&tactics of fighting such problems.
Hi
Here is sample code for example:
//------------------------ Start of Source Code of Main Forms Unit
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtrls, Unit2;
type
TMainForm = class(TForm)
Panel1: TPanel;
Splitter1: TSplitter;
Panel2: TPanel;
Label1: TLabel;
Panel3: TPanel;
Splitter2: TSplitter;
Panel4: TPanel;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
procedure SetParent(P : TWinControl; C : TControl; A : TAlign; W, H , T, L : Integer);
overload;
procedure SetParent(P, C : TWinControl; A : TAlign; W, H , T, L : Integer); overload;
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
//------------------------ ---------- ---------- ---------- ---------- ---------- ----
procedure TMainForm.SetParent(P : TWinControl; C : TControl; A : TAlign; W, H, T, L : Integer);
begin
P.InsertControl(C);
C.Parent := P;
C.Align := A;
C.Height := H;
C.Width := W;
C.Top := T;
C.Left := L;
C.Show;
end;
//------------------------ ---------- ---------- ---------- ---------- ---------- ----
procedure TMainForm.SetParent(P, C : TWinControl; A : TAlign; W, H, T, L : Integer);
begin
P.InsertComponent(C);
C.Parent := P;
C.Align := A;
C.Height := H;
C.Width := W;
C.Top := T;
C.Left := L;
C.Show;
end;
//------------------------ ---------- ---------- ---------- ---------- ---------- ----
procedure TMainForm.FormCreate(Sende r: TObject);
var
C : TChildForm;
S: TSplitter;
begin
C := TChildForm.Create(Applicat ion);
SetParent(Panel3, C, alTop, 30, 90, 0, 0);
S := TSplitter.Create(nil);
SetParent(Panel3, S, alTop, 30, 5, 60, 0);
C := TChildForm.Create(Applicat ion);
SetParent(Panel3, C, alClient, 30, 30, 65, 0);
end;
//------------------------ ---------- ---------- ---------- ---------- ---------- ----
procedure TMainForm.FormDestroy(Send er: TObject);
var
Temp : TComponent;
begin
try
While Panel3.ComponentCount<>0 do
begin
Temp := Panel3.Components[0];
Panel3.RemoveComponent(Tem p);
Temp.Free;
end;
except
on exception do;
end;
end;
end.
//------------------------ End of Source Code of Main Forms Unit
//------------------------ Start of Main Forms dfm file code
object MainForm: TMainForm
Left = 190
Top = 114
Width = 544
Height = 375
Caption = 'OutLook Expree'
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 13
object Splitter1: TSplitter
Left = 0
Top = 41
Width = 536
Height = 5
Cursor = crVSplit
Align = alTop
ResizeStyle = rsLine
end
object Panel1: TPanel
Left = 0
Top = 0
Width = 536
Height = 41
Align = alTop
Caption = 'Panel1'
TabOrder = 0
end
object Panel2: TPanel
Left = 0
Top = 46
Width = 536
Height = 295
Align = alClient
Caption = 'Panel2'
TabOrder = 1
object Label1: TLabel
Left = 1
Top = 1
Width = 534
Height = 24
Align = alTop
AutoSize = False
Caption = 'Outlook Express'
Color = clMedGray
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -19
Font.Name = 'MS Sans Serif'
Font.Style = [fsBold]
ParentColor = False
ParentFont = False
Layout = tlCenter
end
object Splitter2: TSplitter
Left = 233
Top = 25
Width = 5
Height = 269
Cursor = crHSplit
end
object Panel3: TPanel
Left = 1
Top = 25
Width = 232
Height = 269
Align = alLeft
Caption = 'Panel3'
TabOrder = 0
end
object Panel4: TPanel
Left = 238
Top = 25
Width = 297
Height = 269
Align = alClient
Caption = 'Panel4'
TabOrder = 1
end
end
end
//------------------------ End of Main Forms dfm file code
//------------------------ Start of Source Code of Child Form Unit
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComCtrls;
type
TChildForm = class(TForm)
TreeView1: TTreeView;
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
ChildForm: TChildForm;
implementation
Uses Unit1;
{$R *.dfm}
//------------------------ ---------- ---------- ---------- ---------- ---------- ----
procedure TChildForm.FormDestroy(Sen der: TObject);
begin
MainForm.Panel3.RemoveComp onent(self );
end;
end.
//------------------------ End of Source Code of Child Forms Unit
//------------------------ Start of Child Forms dfm file code
object ChildForm: TChildForm
Left = 190
Top = 114
BorderIcons = [biSystemMenu]
BorderStyle = bsSingle
ClientHeight = 334
ClientWidth = 450
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
OnDestroy = FormDestroy
PixelsPerInch = 96
TextHeight = 13
object TreeView1: TTreeView
Left = 0
Top = 0
Width = 450
Height = 334
Align = alClient
Indent = 19
TabOrder = 0
end
end
//------------------------ End of Child Forms dfm file code
In this code you see i am creating 2 'child forms', and 'panel3' is the parent of them, and i do this all manually, so thats why it is my responsibility to destroy those forms manually from panel3. And you have already see that i write a code of destroying them in Main form's destroy procedure. when i run this application i got Access violation error on destroy event (note: first time i didn't write the code in Child form destroy procedure) or every time when i close main form even i remove the destroy event procedure. And i already know that the access violation errors are generate when we miss use the memory and you can't catch them in try...except block. Because these are generated by OS. Then i caught my mistake. You know that all visible components/controls are destroyed by application self. We have no need to destroy them manually. Acctually problem is that the owner of all forms is Application. And application is destroy them from last to first order(means the reverse of that order in which we create forms). Problem occurs when two child forms those are already destroyed are again try to destroy when application destroy Main form and all its visible Components or child components. And application took reference of two child forms from panel3 components list. I caught that and write code on child forms destroy event and remove self references from Main form's Panel3 component. And after that also remove event procedure reference from Main form destroy event. Because i have no need to destroy them manually. This is all problem that i have faced with this test application. And i think same problem is happen with you.
So you have to find that. It is simple you have to write code on ADOConnection OnDisconnect event and showing message that it is going to disconnect. And make sure it dont try disconnect it manually because you dont catch it when it destroy (note:- when you destroy ADOConnection manually or by other component/control/Applicat ion it is the responsibility of Destroy method to disconnect it(note: for this you read the help of ADOConnection destroy method)). With the help of this procedure i think you caught it when it is destroy.
With best regards
Fareed.
Here is sample code for example:
//------------------------
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtrls, Unit2;
type
TMainForm = class(TForm)
Panel1: TPanel;
Splitter1: TSplitter;
Panel2: TPanel;
Label1: TLabel;
Panel3: TPanel;
Splitter2: TSplitter;
Panel4: TPanel;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
procedure SetParent(P : TWinControl; C : TControl; A : TAlign; W, H , T, L : Integer);
overload;
procedure SetParent(P, C : TWinControl; A : TAlign; W, H , T, L : Integer); overload;
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
//------------------------
procedure TMainForm.SetParent(P : TWinControl; C : TControl; A : TAlign; W, H, T, L : Integer);
begin
P.InsertControl(C);
C.Parent := P;
C.Align := A;
C.Height := H;
C.Width := W;
C.Top := T;
C.Left := L;
C.Show;
end;
//------------------------
procedure TMainForm.SetParent(P, C : TWinControl; A : TAlign; W, H, T, L : Integer);
begin
P.InsertComponent(C);
C.Parent := P;
C.Align := A;
C.Height := H;
C.Width := W;
C.Top := T;
C.Left := L;
C.Show;
end;
//------------------------
procedure TMainForm.FormCreate(Sende
var
C : TChildForm;
S: TSplitter;
begin
C := TChildForm.Create(Applicat
SetParent(Panel3, C, alTop, 30, 90, 0, 0);
S := TSplitter.Create(nil);
SetParent(Panel3, S, alTop, 30, 5, 60, 0);
C := TChildForm.Create(Applicat
SetParent(Panel3, C, alClient, 30, 30, 65, 0);
end;
//------------------------
procedure TMainForm.FormDestroy(Send
var
Temp : TComponent;
begin
try
While Panel3.ComponentCount<>0 do
begin
Temp := Panel3.Components[0];
Panel3.RemoveComponent(Tem
Temp.Free;
end;
except
on exception do;
end;
end;
end.
//------------------------
//------------------------
object MainForm: TMainForm
Left = 190
Top = 114
Width = 544
Height = 375
Caption = 'OutLook Expree'
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
OnCreate = FormCreate
PixelsPerInch = 96
TextHeight = 13
object Splitter1: TSplitter
Left = 0
Top = 41
Width = 536
Height = 5
Cursor = crVSplit
Align = alTop
ResizeStyle = rsLine
end
object Panel1: TPanel
Left = 0
Top = 0
Width = 536
Height = 41
Align = alTop
Caption = 'Panel1'
TabOrder = 0
end
object Panel2: TPanel
Left = 0
Top = 46
Width = 536
Height = 295
Align = alClient
Caption = 'Panel2'
TabOrder = 1
object Label1: TLabel
Left = 1
Top = 1
Width = 534
Height = 24
Align = alTop
AutoSize = False
Caption = 'Outlook Express'
Color = clMedGray
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -19
Font.Name = 'MS Sans Serif'
Font.Style = [fsBold]
ParentColor = False
ParentFont = False
Layout = tlCenter
end
object Splitter2: TSplitter
Left = 233
Top = 25
Width = 5
Height = 269
Cursor = crHSplit
end
object Panel3: TPanel
Left = 1
Top = 25
Width = 232
Height = 269
Align = alLeft
Caption = 'Panel3'
TabOrder = 0
end
object Panel4: TPanel
Left = 238
Top = 25
Width = 297
Height = 269
Align = alClient
Caption = 'Panel4'
TabOrder = 1
end
end
end
//------------------------
//------------------------
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, ComCtrls;
type
TChildForm = class(TForm)
TreeView1: TTreeView;
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
ChildForm: TChildForm;
implementation
Uses Unit1;
{$R *.dfm}
//------------------------
procedure TChildForm.FormDestroy(Sen
begin
MainForm.Panel3.RemoveComp
end;
end.
//------------------------
//------------------------
object ChildForm: TChildForm
Left = 190
Top = 114
BorderIcons = [biSystemMenu]
BorderStyle = bsSingle
ClientHeight = 334
ClientWidth = 450
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'MS Sans Serif'
Font.Style = []
OldCreateOrder = False
OnDestroy = FormDestroy
PixelsPerInch = 96
TextHeight = 13
object TreeView1: TTreeView
Left = 0
Top = 0
Width = 450
Height = 334
Align = alClient
Indent = 19
TabOrder = 0
end
end
//------------------------
In this code you see i am creating 2 'child forms', and 'panel3' is the parent of them, and i do this all manually, so thats why it is my responsibility to destroy those forms manually from panel3. And you have already see that i write a code of destroying them in Main form's destroy procedure. when i run this application i got Access violation error on destroy event (note: first time i didn't write the code in Child form destroy procedure) or every time when i close main form even i remove the destroy event procedure. And i already know that the access violation errors are generate when we miss use the memory and you can't catch them in try...except block. Because these are generated by OS. Then i caught my mistake. You know that all visible components/controls are destroyed by application self. We have no need to destroy them manually. Acctually problem is that the owner of all forms is Application. And application is destroy them from last to first order(means the reverse of that order in which we create forms). Problem occurs when two child forms those are already destroyed are again try to destroy when application destroy Main form and all its visible Components or child components. And application took reference of two child forms from panel3 components list. I caught that and write code on child forms destroy event and remove self references from Main form's Panel3 component. And after that also remove event procedure reference from Main form destroy event. Because i have no need to destroy them manually. This is all problem that i have faced with this test application. And i think same problem is happen with you.
So you have to find that. It is simple you have to write code on ADOConnection OnDisconnect event and showing message that it is going to disconnect. And make sure it dont try disconnect it manually because you dont catch it when it destroy (note:- when you destroy ADOConnection manually or by other component/control/Applicat
With best regards
Fareed.
>> you can't catch them in try...except block.
>> Because these are generated by OS
That's not generally true. Try..except does catch external/OS exceptions, too.
>> Because these are generated by OS
That's not generally true. Try..except does catch external/OS exceptions, too.
Hi
Ok i understand it but i mean to say that Access Violation errors are not catch in try....except block. Thanks Mr. Madshi for correct my sentence.
With Best Regards
Fareed.
Ok i understand it but i mean to say that Access Violation errors are not catch in try....except block. Thanks Mr. Madshi for correct my sentence.
With Best Regards
Fareed.
Hi Fareed,
please try this code outside of the IDE:
try
integer(nil^) := 0;
except MessageBox(0, 'error', 'error', 0) end;
The try..except block *does* catch access violation errors. This works only if the project uses SysUtils, though. But 99% of all projects do.
Regards, Madshi.
please try this code outside of the IDE:
try
integer(nil^) := 0;
except MessageBox(0, 'error', 'error', 0) end;
The try..except block *does* catch access violation errors. This works only if the project uses SysUtils, though. But 99% of all projects do.
Regards, Madshi.
Hi
You are right but application always terminating abnormal, and successful error handling is that your application cant terminate abnormal. This is my thinking. But i do agree with you.
With Best Regards.
Fareed.
You are right but application always terminating abnormal, and successful error handling is that your application cant terminate abnormal. This is my thinking. But i do agree with you.
With Best Regards.
Fareed.
Hi
You are right but application always terminating abnormal, and successful error handling is that your application cant terminate abnormal. This is my thinking. But i do agree with you.
With Best Regards.
Fareed.
You are right but application always terminating abnormal, and successful error handling is that your application cant terminate abnormal. This is my thinking. But i do agree with you.
With Best Regards.
Fareed.
ASKER
Madshi: I used your exception tracker (which certainly looks very professional compared to simply 'runtime error 216' :-)) and got the following results:
-- Bug report --
date/time : 2003-01-06 22:29
computer name : WISP
user name : wouter
operating system : Windows 2000 Service Pack 2
free disk space : 574,17 MB
executable : iNet.exe
exec. date/time : 2003-01-06 22:29
exception class : EAccessViolation
exception message : Access violation at address 004067C0 in module 'iNet.exe'. Read of address 000025B0.
main thread ($5F4):
$004067BE iNet.exe System 17222 @IntfClear
$0040549F iNet.exe System 14004 @FinalizeArray
$004053CC iNet.exe System 13827 @FinalizeRecord
$004D1CA0 iNet.exe unitInterfaces Finalization
$0040440E iNet.exe System 10512 FinalizeUnits
$00419B4A iNet.exe madExcept 2388 InterceptFinalizeUnits
$004046B1 iNet.exe System 11048 @Halt0
$004FF3BC iNet.exe iNet 31 EntryPoint
....
----------------
Place in the system.pas where the exception occured:
----------------
function _IntfClear(var Dest: IInterface): Pointer;
{$IFDEF PUREPASCAL}
var
P: Pointer;
begin
Result := @Dest;
if Dest <> nil then
begin
P := Pointer(Dest);
Pointer(Dest) := nil;
IInterface(P)._Release;
end;
end;
{$ELSE}
asm
MOV EDX,[EAX]
TEST EDX,EDX
JE @@1
MOV DWORD PTR [EAX],0
PUSH EAX
PUSH EDX
MOV EAX,[EDX]
>> HERE <<
CALL DWORD PTR [EAX] + VMTOFFSET IInterface._Release
>> /HERE <<
POP EAX
@@1:
end;
----------------
My interpretation is that possibly the refcount mechanism tries to free an object that is already freed. However, all the interfaces I use are interfaces to forms that are owned and freed by delphi, so I don't feel I'm doing anything wrong there.
Remaining questions:
1) do I interpret the error messages correctly?
2) is there anything wrong with using interfaces to normal forms/components? should I take specials care with their destruction? Should I perhaps not give them owners (i.e. use create(nil)) but instead rely on the refcount for destruction? And for the main form?
-- Bug report --
date/time : 2003-01-06 22:29
computer name : WISP
user name : wouter
operating system : Windows 2000 Service Pack 2
free disk space : 574,17 MB
executable : iNet.exe
exec. date/time : 2003-01-06 22:29
exception class : EAccessViolation
exception message : Access violation at address 004067C0 in module 'iNet.exe'. Read of address 000025B0.
main thread ($5F4):
$004067BE iNet.exe System 17222 @IntfClear
$0040549F iNet.exe System 14004 @FinalizeArray
$004053CC iNet.exe System 13827 @FinalizeRecord
$004D1CA0 iNet.exe unitInterfaces Finalization
$0040440E iNet.exe System 10512 FinalizeUnits
$00419B4A iNet.exe madExcept 2388 InterceptFinalizeUnits
$004046B1 iNet.exe System 11048 @Halt0
$004FF3BC iNet.exe iNet 31 EntryPoint
....
----------------
Place in the system.pas where the exception occured:
----------------
function _IntfClear(var Dest: IInterface): Pointer;
{$IFDEF PUREPASCAL}
var
P: Pointer;
begin
Result := @Dest;
if Dest <> nil then
begin
P := Pointer(Dest);
Pointer(Dest) := nil;
IInterface(P)._Release;
end;
end;
{$ELSE}
asm
MOV EDX,[EAX]
TEST EDX,EDX
JE @@1
MOV DWORD PTR [EAX],0
PUSH EAX
PUSH EDX
MOV EAX,[EDX]
>> HERE <<
CALL DWORD PTR [EAX] + VMTOFFSET IInterface._Release
>> /HERE <<
POP EAX
@@1:
end;
----------------
My interpretation is that possibly the refcount mechanism tries to free an object that is already freed. However, all the interfaces I use are interfaces to forms that are owned and freed by delphi, so I don't feel I'm doing anything wrong there.
Remaining questions:
1) do I interpret the error messages correctly?
2) is there anything wrong with using interfaces to normal forms/components? should I take specials care with their destruction? Should I perhaps not give them owners (i.e. use create(nil)) but instead rely on the refcount for destruction? And for the main form?
------------------------
vanatteveldt wrote:
is there anything wrong with using interfaces to normal forms/components? should I take specials care with their destruction? Should I perhaps not give them owners (i.e. use create(nil)) but instead rely on the refcount for destruction? And for the main form?
------------------------
There can be a problem since interfaces are managed by Delphi. Delphi calls Release when a variable of interface type goes out of scope, and this is exactly what happens in unit filnalization when you have in that unit global variable holding an interface to some object. You should choose which mechanism to use for controlling lifetime of objects - either native Delphi ownership mechanism or the COM reference counting.
Regarding Main form - you should either put nil pointer into all variables holding Main form interface(s) upon Main form destruction or never call Main form's Free method, which means you have to change project source so that Main form will not be owned by Application.
vanatteveldt wrote:
is there anything wrong with using interfaces to normal forms/components? should I take specials care with their destruction? Should I perhaps not give them owners (i.e. use create(nil)) but instead rely on the refcount for destruction? And for the main form?
------------------------
There can be a problem since interfaces are managed by Delphi. Delphi calls Release when a variable of interface type goes out of scope, and this is exactly what happens in unit filnalization when you have in that unit global variable holding an interface to some object. You should choose which mechanism to use for controlling lifetime of objects - either native Delphi ownership mechanism or the COM reference counting.
Regarding Main form - you should either put nil pointer into all variables holding Main form interface(s) upon Main form destruction or never call Main form's Free method, which means you have to change project source so that Main form will not be owned by Application.
ASKER
rpo: thanks for this hint. I had naively assumed that since all Delphi Component descendants implement the standard interface that somehow using them in interfaces and as normal VCL 'owned' components would not conflict. I changed all creates of interfaced objects to create(nil) and the error disappeared (so I hope it will not reappear as it has in the past). This leaves me with two fundamental questions:
1) Is there any way to use interfaces (or any functional equivalent; i.e. some mechanism that provides me with a reference to a component without giving me access to all published fields) without using the refcount mechanism in Delphi?
2) Does this mean I should create my own 'fake' destructors that don't really destroy an object but do release all interfaces? To me it seems that this is asking for problems with circular references...
Bottom line is: I don't like the Delphi/COM interfaces with refcounts, but I do like the OO concept of interfaces. Does this mean I should pick a different language?
1) Is there any way to use interfaces (or any functional equivalent; i.e. some mechanism that provides me with a reference to a component without giving me access to all published fields) without using the refcount mechanism in Delphi?
2) Does this mean I should create my own 'fake' destructors that don't really destroy an object but do release all interfaces? To me it seems that this is asking for problems with circular references...
Bottom line is: I don't like the Delphi/COM interfaces with refcounts, but I do like the OO concept of interfaces. Does this mean I should pick a different language?
Your language choice is fine.
The result from madExcept definitely points to your doing some sort of memory deallocation on a pointer or object that Delphi is trying to automatically free during finalization.
As I mentioned in my earlier comment, I have had the same problem on a number of occasions and have discovered that the original deallocation has happened long before finalization is entered.
Trying to find the error site may be time-consuming. One of the reasons for all this FUD is Delphi's major weakness (my opinion) namely not automatically nulling all pointers when it frees them.
The result from madExcept definitely points to your doing some sort of memory deallocation on a pointer or object that Delphi is trying to automatically free during finalization.
As I mentioned in my earlier comment, I have had the same problem on a number of occasions and have discovered that the original deallocation has happened long before finalization is entered.
Trying to find the error site may be time-consuming. One of the reasons for all this FUD is Delphi's major weakness (my opinion) namely not automatically nulling all pointers when it frees them.
I wonder if FreeAndNil would help?
vanatteveldt, you can acieve the functionality you want, i.e. using interfaces without reference counting, only if you build your own hierarchy of classes. Overriding destructor isn't the solution, probably overriding the _Release method is.
All the existing "interfaced" classes implement IUnknown interface with reference counting. Look at the implementation of TInterfacedObject, for example (it does implement reference counting).
DragonSlayer, FreeAndNil wouldn't help since you may have more than one reference to an object. FreeAndNil will only null one of them, the others will remain invalid and eventually lead to access violation.
All the existing "interfaced" classes implement IUnknown interface with reference counting. Look at the implementation of TInterfacedObject, for example (it does implement reference counting).
DragonSlayer, FreeAndNil wouldn't help since you may have more than one reference to an object. FreeAndNil will only null one of them, the others will remain invalid and eventually lead to access violation.
This is my interpretation of madExcept's bug report:
$004067BE iNet.exe System 17222 @IntfClear
$0040549F iNet.exe System 14004 @FinalizeArray
$004053CC iNet.exe System 13827 @FinalizeRecord
$004D1CA0 iNet.exe unitInterfaces Finalization
The unit "unitInterfaces" runs through the finalization and as a result of that @FinalizeRecord is called. There's no line number shown for "unitInterfaces". I guess its your own unit? In that case I guess that @FinalizeRecord is not called as a result of your own code, but as a result of Delphi's automatic global variable destruction during finalization of each unit. So I guess that you have a record in your "unitInterfaces", which contains an array, which contains interfaces. And at least one of those interfaces probably was already destroyed. Now you should try to find out which record/array Delphi is trying to free. Should be possible by checking the global variables of "unitInterfaces". What global variable is Delphi trying to finalize at address "$004D1CA0"?
So it seems that something is wrong with your interface handling. See all the other comments. I have nothing (important) to add to that discussion.
Regards, Madshi.
$004067BE iNet.exe System 17222 @IntfClear
$0040549F iNet.exe System 14004 @FinalizeArray
$004053CC iNet.exe System 13827 @FinalizeRecord
$004D1CA0 iNet.exe unitInterfaces Finalization
The unit "unitInterfaces" runs through the finalization and as a result of that @FinalizeRecord is called. There's no line number shown for "unitInterfaces". I guess its your own unit? In that case I guess that @FinalizeRecord is not called as a result of your own code, but as a result of Delphi's automatic global variable destruction during finalization of each unit. So I guess that you have a record in your "unitInterfaces", which contains an array, which contains interfaces. And at least one of those interfaces probably was already destroyed. Now you should try to find out which record/array Delphi is trying to free. Should be possible by checking the global variables of "unitInterfaces". What global variable is Delphi trying to finalize at address "$004D1CA0"?
So it seems that something is wrong with your interface handling. See all the other comments. I have nothing (important) to add to that discussion.
Regards, Madshi.
ASKER
madshi: I accepted your original comment as answer; My main problem was being clueless about where the error occured, your exception package solved that.
rpo: I can hardly build all my forms from TObject ("build your my hierarchy of classes") without loosing most advantages of Delphi. However, it might be a nice experiment to try and override all the refcount methods and make them dummy methods to stop the refcount mechanism. Since these methods are static, however, I'm unsure as to which method would be used.
One last thing I do not understand:
If I have a form in a variable f of type TMyForm, and if TMyForm implements dummyInterface, why doesn't the following code destroy the form:
var d : dummyInterface
begin
d := f;
d := nil;
end;
(This being the only place the dummyInterface is ever used)
rpo: I can hardly build all my forms from TObject ("build your my hierarchy of classes") without loosing most advantages of Delphi. However, it might be a nice experiment to try and override all the refcount methods and make them dummy methods to stop the refcount mechanism. Since these methods are static, however, I'm unsure as to which method would be used.
One last thing I do not understand:
If I have a form in a variable f of type TMyForm, and if TMyForm implements dummyInterface, why doesn't the following code destroy the form:
var d : dummyInterface
begin
d := f;
d := nil;
end;
(This being the only place the dummyInterface is ever used)
---------------------
var d : dummyInterface
begin
d := f;
d := nil;
end;
---------------------
f does not get destroyed because of TComponent's implementation of _Release which does not destroy the object even if RefCount becomes zero.
And this contradicts to what I wrote before (sorry). You can use Delphi interfaces without reference counting unless you start using VCLComObject and make it FreeOnRelease. This is correct for VCL clases which are not COM classes. It is not correct for COM and COM-like classes such as TComObject, TInterfacedObject, etc., they do destroy an object when RefCount reaches zero.
It appears that everything possible with Delphi :)
var d : dummyInterface
begin
d := f;
d := nil;
end;
---------------------
f does not get destroyed because of TComponent's implementation of _Release which does not destroy the object even if RefCount becomes zero.
And this contradicts to what I wrote before (sorry). You can use Delphi interfaces without reference counting unless you start using VCLComObject and make it FreeOnRelease. This is correct for VCL clases which are not COM classes. It is not correct for COM and COM-like classes such as TComObject, TInterfacedObject, etc., they do destroy an object when RefCount reaches zero.
It appears that everything possible with Delphi :)
ASKER
In that case, I still do not know why the deallocation of the global struct of interfaces in UnitInterfaces (see above) causes an error... So I guess the topic is not exhausted yet!
I think the problem was in following scenario :
1) the form was first destroyed through components ownership mechanism.
2) there was an attempt to release form's interface in unit finalization. But, at this point the interface points to already destroyed object so the call to _Release caused access violation and not the call to Destroy from _Release.
1) the form was first destroyed through components ownership mechanism.
2) there was an attempt to release form's interface in unit finalization. But, at this point the interface points to already destroyed object so the call to _Release caused access violation and not the call to Destroy from _Release.
If there's no general error with your interface handling you should now probably try to find out which record/array "unitInterfaces" tries to finalize and about which interface exactly it is stumbling.
ASKER
(What I really should do of course is stop using that global struct (which was only temporary anyway) and use a proper OO infrastructure....)
Actually my 216 has disappeared again so I'm afraid I'll have to wait until it chooses to show up again before I can continue debugging... But I suppose setting the struct to nil in my own finalization code so that the error is encountered in the normal program flow instead of during delphi unit finalization might be a very good start...
Actually my 216 has disappeared again so I'm afraid I'll have to wait until it chooses to show up again before I can continue debugging... But I suppose setting the struct to nil in my own finalization code so that the error is encountered in the normal program flow instead of during delphi unit finalization might be a very good start...
Tracking down the problem requires debugging RTL code (the system.pas unit).
Debugging system.pas is not as trivial as debugging VCL, so
if you're interested I can provide you with almost step-by-step instruction of the process.