Pab*_*ino 14 delphi memory-management interface object delphi-7
在我的应用程序的某些部分,我遇到的情况是我收到一个我知道是对象的接口,尽管我不知道确切的类.我必须将该对象存储在interface-type变量中.
最终,我可能会收到该类型的另一个实例,第一个必须被丢弃并替换为新实例.为此,我需要释放接口对象使用的内存(我的接口提供了一个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.
Run Code Online (Sandbox Code Playgroud)
所以问题是:我怎样才能正确释放该对象?
Del*_*ics 27
假设你有一个合理的理由这样做(并且使用TComponent很可能你会这样做 - 看到答案的结尾为什么),然后问题发生在你破坏了一个接口变量的引用之后它目前引用的对象.
对接口引用的任何更改都会生成如下代码:
intfA := intfB;
Run Code Online (Sandbox Code Playgroud)
变得(简单来说):
if Assigned(intfA) then
intfA.Release;
intfA := intfB;
if Assigned(intfA) then
intfA.AddRef;
Run Code Online (Sandbox Code Playgroud)
如果您将其与您的代码相关联,您应该会看到问题:
SomeInterface.AsObject.Free;
SomeInterface := nil;
Run Code Online (Sandbox Code Playgroud)
变为:
SomeInterface.AsObject.Free;
if Assigned(SomeInterface) then
SomeInterface.Release;
SomeInterface := nil;
if Assigned(SomeInterface) then
SomeInterface.AddRef;
Run Code Online (Sandbox Code Playgroud)
因此,您可以看到生成的对Release()的调用是通过将NIL分配给导致访问冲突的接口而产生的.
您还应该快速看到有一种简单的方法可以避免这种情况,只需将对象的释放推迟到NIL为接口引用之后:
obj := SomeInterface.AsObject;
SomeInterface := NIL;
obj.Free;
Run Code Online (Sandbox Code Playgroud)
但
这里的关键问题是为什么你明确地释放了一个接口的对象(并且可能是引用计数).
当您在显式释放对象之前更改代码以缓存对象引用和NIL接口时,您可能会发现obj.Free将导致访问冲突,因为NIL的接口引用本身可能会导致对象被释放了.
确保明确释放接口对象是安全的唯一方法是:
1)接口对象已覆盖/重新实现IUnknown并消除了引用计数生命周期管理
和
2)在代码的其他地方没有对该对象的其他接口引用.
如果这些条件中的第一个对你来说没有多大意义,那么在不希望光顾的情况下,这可能是一个很好的迹象,你不应该明确地释放该对象,因为它几乎肯定是通过引用计数来管理的.
话虽如此,由于您使用的是接口的TComponent类,只要您的TComponent类没有封装COM对象,那么TComponent就满足条件#1,所以剩下的就是确保代码的其余部分满足条件# 2.
您不应该使用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.
Run Code Online (Sandbox Code Playgroud)