Link to home
Start Free TrialLog in
Avatar of lavitz
lavitzFlag for Poland

asked on

Invoking freed object's function

Hi,

Iam using Delphi 7.
I need to call function on object i.e  

try
 FObj.fun();
except
   // something
end;

procedure fun();
begin
  //some code
end;

Problem is that sometimes FObj is freed and delphi invoke this function anyway. But invoke it only some part of it. Result is that i have cursor with hourglass becouse at the begining of fun() is code that change cursor.

I search for solution that prevent call freed function. Or how i can check this.
I know that i can  do FreeAndNil on FObj.
Avatar of Geert G
Geert G
Flag of Belgium image

not to be picky, but your code is not correct

>>procedure fun();
should be
procedure TMyObject.fun();

use the try finally structure

procedure TMyObject.Fun;
begin
  Screen.Cursor := crHourGlass;
  try
    Raise Exception.Create('Hourglass problem');
  finally
    Screen.Cursor := crDefault;
  end;
end;
isn't it a handler being executed ?
probably from a timer ?

procedure TTimer1.OnTimer(Sender: TObject);
begin
  ShowMessage(Test);
end;

procedure TTimer2.OnTimer(Sender: TObject);
begin
  Sleep(2000);
  FreeAndNil(Timer1);
  Sleep(5000);
end;
 
Event for Timer1 is on the stack (interval = default 1 sec)
This causes the same problem
The solution is to remove the timer events from the event stack
Avatar of lavitz

ASKER

procedure TMyObject.Fun;
begin
  Screen.Cursor := crHourGlass;
  try
    Raise Exception.Create('Hourglass problem');
  finally
   //<---- 1
    Screen.Cursor := crDefault;
  end;
end;

Its close to my code but in place of //<---- 1 i have EndUpdate that cause access violation and Screen.Cursor := crDefault; is not executed. Ofcourse this happen when object not exists
Ofcouse i could move crDefault to the top but for now i search how to prevent to execut that code.

If its not possible i'll have to change concept of my project.
I have event manager. Some forms can register to EM and EM loop through registered 'events' and call callback function. But forms are not without errors so in some case i have leak, object is freed but still connected to EM.




> Problem is that sometimes FObj is freed and delphi invoke this function anyway.

Delphi will do whatever you ask it to do.
For example:

FObj: TObj;

procedure BadProc();
begin
  FObj := TObj.Create();
  FObj.fun(); // this is fine.
  FreeAndNil(FObj);
  FObj.fun(); // this is not.  But you have asked Delphi to run it anyway.
end;

What is actually happening is that the .fun member function is a specific offset in the TObj memory structure.  Even though FObj is now nil, Delphi will still go to the TObj class memory location for .fun and call it against the instance variable pointed to by the memory location of FObj.  Since it has been nil-ed, it could point to anywhere and you could either corrupt some memory or cause an Access Violation.
> object is freed but still connected to EM.

I think what you mean is that
EM was passed a pointer to object
object was freed, so the pointer is no longer referencing an instance of object class
EM tries to call member function of object via pointer

The object should retain a link (private property) to the EM, and on Destroy (override) disconnect itself from EM.
doesn't using if Assigned(Obj) help ?

if Assigned(fObj) then
  fObj.Fun;

> doesn't using if Assigned(Obj) help ?

No it doesn't. Try it.
It is the reason why there is a FreeAndNil function.
Freeing an object does not nil all references to the object.

EM is still holding a pointer to some location.  Nothing has set the pointer to nil, which is what Assigned() checks.
yes, that's just why FreeAndNil should be used

Since I found FreeAndNil and the problems it solved
it's the only thing i use anymore to free objects

just using Obj.Free causes a lot of hassle like this


Unless EM is in the same object and accesses the FObj private variable, FreeAndNil(FObj) does nothing towards clearing the reference EM has to the object.

TEventManager = class....
  property Obj: TObj; read FObj; read FObj;

procedure BadProc();
var
  anObj := TObj;
  em: TEventManager;
begin
  anObj := TObj.Create();
  em := TEventManager.Create...
  em.Obj = anObj;
  FreeAndNil(anObj); /// the local anObj variable

  if Assigned(em.Obj) then  /// this returns true. *that* variable is still assigned
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
You are right, when you start having more than one reference of an object, in lists, or other kind of containers, it can be a pain to track the freeing of those objects and make sure that no other reference is remaining.
Why I do is this : For each kind of object, there must be ONE container that is responsible for its deletion, and notify every other components to nil the references.

Constructor TMyObject.Create;
begin
// other construction code ...
 RefContainer.Add(Self);
end;


Destructor TMyObject.Destroy;
begin
 RefContainer.Delete(Self);
// other freeing code ...
end;

and RefContainer.Delete method is the one that know where to look for references, and implement the notifications that will finally remove all of them. That is just a principle, not a magic solution that works in all cases. That is the price to pay for not having a garbage collector

About the trapping of errors, because as you say even if you have a complex system to reduce these problems, there can be bugs well hidden. So here is how to code the Fun function and solve your hourglass problem. That is what Geert said, but with 2 levels :
procedure TMyObject.Fun;
begin
  Screen.Cursor := crHourGlass;
  try
   BeginUpdate;
   try
    // full code, without the EndUpdate raising exception
   finally
    EndUpdate;
   end; 
  finally   
    Screen.Cursor := crDefault; // keep this one alone
  end;
end;

Open in new window

SOLUTION
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
for something like changing the cursor you would actually have to use a stack

pushing and popping the new cursor instead of changing and resetting

sample:
default
user opens form > cursor change to hand (push hand cursor on stack)
user opens query via form > cursor changes to hourglass (push hourglass cursor on stack)
query finished > pop previous cursor = hand
form closes > pop previous cusros = default
SOLUTION
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
@Geert_Gruwez

Excellent
Avatar of lavitz

ASKER

Ok, thanks