【发布时间】:2011-05-29 09:13:04
【问题描述】:
在将对象的新实例传递给具有对象类实现的接口的 const 接口参数的方法时,编译器是否应该提示/警告?
编辑:示例当然很容易说明问题。但在现实生活中它变得更加复杂:如果创建和使用是在相距很远的代码中(不同的单元、不同的类、不同的项目)怎么办?如果由不同的人维护呢?如果一个非 const 参数变成了一个 const 参数,并且不是所有调用代码都可以检查(因为更改代码的人无法访问所有调用代码)怎么办?
下面的代码崩溃了,很难找到原因。
首先是日志:
1.Run begin
1.RunLeakCrash
2.RunLeakCrash begin
NewInstance 1
AfterConstruction 0
3.LeakCrash begin
_AddRef 1
4.Dump begin
4.Dump Reference=10394576
4.Dump end
_Release 0
_Release Destroy
BeforeDestruction 0
3.LeakCrash Reference got destroyed if it had a RefCount of 1 upon entry, so now it can be unsafe to access it
_AddRef 1
4.Dump begin
4.Dump Reference=10394576
4.Dump end
_Release 0
_Release Destroy
BeforeDestruction 0
3.LeakCrash end with exception
1.Run end
EInvalidPointer: Invalid pointer operation
然后是过早释放实现接口的对象实例的代码:
//{$define all}
program InterfaceConstParmetersAndPrematureFreeingProject;
{$APPTYPE CONSOLE}
uses
SysUtils,
Windows,
MyInterfacedObjectUnit in '..\src\MyInterfacedObjectUnit.pas';
procedure Dump(Reference: IInterface);
begin
Writeln(' 4.Dump begin');
Writeln(' 4.Dump Reference=', Integer(PChar(Reference)));
Writeln(' 4.Dump end');
end;
procedure LeakCrash(const Reference: IInterface);
begin
Writeln(' 3.LeakCrash begin');
try
Dump(Reference); // now we leak because the caller does not keep a reference to us
Writeln(' 3.LeakCrash Reference got destroyed if it had a RefCount of 1 upon entry, so now it can be unsafe to access it');
Dump(Reference); // we might crash here
except
begin
Writeln(' 3.LeakCrash end with exception');
raise;
end;
end;
Writeln(' 3.LeakCrash end');
end;
procedure RunLeakCrash;
begin
Writeln(' 2.RunLeakCrash begin');
LeakCrash(TMyInterfacedObject.Create());
Writeln(' 2.RunLeakCrash end');
end;
procedure Run();
begin
try
Writeln('1.Run begin');
Writeln('');
Writeln('1.RunLeakCrash');
RunLeakCrash();
finally
Writeln('');
Writeln('1.Run end');
end;
end;
begin
try
Run();
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
EInvalidPointer 将在对Dump(Reference); 的第二次调用中显现。
原因是暴露Reference的底层对象的引用计数已经为零,所以底层对象已经被销毁了。
关于编译器插入或省略的引用计数代码的几点说明:
- 未用
const标记的参数(如procedure Dump(Reference: IInterface);中的参数)获取隐式try/finally 块来执行引用计数。 - 标有
const的参数(如procedure LeakCrash(const Reference: IInterface);)没有得到任何引用计数代码 - 传递对象实例创建的结果(如
LeakCrash(TMyInterfacedObject.Create());)不会生成任何引用计数代码
上述所有编译器行为都非常合乎逻辑,但它们结合起来可能会导致 EInvalidPointer。
EInvalidPointer 仅以非常狭窄的使用模式表现出来。
该模式很容易被编译器识别,但当您陷入其中时很难调试或找到原因。
解决方法非常简单:将TMyInterfacedObject.Create() 的结果缓存在一个中间变量中,然后将其传递给LeakCrash()。
编译器应该提示或警告您这种使用模式吗?
最后是我用来跟踪所有 _AddRef/_Release/etcetera 调用的代码:
unit MyInterfacedObjectUnit;
interface
type
// Adpoted copy of TInterfacedObject for debugging
TMyInterfacedObject = class(TObject, IInterface)
protected
FRefCount: Integer;
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
public
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
class function NewInstance: TObject; override;
property RefCount: Integer read FRefCount;
end;
implementation
uses
Windows;
procedure TMyInterfacedObject.AfterConstruction;
begin
InterlockedDecrement(FRefCount);
Writeln(' AfterConstruction ', FRefCount);
end;
procedure TMyInterfacedObject.BeforeDestruction;
begin
Writeln(' BeforeDestruction ', FRefCount);
if RefCount <> 0 then
System.Error(reInvalidPtr);
end;
class function TMyInterfacedObject.NewInstance: TObject;
begin
Result := inherited NewInstance;
TMyInterfacedObject(Result).FRefCount := 1;
Writeln(' NewInstance ', TMyInterfacedObject(Result).FRefCount);
end;
function TMyInterfacedObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
Writeln(' QueryInterface ', FRefCount);
if GetInterface(IID, Obj) then
Result := 0
else
Result := E_NOINTERFACE;
end;
function TMyInterfacedObject._AddRef: Integer;
begin
Result := InterlockedIncrement(FRefCount);
Writeln(' _AddRef ', FRefCount);
end;
function TMyInterfacedObject._Release: Integer;
begin
Result := InterlockedDecrement(FRefCount);
Writeln(' _Release ', FRefCount);
if Result = 0 then
begin
Writeln(' _Release Destroy');
Destroy;
end;
end;
end.
--杰罗恩
【问题讨论】:
标签: delphi interface parameters constants