Link to home
Start Free TrialLog in
Avatar of MichaelStaszewski
MichaelStaszewskiFlag for United States of America

asked on

Invalid Pointer Operation - TApplication.Components holds bad reference

[EDIT] I am using Delphi XE2 with update 3. We cannot upgrade to 4 at this time.

Hey there, hopefully there is a simple solution here because this problem is rather urgent. I will likely not be able to test solutions until morning though.

This closely mirrors our code. It uses a session component and a query component in the ODAC suite by Corelab. I am not sure that those exact components matter. I think I could get the same results with any TComponent. Following this access violation, shutting down the application generates Invalid Pointer Operation and the application hangs. It will not close. Application.Components still maintains a reference to the created TForm2 even though the free is being called.

uses
  Unit2, Ora;

type
  TMyClass = class
  public
    Sess: TOraSession;
  end;

procedure TForm1.Button1Click(Sender: TObject);
var
  Frm: TForm2;
  MyClass: TMyClass;
begin
  Frm := TForm2.Create(Application);
  try
    Frm.Qry.Session := MyClass.Sess; // AV generated here
  finally
    Frm.Free;
  end;
end;

Open in new window


Thanks in advance,
Michael
Avatar of MichaelStaszewski
MichaelStaszewski
Flag of United States of America image

ASKER

I forgot to add that the problems go away if nil is used as TForm2's owner (as expected) and that Frm.release and freeandnil(frm) have no effect.

Also, the code that is failing is when TApplication destroy's its components. Going to test Delphi 2009 now to see if this issue is a new addition to Delphi.

Delphi 2009 also fails. Any insight on how to avoid this would be fantastic. Ideally we'll not code in exceptions :-) but we have a ton of code and unfortunately things slip through. It would be nice to have EurekaLog grab the unhandled ones and still let the application shutdown without hanging.
Avatar of Geert G
you aren't showing MyClass := TMyClass.Create; line

the AV you get here is correct as MyClass has not been instantiated and thus
if Assigned(MyClass) is false


if Assigned(MyClass) then
  frm.qry.Session := MyClass.Session;


maybe MyClass is owned by a other form which gets freed just before Form2  gets freed
I apologize, but I should have been clearer. I am not creating TMyClass intentionally to produce the AV. We have an AV condition in our code that we can solve, but it illustrates a scenario I thought was impossible. In my code, above, frm.free is being called. It is my understanding that this will remove the object from the components list of its owner. In this case, Application.Components retains a reference to the freed form and invalid pointer operation is raised at shutdown. The exception is only seen when debugging in the IDE. The application is still running with no taskbar button and pegs the CPU. Why is this? Why does Application retain the reference?

I'll try to find a reproducible scenario using native components in the morning. Thanks.
I should also add that i have 2 forms. TForm2 contains a query component and no code. TForm1 contains a TButton and only the code shown above in the implementation section.
it should come down to where you create the session and where it is freed
and after it is freed, where it is still referenced

always use freeandnil to free a component (even a form)
Freeandnil has no effect here. The raised exception is somehow preventing the call to free from notifying its owner that it is no longer instantiated. The owner still thinks it owns this freed object.
the form will only free objects which it owns

you probably have this

unit Form1:
var MyClass: TClass;
MyClass := TClass.Create(Form1);

Form2
Close > MyClass.Free;

why not add logging to the destructor of TMyClass ?

destructor TMyClass.Destroy;
begin
  //>> Breakpoint and Call stack Ctrl-F3 to find where freed
  inherited Destroy;
end;
No. I have only as described and what you see in my sample code. My sample project, used purely for illustrative purposes has two forms. TForm1 (AKA Application.mainform) has a single TButton with assigned OnClick and a simple class. All code for the class and OnClick are shown above. There is nothing more in TForm1. Another form, TForm2, contains a single component. It is a TSmartQuery from a 3rd party library we use. There is no code at all in TForm2. This is the *entire* code in my sample app created to isolate the behavior I'm seeing.

The AV is trivial. We have isolated and fixed it in our production app. What is unclear to me is why TApplication continues to think that it owns an instance of TForm2 (in my sample app) when it has clearly been freed. It is the first time in my 10 years of Delphi experience that I've seen this and the same goes for my colleagues who each have more Delphi experience than myself. Ideally what we'd like to do is prevent this scenario from occuring for cases where we may not catch exceptions before we release our product. Our product is large, 3.2 million lines of code, and although it is tested thouroughly before release some bugs do escape detection and in those cases it would be nice to avoid this hanging scenario. If it's just the way it is then so be it, but I've not knowingly seen this before.

I hope the problem is more clearly described now.

Thanks,
Michael
ASKER CERTIFIED SOLUTION
Avatar of Geert G
Geert G
Flag of Belgium image

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
ugh ... sometimes i do get a bug in Toad ... :)
:-)

That's what we try to avoid. I'll try a case w/ devart and post back. We are on the devart version, latest version, but i see this in an earlier one too in Delphi 9.

Thanks,
Michael
I mean... Delphi *2009*

And the sample is terrible. It's poor code for sure... sometimes unexpected conditions allow such scenarios to exist. :-(
this is just guessing, but could it be a timing issue ?

sample:
set a timer on form1 with a event handler from form2
(only possible in code)
after freeing form1 sometimes the event handler still gets called

procedure TForm1.OnCreate;
begin
  Timer1.OnTimer := Form2OnTimer;
end;

>> Free(Form1);

procedure TForm2.OnTimer;
begin
  Form1.Caption := 'Timer called'; >> Av here
end;
I have seen TTimer issues many times in the past doing exactly what you suggest. My solution for that has always been to disable all timers in TForm.OnDestroy handler. I'm not sure that the same issue applies here. Nevertheless I thought perhaps you were on to something so I wrapped my code in a try..except just to see if the open exception dialog in the application while the form had been freed was the cause. No joy.

procedure TForm1.Button1Click(Sender: TObject);
var
  Frm: TForm2;
  MyClass: TMyClass;
begin
  Frm := TForm2.Create(Application);
  try
    try
      Frm.Qry.Session := MyClass.Sess;
    except
    end;
  finally
    Frm.Free;
  end;
end;

Open in new window

Thanks Geert_Gruwez for listening and providing your comments. After some sleep and coffee the problem is more clear. An ODAC control is raising an exception in its destructor due to the buggy code and it's bypassing necessary code in TComponent.Destroy. Ughh. The solution... don't create buggy code. :-) Everything is functioning as designed.

A correction. I saw massive exceptions in Delphi 2009, but it does not hang in that version afterall. I was mistaken. Eventually the application closes. This looks like an XE2 specific thing. I don't have XE to test with. It's a slow day, I'll try to get to the bottom of the hang and send to Embarcadero to see if there's a bug there. Crappy code aside it would be nice if the application could recover and close without CPU pegging.
you may also want to look at FreeNotification
Components wanting to be notified of other components being freed use FreeNotification
and RemoveFreeNotification
That was our solution to this particular problem. I was more or less looking to see if anyone had a possible solution to avoid application hang in the event of such an AV.