Link to home
Start Free TrialLog in
Avatar of vanatteveldt
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
Avatar of rpo
rpo

Well, I have some experience in solving similar problems.

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.
Avatar of vanatteveldt

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.
ASKER CERTIFIED SOLUTION
Avatar of Madshi
Madshi

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
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.
(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
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.
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(Sender: TObject);
var
  C : TChildForm;
  S: TSplitter;
begin
C := TChildForm.Create(Application);
SetParent(Panel3, C, alTop, 30, 90, 0, 0);
S := TSplitter.Create(nil);
SetParent(Panel3, S, alTop, 30, 5, 60, 0);
C := TChildForm.Create(Application);
SetParent(Panel3, C, alClient, 30, 30, 65, 0);
end;

//------------------------------------------------------------------------------
procedure TMainForm.FormDestroy(Sender: TObject);
var
  Temp : TComponent;
begin
try
  While Panel3.ComponentCount<>0 do
    begin
    Temp := Panel3.Components[0];
    Panel3.RemoveComponent(Temp);
    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(Sender: TObject);
begin
MainForm.Panel3.RemoveComponent(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/Application 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.
>> 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.
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.
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.
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.
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.
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?
------------------------
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.
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?
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.
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.
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.
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)
---------------------
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 :)
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.
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.
(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...