当将对象实例直接传递为const接口参数时,编译器是否提示/警告? [英] Should the compiler hint/warn when passing object instances directly as const interface parameters?

查看:139
本文介绍了当将对象实例直接传递为const接口参数时,编译器是否提示/警告?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

传递对象的新实例时,编译器会提示/警告一个方法,该方法的对象的类实现的接口有一个const接口参数?



编辑当然,示例很简单,可以说明问题。但是在现实生活中,它变得越来越复杂:如果创建和使用的代码远远不同(不同的单元,不同的类,不同的项目)会怎么样?如果由不同的人维护怎么办?如果一个非const参数变成一个const参数,而不是所有调用代码都可以被检查(因为改变代码的人不能访问所有调用代码)?



下面的代码崩溃,很难找到原因。



首先记录日志:

  1.Run开始

1.RunLeakCrash
2.RunLeakCrash开始
NewInstance 1
AfterConstruction 0
3.LeakCrash开始
_AddRef 1
4.Dump开始
4.Dump参考= 10394576
4.转储结束
_Release 0
_Release销毁
BeforeDestruction 0
3.LeakCrash引用被破坏,如果它有一个RefCount为1进入,所以现在可以不安全访问它
_AddRef 1
4.Dump开始
4.Dump参考= 10394576
4.转储结束
_Release 0
_Release Destroy
BeforeDestruction 0
3.LeakCrash结束与异常

1.结束
EI nvalidPointer:无效的指针操作

然后,过早释放实现接口的对象实例的代码:

  // {$ define all} 

程序InterfaceConstParmetersAndPrematureFreeingProject;

{$ APPTYPE CONSOLE}

使用
SysUtils,
Windows,
MyInterfacedObjectUnit在'..\src\MyInterfacedObjectUnit中。帕斯

程序转储(参考:IInterface);
begin
Writeln('4.Dump begin');
Writeln('4.Dump Reference =',Integer(PChar(Reference)));
Writeln('4.Dump end');
结束

程序LeakCrash(const参考:IInterface);
begin
Writeln('3.LeakCrash begin');
try
转储(参考); //现在我们泄漏,因为调用者没有保留对我们的引用
Writeln('3.LeakCrash Reference被破坏,如果它的RefCount为1,进入时,所以现在可以不安全访问它');
转储(参考); //我们可能在这里崩溃
除了
begin
Writeln('3.LeakCrash end with exception');
加注
结束
结束
Writeln('3.LeakCrash end');
结束

程序RunLeakCrash;
begin
Writeln('2.RunLeakCrash begin');
LeakCrash(TMyInterfacedObject.Create());
Writeln('2.RunLeakCrash end');
结束

程序Run();
begin
try
Writeln('1.Run begin');

Writeln('');
Writeln('1.RunLeakCrash');
RunLeakCrash();

finally
Writeln('');
Writeln('1.Run end');
结束
结束

begin
try
Run();
除了
在E:Exception do
Writeln(E.ClassName,':',E.Message);
结束
Readln;
结束。

EInvalidPointer将在第二次调用中显示转储(参考);
原因是引用引用的引用计数已经为零,因此底层对象已经被破坏。



有关引用计数的几个注释编码插入或省略的代码:




  • 没有标记为 const的参数程序转储(参考:IInterface); )获取隐式try / finally块来执行引用计数。

  • 标记为 const (像程序中的程序LeakCrash(const参考:IInterface); )没有任何引用计数代码
  • $传递对象实例创建的结果(如 LeakCrash(TMyInterfacedObject.Create()); )不生成任何引用计数代码


所有上述编译器行为都是非常合乎逻辑的,但它们可以导致一个EInvalidPointer。

EInvalidPointer仅显示自身在非常狭窄的使用p attern。

该模式很容易被编译器识别,但很难调试或找到原因,当你陷入它。

解决方法很简单:缓存结果的$ code> TMyInterfacedObject.Create()在一个中间变量,然后传递给 LeakCrash()



如果编译器提示或警告您这个使用模式?



