Solved

Invoking freed object's function

Posted on 2010-09-08
16
492 Views
Last Modified: 2012-06-27
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.
0
Comment
Question by:lavitz
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 7
  • 4
  • 2
  • +2
16 Comments
 
LVL 37

Expert Comment

by:Geert Gruwez
ID: 33625909
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;
0
 
LVL 37

Expert Comment

by:Geert Gruwez
ID: 33625933
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
0
 

Author Comment

by:lavitz
ID: 33625988
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.




0
Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 58

Expert Comment

by:cyberkiwi
ID: 33625994
> 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.
0
 
LVL 58

Expert Comment

by:cyberkiwi
ID: 33626023
> 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.
0
 
LVL 37

Expert Comment

by:Geert Gruwez
ID: 33626048
doesn't using if Assigned(Obj) help ?

if Assigned(fObj) then
  fObj.Fun;

0
 
LVL 58

Expert Comment

by:cyberkiwi
ID: 33626076
> 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.
0
 
LVL 37

Expert Comment

by:Geert Gruwez
ID: 33626276
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


0
 
LVL 58

Expert Comment

by:cyberkiwi
ID: 33626348
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
0
 
LVL 37

Accepted Solution

by:
Geert Gruwez earned 334 total points
ID: 33626568
if this is the problem,
you could solve this with the FreeNotification procedure

procedure TComponent.FreeNotification(AComponent: TComponent);

fObj.FreeNotification(em);

in em you then get notified when fObj is freed

you need to override the protected procedure Notification to get the message

procedure TEventManager.Notification(AComponent: TComponent; Operation: TOperation); // override;
begin
  if Operation = opRemove then
    anObj := nil;
end;

the same thing you need to do when writing a component which can have a subcomponent
(like TImageList of TToolBar)

0
 
LVL 25

Expert Comment

by:epasquier
ID: 33627602
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

0
 
LVL 32

Assisted Solution

by:Ephraim Wangoya
Ephraim Wangoya earned 166 total points
ID: 33630947

I wrote an article on an auto free string list. The same can also be applied to create a cursor object which when created will set your screen cursor to hour glass and when it gets out of scope, reverts the cursor to the default cursor. This way you don't have to worry about a finally block that sets the cursor back to default. The article is
http://www.experts-exchange.com/Programming/Languages/Pascal/Delphi/A_3659-Creating-an-auto-free-string-list.html

Example for the cursor would be something like
var
  BusyCursor: IBusyCursor;
begin
  BusyCursor := TBusyCursor.Create;

  BeginUpdate;
   try
    // full code, without the EndUpdate raising exception
   finally
    EndUpdate;
   end;
end;

If you need sample code for that, let me know
0
 
LVL 37

Expert Comment

by:Geert Gruwez
ID: 33634339
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
0
 
LVL 37

Assisted Solution

by:Geert Gruwez
Geert Gruwez earned 334 total points
ID: 33634895
This unit is based on ewangoya's idea (auto freeing a object) :

and my idea for push/pop cursor

uses uCursors;

procedure TfrmTestEE.Button1Click(Sender: TObject);
var a, b: ISCursor;
begin
  A := TSCursor.Create(crHSplit);
  Sleep(5000);
  B := TSCursor.Create(crSizeNESW);
  Sleep(6000);
end;

procedure TfrmTestEE.Button2Click(Sender: TObject);
begin
  ShowMessage(Format('Cursors in stack: %d', [CursorStackCount]));
end;

end.
unit uCursors;

interface

uses Classes, Controls, SysUtils;

type
  ISCursor = interface
    ['{CD0C1293-C066-4964-B513-F5B79DE9D9C9}']
  end;

  TSCursor = class(TInterfacedObject, ISCursor)
  public
    constructor Create(aCursor: TCursor); virtual;
    destructor Destroy; override;
  end;

function CursorStackCount: Integer;

implementation

uses Contnrs, Forms;

type
  TCursorObj = class(TObject)
  private
    fCursor: TCursor;
  end;

var
  mCursorStack: TObjectStack;

function Cursors: TObjectStack;
begin
  if not Assigned(mCursorStack) then
    mCursorStack := TObjectStack.Create;
  Result := mCursorStack;
end;

function CursorStackCount: Integer;
begin
  Result := Cursors.Count;
end;

procedure FreeCursors;
begin
  FreeAndNil(mCursorStack);
end;

{ TSCursor }

constructor TSCursor.Create(aCursor: TCursor);
var OldCursor: TCursorObj;
begin
  inherited Create;
  OldCursor := TCursorObj.Create;
  OldCursor.fCursor := Screen.Cursor;
  Cursors.Push(OldCursor);
  Screen.Cursor := aCursor;
end;

destructor TSCursor.Destroy;
var OldCursor: TCursorObj;
begin
  if Cursors.Count > 0 then
  begin
    OldCursor := TCursorObj(Cursors.Pop);
    Screen.Cursor := OldCursor.fCursor;
    OldCursor.Free;
  end else
    Screen.Cursor := crDefault;
  inherited Destroy;
end;

initialization
  //
finalization
  FreeCursors;
end.

Open in new window

0
 
LVL 32

Expert Comment

by:Ephraim Wangoya
ID: 33651205
@Geert_Gruwez

Excellent
0
 

Author Closing Comment

by:lavitz
ID: 33767921
Ok, thanks
0

Featured Post

Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Introduction I have seen many questions in this Delphi topic area where queries in threads are needed or suggested. I know bumped into a similar need. This article will address some of the concepts when dealing with a multithreaded delphi database…
In my programming career I have only very rarely run into situations where operator overloading would be of any use in my work.  Normally those situations involved math with either overly large numbers (hundreds of thousands of digits or accuracy re…
Monitoring a network: why having a policy is the best policy? Michael Kulchisky, MCSE, MCSA, MCP, VTSP, VSP, CCSP outlines the enormous benefits of having a policy-based approach when monitoring medium and large networks. Software utilized in this v…
This is my first video review of Microsoft Bookings, I will be doing a part two with a bit more information, but wanted to get this out to you folks.

726 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question