views:

361

answers:

8

I have an issues where I am trying to determine if a reference to an object is valid. But it seems to be returning strange results.

procedure TForm1.Button1Click(Sender: TObject);
  var form1 : TForm;
      ref2 : TControl;
begin
  form1 := TForm.Create(nil);
  form1.Name := 'CustomForm';
  form1.Parent := self; //Main Form

  form1.Show;

  ref2 := form1;
  showmessage(ref2.ClassName+' - '+ref2.Name+' - '+BoolToStr(ref2.visible,true));
  freeandnil(form1);
  showmessage(ref2.ClassName+' - '+ref2.Name+' - '+BoolToStr(ref2.visible,true));
end;

The first showmessage returns - "TForm - CustomForm - True" (Just like I would expect it to).

The second showmessage return - "TForm - - False". I was actually hoping for some kind of access violation that I could then trap and know that the reference isn't valid.

In my application I need to compile a list of random TForm descendants as they are created and then check later if they have gone away (or are not visible). Unfortunately it is a plugin based system so I can go change all of these Forms to post a "I'm done Message."

Would code like this be safe to use (assuming I actually am checking for access violations)? Does anybody have any ideas what is happening.

Thanks

+5  A: 

The problem is that with a certain likelyhood the memory accessed is still reserved by the Delphi memory manager. In that case Windows does not generate any kind of access violation, because that memory belongs to you!

One possibility is to switch to a different Delphi memory manager which can detect the use of freed objects. FastMM4, for example, has several "memory hygiene" checks, which are very useful for debugging, but even then you won't catch all of these errors immediately.

You can download FastMM4 from SourceForge.

DR
A high, but not 100% sure likelyhood. In general the solution is to have some plan or system for who is responsible for freeing who, and not share references willy nilly.
Marco van de Voort
+1 for mentioning FastMM4
Jeroen Pluimers
I decided to implement in a different way where I don't need figure out if the references are valid. The above code actually worked, but I don't want to rely on a memory hack/bug/inconsistency that might change later. Thanks
tmjac2
I'm curious if you tried Deltic's suggestion, just to see if you DO get notified. It ought to work, but if you are happy, that's fine too.
Warren P
+1  A: 

After

freeandnil(form1);

the Delphi memory manager just marks the memory allocated by form1 as free, but all form1 data is still there, and can be accessed via ref2 until the memory manager reuse the freed memory for some other object(s).

You can't check that way if ref2 references a valid object or not. Code like this can't be safe, it is actually a bug. If you want to obtain a 100% access violation modify the code as follows (here ref2^ = nil if form1 is freed):

procedure TForm1.Button1Click(Sender: TObject);
  var form1 : TForm;
      ref2 : ^TControl;
begin
  form1 := TForm.Create(nil);
  form1.Name := 'CustomForm';
  form1.Parent := self; //Main Form

  form1.Show;

  ref2 := @form1;
  showmessage(ref2^.ClassName+' - '+ref2^.Name+' - '+BoolToStr(ref2^.visible,true));
  freeandnil(form1);
  showmessage(ref2^.ClassName+' - '+ref2^.Name+' - '+BoolToStr(ref2^.visible,true));
end;
Serg
+1  A: 

In a similar case I am using a singleton object that keeps a list of all the created forms. Each form has a field with a reference to this Object.

TMyForm = class(TForm)
private
  //*** This is the reference to the singleton...
  FFormHandler: TFormHandler;
public
  ...
  //*** you might want to publish it as a property:
  property FormHandler: TFormHandler read FFormHandler write FFormHandler;
end;

You can set this reference e.g. when calling the constructor:

TMyForm.Create(aFormHandler: TFormHandler; aOwner: TComponent)
begin
  FFormHandler := aFormHandler;
  inherited Create(aOwner);
end;

