我不应该将接口作为const传递吗?

时间:2015-06-24 13:41:26

标签: delphi interface const

我最近遇到(再次)the Delphi compiler code-gen bug when passing an interface as const泄露了参考资料。

如果声明您的方法将接口变量传递为const,例如:

,则会发生这种情况
procedure Frob(const Grob: IGrobber);

,修复方法是删除const

procedure Frob(Grob: IGrobber);

我了解const(以及varout)允许您通过引用传递。在结构的情况下,这保存了参数副本;让你只需将指针传递给项目。

如果是Object / Pointer / Interface,则无需通过引用传递,因为 是引用;它已经适合注册。

为了永远不再遇到这个问题,我进行了一次十字军东征。我搜索了所有源代码树:

const [A-Za-z]+\: I[A-Z]

我删除了大约150个实例,我将界面作为 const 传递。

但有一些我无法改变。 TWebBrowser回调事件声明为:

OnDocumentComplete(Sender: TObject; const pDisp: IDispatch; var URL: OleVariant);
                                    \___/
                                      |
                                      ?

我走得太远了吗?我做了一件坏事吗?

修改:或者,用较少的“基于意见”样式问题对其进行短语:是否有任何严重的缺点>将接口作为const传递?

奖金:当Delphi没有(总是)增加接口引用计数时,他们违反了The Rules of COM

  

参考计数规则

     必须为接口指针的每个新副本调用

规则1: AddRef ,并为每次销毁接口指针调用 Release ,除非后续规则明确允许,否则。

     

规则2 :关于接口指针的两个或多个副本的生命周期的开始和结束关系的一段代码的特殊知识可以允许 AddRef / Release 对将被省略。

因此虽然它可能是编译器可以利用的优化,但它必须正确地执行以便不违反规则。

1 个答案:

答案 0 :(得分:16)

  

如果声明您的方法将接口变量作为const传递,例如:

,则会发生这种情况
procedure Frob(const Grob: IGrobber);

那不太对劲。为了存在泄漏,您需要在代码中没有任何内容可以引用新创建的对象。所以,如果你写:

Frob(grob);

没有问题,因为界面grob已经至少有一个参考。

当你写下来时会出现问题:

Frob(TGrobberImplementer.Create);

在那种情况下,没有任何东西需要引用接口,所以它被泄露了。好吧,只要Frob的实现中没有任何内容引用它,它就会被泄露。

  

我做了一件坏事吗?

嗯,这取决于。我不认为你所做的事情会有什么特别糟糕的。在性能方面存在缺点,因为接受接口参数的所有函数现在都必须使用隐式try / finally块添加和释放引用。只有你能判断这是否重要。

更重要的问题与您无法控制的代码有关。你给

procedure OnDocumentComplete(Sender: TObject; const pDisp: IDispatch; var URL: OleVariant);

作为一个例子。那里没有问题,因为你从来没有打过那种方法。它是您实现的事件处理程序。框架调用它,并传递已经引用的接口。

真正的问题来自RTL中声明的方法或您调用的任何其他第三方代码。如果您正在调用这些方法,并且他们使用const接口参数,那么您可能会陷入陷阱。

虽然很烦人,但它很容易解决。

grob := TGrobberImplementer.Create;
Frob(grob);

我处理这个问题的理由是这样的:

  1. 按值传递接口参数会产生性能成本。
  2. 我无法确保我调用的每个方法都会按值接受接口参数。
  3. 因此,我接受这样一个事实,即至少在某些时候我需要处理调用const接口参数。
  4. 由于我必须在某些时候处理它,因为我讨厌不一致,所以我总是选择接受处理它。
  5. 因此,我选择在我编写的方法中设置所有接口参数为const
  6. 因此我确保我从不将接口作为参数传递,除非它已被变量引用。