从内存中转储接口对象
Interfaced object being dumped from memory
我们有一个有趣的。
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 上制作了这个。任何人都可以在任何其他版本上确认此行为吗?
问题是您手动释放了一个引用计数大于零的接口对象。此处引发异常:
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;
情况二:正常使用接口引用
如果你绝对需要引用计数并且你仍然需要访问具体的 class 成员那么最简单的解决方案是严格使用接口变量,让你的容器 class 固定对象生命周期,并且需要时转换为具体类型。让我们向 class 介绍一些状态:
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;
你的容器 class 然后变成:
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 的回答。在任何一种情况下,关键是以正常方式使用接口引用来管理对象生命周期,而不是依赖于手动修改引用计数。
使用两个变量:一个用于 class,一个用于接口。
- 使用接口变量来管理实例生命周期。不要调用free,而是将接口变量设置为nil(或超出范围)让实例运行.
- 如果需要,使用 class 变量直接访问实例 - 但不应该这样,或者至少让 class 只能从 protected/private 所有者成员 class.
因此您的代码变为:
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.
我们有一个有趣的。
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 上制作了这个。任何人都可以在任何其他版本上确认此行为吗?
问题是您手动释放了一个引用计数大于零的接口对象。此处引发异常:
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;
情况二:正常使用接口引用
如果你绝对需要引用计数并且你仍然需要访问具体的 class 成员那么最简单的解决方案是严格使用接口变量,让你的容器 class 固定对象生命周期,并且需要时转换为具体类型。让我们向 class 介绍一些状态:
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;
你的容器 class 然后变成:
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 的回答。在任何一种情况下,关键是以正常方式使用接口引用来管理对象生命周期,而不是依赖于手动修改引用计数。
使用两个变量:一个用于 class,一个用于接口。
- 使用接口变量来管理实例生命周期。不要调用free,而是将接口变量设置为nil(或超出范围)让实例运行.
- 如果需要,使用 class 变量直接访问实例 - 但不应该这样,或者至少让 class 只能从 protected/private 所有者成员 class.
因此您的代码变为:
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.