如何检查对象引用是否仍然有效?

时间:2010-01-28 15:30:46

标签: delphi memory-management delphi-2009

我有一些问题,我试图确定对象的引用是否有效。但它似乎回归了奇怪的结果。

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;

第一个showmessage返回 - “TForm - CustomForm - True”(就像我期望的那样)。

第二个showmessage回归 - “TForm - - False”。我实际上希望某种访问违规,然后我可以陷阱并知道该引用无效。

在我的应用程序中,我需要在创建它们时编译随机TForm后代的列表,然后稍后检查它们是否已经消失(或者不可见)。不幸的是,它是一个基于插件的系统,所以我可以更改所有这些表单,发布“我已完成消息。”

这样的代码是否可以安全使用(假设我实际上正在检查访问冲突)?有没有人有任何想法发生了什么。

由于

9 个答案:

答案 0 :(得分:6)

问题是,在某种可能性下,访问的内存仍由Delphi内存管理器保留。在这种情况下,Windows不会生成任何类型的访问冲突,因为该内存属于您!

一种可能性是切换到另一个可以检测释放对象使用的Delphi内存管理器。例如,FastMM4有几个“内存卫生”检查,这对于调试非常有用,但即使这样,你也不会立即发现所有这些错误。

你可以download FastMM4 from SourceForge

答案 1 :(得分:6)

任何 TComponent (例如 TForm 后代)都可以在其他组件被销毁时注册通知。

在您的表单中,为您希望收到销毁通知的每个表单调用 FreeNotification(表单。然后在同一表单上覆盖 Notification()方法。当您销毁了 FreeNotification()的任何表单(或其他组件)时,将使用组件 Notification()方法>引用表单的参数和 opRemove 操作

如果我已经理解了你想要实现的目标,我认为这应该是足够的信息来设计出你所需要的方法。

答案 2 :(得分:2)

freeandnil(form1);

Delphi内存管理器只是将form1分配的内存标记为空闲,但所有form1数据仍然存在,并且可以通过ref2访问,直到内存管理器为其他某些对象重用释放的内存。

如果ref2引用了有效对象,则无法检查该方式。像这样的代码不安全,它实际上是一个bug。 如果你想获得100%的访问冲突,请修改代码如下(如果释放form1,这里ref2 ^ = nil):

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;

答案 3 :(得分:1)

在类似的情况下,我使用的是一个单独的对象,它保存了所有已创建表单的列表。 每个表单都有一个字段,其中包含对此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;

您可以设置此引用,例如在调用构造函数时:

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

(或者,如果您不想更改构造函数的参数,可以在创建表单后直接从外部设置字段。)

当表单被破坏时,它通知处理程序并告诉他从列表中删除表单 - 类似于:

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

(跟踪的详细信息不包括在示例中 - 例如方法“AddToFomList”或类似的东西)

答案 4 :(得分:1)

使用您正在尝试的技术,没有可靠的方法来做您正在尝试做的事情。已经“消失”的表单可能会重复使用它们的记忆,甚至可能是一种新形式。

充其量,你可以使用一些机制来缓存迭代Screen.Forms的结果,但是你仍然可以忽略意外重复,其中一个表被破坏而另一个被重新分配并获得相同的对象地址。但是,这种情况不太可能比其他对象重用内存。

答案 5 :(得分:1)

有一个非常有趣的内存管理器。它被称为SafeMM:http://blogs.embarcadero.com/medington/2009/10/16/24839但它仍然只用于调试。

答案 6 :(得分:0)

鉴于您无法修改插件中的代码,所有关于如何编写更安全代码的好解决方案都不适用于您的情况。

  • 您有1 way of doing it个 检查对象引用是否是 它应该是什么样的 查找VMT。这个想法是 由Ray Lischner首先出版(由于这个原因,他主张FreeAndNil)和 后来Hallvard Vassbotn:见 this SO answer

  • 另一个更好但引入主要减速的方法是在FullDebugmode中使用FastMM4让它用TFreeObject实例替换所有释放的对象,而不是简单地将内存释放到可用池。

注意如果同一个类的另一个实例碰巧在同一个内存地址创建,则两种方法都不会阻止误报。您将获得正确类型的有效对象,而不是原始对象。 (在您的情况下不太可能,但可能)

答案 7 :(得分:0)

就像与NIL比较一样简单:

    // object declaration

Type object;

object = new Type();

...


// here you want to be sure of the existance of the object:

if (object <> nil )

  object.free;

答案 8 :(得分:-1)

如果您无法以其他方式进行测试,可以将此作为最后的手段使用±

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来自Windows。