编辑:样本当然很简单,可以说明问题.但在现实生活中,它变得越来越复杂:如果创建和使用的代码远远不同(不同的单元,不同的类,不同的项目)会怎么样?如果由不同的人维护怎么办?如果一个非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);
原因是引用引用的引用计数已经为零,所以底层对象已经被破坏了.
有关编译器插入或省略的引用计数代码的几个注释:
>没有标记const的参数(如在过程Dump(参考:IInterface));)获取隐式try / finally块来执行引用计数.
>标记为const的参数(类似于过程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.
–jeroen
解决方法
原文地址:https://www.jb51.cc/delphi/102842.html
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。