(Or you could set the field from outside directly after creating the form if you don't want to change the parameters of the constructor).

When the form ist destroyed it notifies the handler and tells him to remove the form from the list - something like that:

TMyForm.Destroy(Sender: TObject);
begin
  FFormHandler.RemoveFromFormList(Self);
  inherited;
end;

(The details of the track-keeping are not included in the expample - e.g. a method "AddToFomList" or something alike would be needed)

Andreas Wieland
Love the idea, but doesn't really answer the question very well. I will end up doing something similar to this, only replacing the OnDestroy event with an different event (and then calling the original) so I can keep a list of the active Forms.
tmjac2
A: 

If you cannot test in another manner, you can use this as a last resort±

function IsValidClass( Cls: TClass ): Boolean;
var
    i: Integer;
begin
    for i := 0 to 99 do begin
        Result := ( Cls = TObject ); // note that other modules may have a different root TObject!
        if Result then Exit;
        if IsBadReadPtr( Cls, sizeof( Pointer ) ) then Break;
        if IsBadReadPtr( Pointer( Integer( Cls ) + vmtParent ), sizeof( Pointer ) ) then Break;
        Cls := Cls.ClassParent;
    end;
    Result := False;
end;

function IsValidObject( Obj: TObject ): Boolean;
begin
    Result := not IsBadReadPtr( Obj, sizeof( Pointer ) ) and IsValidClass( Obj.ClassType ) and not IsBadReadPtr( Obj, Obj.InstanceSize );
end;

IsBadReadPtr comes from Windows.

Ritsaert Hornstra
That only determines whether the memory contents look like a valid object. A newly freed object will still look valid even though it isn't. If the freed memory gets re-allocated for a new object, dangling references to the old object will suddenly appear valid again, even though they don't point to what you think they do. Besides that, there's plenty of information around describing the reasons *not* to use IsBadReadPtr. If your program doesn't know whether its own pointers are valid, you've already lost the game.
Rob Kennedy
@Rob: Yes you're right. I forgot to mention that I use this in combination with FastMM in debug mode. I use this code in one of our exception handlers where I try to rescue as much information as possi ble to the log file just before terminating the application. I agree with you that this is for debugging and not for regular usage.
Ritsaert Hornstra
A: 

There is no reliable way to do what you are trying to do using the technique you're attempting. Forms that have "gone away" may have their memory reused, possibly even for a new form.

At best, you could work some mechanism whereby you cache the results of iterating Screen.Forms, but you can still fall foul of accidental duplicates, where a form gets destroyed and another gets reallocated and gets the same object address. That scenario is less likely than the memory being reused for some other object, however.

Barry Kelly
A: 

Given that you cannot modify the code that is out there in the plugins, all the good solutions about how to write safer code are not applicable to your case.

  • You have 1 way of doing it by checking if an Object reference is still what it's supposed to be by looking up the VMT. This idea was first published by Ray Lischner (who advocated for FreeAndNil for that very reason) and later by Hallvard Vassbotn: see this SO answer.

  • Another, better but introducing major slowdown, is to use FastMM4 in FullDebugmode to have it to replace all the freed objects by a TFreeObject instance instead of simply releasing the memory to the available pool.

Note that both methods do not prevent a false positive if another instance of the same class happens to be created at the same memory address. You get a valid object of the right type, just not the original one. (Unlikely in your case, but possible)

François
+4  A: 

Any TComponent (e.g. a TForm descendant) can register for notifications when other components are destroyed.

In your form, call FreeNotification(form) for each form that you wish to be notified of the destruction of. Then on the same form override the Notification() method. When any form (or other component) for which you have called FreeNotification() is destroyed, your Notification() method will be called with a Component parameter referencing the form and an Operation of opRemove.

If I've understood what it is you are trying to achieve, I think this should be enough information to devise an approach to do what you need.

Deltics
A: 

There is one very interesting memory manager. It is called SafeMM: http://blogs.embarcadero.com/medington/2009/10/16/24839 But still it is for debugging only.

Torbins