Solved

Invoking freed object's function

Posted on 2010-09-08
16
482 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
  • 7
  • 4
  • 2
  • +2
16 Comments
 
LVL 36

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 36

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
 
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 36

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 36

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
Maximize Your Threat Intelligence Reporting

Reporting is one of the most important and least talked about aspects of a world-class threat intelligence program. Here’s how to do it right.

 
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 36

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:ewangoya
ewangoya 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 36

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 36

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:ewangoya
ID: 33651205
@Geert_Gruwez

Excellent
0
 

Author Closing Comment

by:lavitz
ID: 33767921
Ok, thanks
0

Featured Post

IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

Join & Write a Comment

A lot of questions regard threads in Delphi.   One of the more specific questions is how to show progress of the thread.   Updating a progressbar from inside a thread is a mistake. A solution to this would be to send a synchronized message to the…
The uses clause is one of those things that just tends to grow and grow. Most of the time this is in the main form, as it's from this form that all others are called. If you have a big application (including many forms), the uses clause in the in…
This video shows how to remove a single email address from the Outlook 2010 Auto Suggestion memory. NOTE: For Outlook 2016 and 2013 perform the exact same steps. Open a new email: Click the New email button in Outlook. Start typing the address: …
This video demonstrates how to create an example email signature rule for a department in a company using CodeTwo Exchange Rules. The signature will be inserted beneath users' latest emails in conversations and will be displayed in users' Sent Items…

706 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

Need Help in Real-Time?

Connect with top rated Experts

12 Experts available now in Live!

Get 1:1 Help Now