在我的应用程序的某些部分,我会遇到这样的情况:我收到一个接口,我知道它是一个对象,尽管我不知道确切的类。我必须将该对象存储在一个接口类型的变量中。
最终,我可能会收到该类型的另一个实例,第一个实例必须被丢弃并替换为新的实例。为此,我需要释放接口对象使用的内存(我的接口提供了一个AsObject方法,所以我可以在它上面使用TObject方法)。我的问题是,当我想再次将"nil“赋值给这个变量时,我得到了一个访问冲突。
我写了一个小程序来重现我的情况。我把它贴在这里是为了澄清情况。
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils, Classes;
type
ISomeInterface = interface
function SomeFunction : String;
function AsObject : TObject;
end;
TSomeClass = class(TComponent, ISomeInterface)
public
called : Integer;
function SomeFunction : String;
function AsObject : TObject;
end;
var
SomeInterface : ISomeInterface;
i : Integer;
function TSomeClass.SomeFunction : String;
begin
Result := 'SomeFunction called!';
end;
function TSomeClass.AsObject : TObject;
begin
Result := Self;
end;
begin
try
SomeInterface := nil;
for i := 1 to 10 do
begin
if SomeInterface <> nil then
begin
SomeInterface.AsObject.Free;
SomeInterface := nil; // <-- Access Violation occurs here
end;
SomeInterface := TSomeClass.Create(nil);
SomeInterface.SomeFunction; // <-- if commented, Access
// Violation does not occur
end;
except on e : Exception do
WriteLn(e.Message);
end;
end.所以问题是:我怎样才能正确地释放那个对象?
发布于 2011-01-28 05:26:10
假设您有合法的理由这样做(并且使用TComponent,您很有可能这样做-原因请参阅答案末尾),那么问题的发生是因为您在销毁接口变量当前引用的对象后更改了该变量的引用。
对接口引用的任何更改都会生成如下代码:
intfA := intfB;变成(简而言之):
if Assigned(intfA) then
intfA.Release;
intfA := intfB;
if Assigned(intfA) then
intfA.AddRef;如果你把它和你的代码联系起来,你应该会看到问题:
SomeInterface.AsObject.Free;
SomeInterface := nil; 变成:
SomeInterface.AsObject.Free;
if Assigned(SomeInterface) then
SomeInterface.Release;
SomeInterface := nil;
if Assigned(SomeInterface) then
SomeInterface.AddRef;因此,您可以看到,正是由于将NIL赋值给接口而生成的Release()调用导致了访问冲突。
您还应该很快看到有一种简单的方法来避免这种情况,只需将对象的释放推迟到您将接口引用设为空之后:
obj := SomeInterface.AsObject;
SomeInterface := NIL;
obj.Free;但
这里的关键问题是,为什么要显式地释放一个接口的对象(并假设引用计数)。
当您更改代码以缓存对象引用并在显式释放对象之前将接口设为空时,您可能会发现obj.Free将导致访问冲突,因为接口引用的设为空本身可能会导致对象被释放。
确保显式释放接口对象是安全的唯一方法是:
1)接口对象覆盖/重新实现了IUnknown,并消除了引用计数生命周期管理
和
2)在代码中的其他地方没有对该对象的其他接口引用。
如果第一个条件对你没有多大意义,那么这可能是一个好兆头,表明你不应该显式释放对象,因为它几乎肯定是由引用计数管理的。
话虽如此,因为您使用的是一个接口TComponent类,所以只要您的TComponent类没有封装COM对象,那么TComponent就满足条件#1,因此剩下的就是确保其余代码满足条件#2。
发布于 2011-01-28 05:30:46
您不应该使用TComponent作为接口对象的基类,而应该使用TInterfacedObject。TInerfacedObject已经实现了必要的函数来处理Delphi语言中接口的生命周期管理。你也不应该把访问你的接口作为接口和对象。以下是对代码的修改,它工作得很好,没有内存泄漏。
program Project2;
{$APPTYPE CONSOLE}
uses
SysUtils, Classes;
type
ISomeInterface = interface
function SomeFunction: string;
end;
TSomeClass = class(TInterfacedObject, ISomeInterface)
public
called: Integer;
function SomeFunction: string;
end;
var
SomeInterface: ISomeInterface;
i: Integer;
function TSomeClass.SomeFunction: string;
begin
Result := 'SomeFunction called!';
end;
begin
try
SomeInterface := nil;
for i := 1 to 10 do
begin
if SomeInterface <> nil then
begin
SomeInterface := nil;
end;
SomeInterface := TSomeClass.Create;
SomeInterface.SomeFunction;
end;
except
on e: Exception do
WriteLn(e.message);
end;
end.发布于 2011-01-28 04:51:31
当你有一个接口变量时,比如你的ISomeInterface变量,你不需要释放它,因为它是引用计数的,当它退出作用域时会释放它自己。
阅读罗布·肯尼迪对这个问题的回答:Delphi7, passing object's interface - causes Invalid Pointer Operation when freeing the object
来自http://delphi.about.com/od/beginners/l/aa113004a.htm
一旦接口超出范围,Delphi实际上会自动为您释放接口!在过程或函数中声明的接口在过程结束时自然会超出作用域。当对象被释放或程序结束时,在类中声明的接口或全局声明的接口自然会超出范围。
如果有疑问,请尝试使用FastMM内存管理器并调优内存泄漏检测,以查看对象是否泄漏。
https://stackoverflow.com/questions/4821158
复制相似问题