我有一些问题,我试图确定对象的引用是否有效。但它似乎回归了奇怪的结果。
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后代的列表,然后稍后检查它们是否已经消失(或者不可见)。不幸的是,它是一个基于插件的系统,所以我可以更改所有这些表单,发布“我已完成消息。”
这样的代码是否可以安全使用(假设我实际上正在检查访问冲突)?有没有人有任何想法发生了什么。
由于
答案 0 :(得分:6)
问题是,在某种可能性下,访问的内存仍由Delphi内存管理器保留。在这种情况下,Windows不会生成任何类型的访问冲突,因为该内存属于您!
一种可能性是切换到另一个可以检测释放对象使用的Delphi内存管理器。例如,FastMM4有几个“内存卫生”检查,这对于调试非常有用,但即使这样,你也不会立即发现所有这些错误。
答案 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。