我们有一个有趣的。
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
ITestInterface = interface(IInvokable)
['{4059D1CD-A342-48EE-B796-84B8B5589AED}']
function GetPort: string;
function GetRoot: string;
end;
TTestInterface = class(TInterfacedObject, ITestInterface)
private
FPort: string;
FRoot: string;
public
constructor Create(FileName: TFileName);
destructor Destroy; override;
function GetPort: string;
function GetRoot: string;
end;
{ TTestInterface }
constructor TTestInterface.Create(FileName: TFileName);
begin
FPort := '8080';
FRoot := 'top';
end;
destructor TTestInterface.Destroy;
begin
// ^ Place Breakpoint here
inherited;
end;
function TTestInterface.GetPort: string;
begin
Result := FPort;
end;
function TTestInterface.GetRoot: string;
begin
Result := FRoot;
end;
type
TTestService = class
protected
FTest : TTestInterface;
public
constructor Create;
destructor Destroy; override;
procedure Process;
end;
{ TTestService }
constructor TTestService.Create;
begin
FTest := TTestInterface.Create('');
(FTest as IInterface)._AddRef;
end;
destructor TTestService.Destroy;
begin
FTest.Free;
inherited;
end;
procedure TTestService.Process;
begin
writeln( 'Config Root: ', FTest.GetRoot );
writeln( 'Config Port: ', FTest.GetPort );
end;
var
TS : TTestService;
begin
TS := TTestService.Create;
try
TS.Process;
finally
TS.Free;
end;
end.
当此应用程序完成时,它会生成无效的指针操作。 真正奇怪的部分是在析构函数上设置一个断点,你可以看到它在第一次被调用时会产生错误,从而排除它被释放两次。这几乎就好像是从内存中转储对象而根本没有调用析构函数。
删除_AddRef
一切都按预期工作。
我们设法在Delphi 6上生成了这个。任何人都可以在任何其他版本上确认这种行为吗?
答案 0 :(得分:4)
问题是您手动释放引用计数大于零的接口对象。这里引发了例外:
procedure TInterfacedObject.BeforeDestruction;
begin
if RefCount <> 0 then {!! RefCount is still 1 - you made it that way!}
Error(reInvalidPtr);
end;
所以...你可能只需在析构函数中调用(FTest as IInterface)._Release;
代替FTest.Free
,但这感觉就像通过制作另一个错误来解决一个错误。您要么引用计数,要么不要 - 如果您这样做,那么您应该以这种方式使用对象(使用接口变量并让范围和变量生命周期管理对象生存期)。如果您不想要引用计数,请将其禁用。无论哪种方式,您都应该选择终身管理模型并以正常方式使用它。
案例1:禁用引用计数
如果您想禁用自动引用计数,并且您使用Delphi 2009或更高版本,则可以通过继承TSingletonImplementation
而不是TInterfacedObject
来执行此操作:
TTestInterface = class(TSingletonImplementation, ITestInterface)
private
FPort: string;
FRoot: string;
public
constructor Create(FileName: TFileName);
destructor Destroy; override;
function GetPort: string;
function GetRoot: string;
end;
否则,您可以通过添加所需的方法自行实现:
TTestInterface = class(TObject, ITestInterface)
private
FPort: string;
FRoot: string;
{ ** Add interface handling methods ** }
protected
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
{ ** ---------------------- ** }
public
constructor Create(FileName: TFileName);
destructor Destroy; override;
function GetPort: string;
function GetRoot: string;
end;
您实现为:
function TTestInterface.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := S_OK
else
Result := E_NOINTERFACE;
end;
function TTestInterface._AddRef: Integer;
begin
Result := -1;
end;
function TTestInterface._Release: Integer;
begin
Result := -1;
end;
案例2:正常使用界面参考
如果你绝对需要引用计数,你仍然需要访问具体的类成员,那么最简单的解决方案是严格使用接口变量,让容器类固定对象的生命周期,并在需要时转换为具体类型。让我们向班级介绍一些州:
TTestInterface = class(TInterfacedObject, ITestInterface)
private
FPort: string;
FRoot: string;
public
Foo : integer; { not an interface member...}
constructor Create(FileName: TFileName);
destructor Destroy; override;
function GetPort: string;
function GetRoot: string;
end;
您的容器类将成为:
type
TTestService = class
protected
FTest : ITestInterface;
public
constructor Create;
procedure Process;
end;
{ TTestService }
constructor TTestService.Create;
begin
FTest := TTestInterface.Create('');
end;
procedure TTestService.Process;
begin
writeln( 'Config Root: ', FTest.GetRoot );
writeln( 'Config Port: ', FTest.GetPort );
WriteLn( 'Foo : ', TTestInterface(FTest).Foo); {Cast to access class members}
end;
请注意,上面的TTestInterface(FTest)
演员表仅适用于Delphi 2010及更高版本。对于早于此版本的版本,您必须保留单独的对象引用,如@ ArnaudBouchez的答案。在任何一种情况下,重点是以正常方式使用接口引用来管理对象生存期,而不是依赖于手动黑客攻击引用计数。
答案 1 :(得分:4)
使用两个变量:一个用于类,一个用于接口。
所以你的代码变成了:
type
TTestService = class
protected
FTest: ITestInterface;
FTestInstance : TTestInterface;
public
constructor Create;
procedure Process;
end;
{ TTestService }
constructor TTestService.Create;
begin
FTestInstance := TTestInterface.Create('');
FTest := FTestInstance;
end;
procedure TTestService.Process;
begin
writeln( 'Config Root: ', FTest.GetRoot );
writeln( 'Config Port: ', FTest.GetPort );
end;
var
TS : TTestService;
begin
TS := TTestService.Create;
try
TS.Process;
finally
TS.Free;
end;
end.