Improve company productivity with a Business Account.Sign Up

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 497
  • Last Modified:

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.
0
lavitz
Asked:
lavitz
  • 7
  • 4
  • 2
  • +2
3 Solutions
 
Geert GOracle dbaCommented:
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
 
Geert GOracle dbaCommented:
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
 
lavitzAuthor Commented:
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
Free Tool: IP Lookup

Get more info about an IP address or domain name, such as organization, abuse contacts and geolocation.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

 
cyberkiwiCommented:
> 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
 
cyberkiwiCommented:
> 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
 
Geert GOracle dbaCommented:
doesn't using if Assigned(Obj) help ?

if Assigned(fObj) then
  fObj.Fun;

0
 
cyberkiwiCommented:
> 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
 
Geert GOracle dbaCommented:
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
 
cyberkiwiCommented:
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
 
Geert GOracle dbaCommented:
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
 
epasquierCommented:
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
 
Ephraim WangoyaCommented:

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
 
Geert GOracle dbaCommented:
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
 
Geert GOracle dbaCommented:
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
 
Ephraim WangoyaCommented:
@Geert_Gruwez

Excellent
0
 
lavitzAuthor Commented:
Ok, thanks
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

Featured Post

Upgrade your Question Security!

Your question, your audience. Choose who sees your identity—and your question—with question security.

  • 7
  • 4
  • 2
  • +2
Tackle projects and never again get stuck behind a technical roadblock.
Join Now