最后我用来跟踪所有的代码_AddRef / _Release / etcetera调用:

  unit MyInterfacedObjectUnit; 

接口

类型
//调用TInterfacedObject的Adpoted副本
TMyInterfacedObject =类(TObject,IInterface)
protected
FRefCount:整数;
函数QueryInterface(const IID:TGUID; out Obj):HResult;标准
function _AddRef:Integer;标准
函数_Release:整数;标准
public
procedure AfterConstruction;覆盖
程序BeforeDestruction;覆盖
类函数NewInstance:TObject;覆盖
属性RefCount:整数读取FRefCount;
结束

实现

使用
Windows;

procedure TMyInterfacedObject.AfterConstruction;
begin
InterlockedDecrement(FRefCount);
Writeln('AfterConstruction',FRefCount);
结束

procedure TMyInterfacedObject.BeforeDestruction;
begin
Writeln('BeforeDestruction',FRefCount);
如果RefCount<> 0 then
System.Error(reInvalidPtr);
结束

类函数TMyInterfacedObject.NewInstance:TObject;
begin
结果:= inherited NewInstance;
TMyInterfacedObject(Result).FRefCount:= 1;
Writeln('NewInstance',TMyInterfacedObject(Result).FRefCount);
结束

函数TMyInterfacedObject.QueryInterface(const IID:TGUID; out Obj):HResult;
begin
Writeln('QueryInterface',FRefCount);
if GetInterface(IID,Obj)then
结果:= 0
else
结果:= E_NOINTERFACE;
结束

function TMyInterfacedObject._AddRef:Integer;
begin
结果:= InterlockedIncrement(FRefCount);
Writeln('_AddRef',FRefCount);
结束

function TMyInterfacedObject._Release:Integer;
begin
结果:= InterlockedDecrement(FRefCount);
Writeln('_Release',FRefCount);
如果Result = 0,那么
begin
Writeln('_Release Destroy');
毁灭
结束
结束

结束。

- jeroen

解决方案

这是一个错误。在RunLeakCrash中从实例到接口引用的转换应该是一个临时变量,在RunLeakCrash的持续时间内保持活动。


Should the compiler hint/warn when passing a new instance of an object to a method having a const interface parameter of an interface that the object's class implements?

Edit: The sample of course is simple to illustrate the issue. But in real life it gets way more complex: What if the creation and usage is in code that is far apart (different units, different classes, different projects)? What if it is maintained by different people? What if a non-const parameter becomes a const one, and not all calling code can be checked (because the person changing the code does not have access to all calling code)?

Code like below crashes, and it is very hard to find the cause.

First the log:

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

Then the code that prematurely releases the object instance implementing an interface:

//{$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.

The EInvalidPointer will manifest itself inside the second call to Dump(Reference);. The reason is that the reference count of the underlying object exposing Reference is already zero, so the underlying object got already destroyed.

A few notes on reference counting code inserted or omitted by the compiler:

  • parameters not marked with const (like in procedure Dump(Reference: IInterface);) get implicit try/finally blocks to perform reference counting.
  • parameters marked with const (like in procedure LeakCrash(const Reference: IInterface);) do not get any reference counting code
  • passing the result of an object instance creation (like LeakCrash(TMyInterfacedObject.Create());) does not generate any reference counting code

Alone all of the above compiler behaviours are very logical, but combined they can cause an EInvalidPointer.
The EInvalidPointer manifests itself only in a very narrow usage pattern.
The pattern is easy to recognize by the compiler, but very hard to debug or find the cause when you trapped into it.
The workaround is pretty simple: cache the result of TMyInterfacedObject.Create() in an intermediate variable, then pass it on to LeakCrash().

Should the compiler hint or warn you about this usage pattern?

Finally the code I used to trace all the _AddRef/_Release/etcetera calls:

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

解决方案

It's a bug. The conversion from instance to interface reference in RunLeakCrash should be to a temporary variable, keeping it alive for the duration of RunLeakCrash.

这篇关于当将对象实例直接传递为const接口参数时,编译器是否提示/警告?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