【问题标题】:Cast from interface to class fails when introducing additional name for interface - why?为接口引入附加名称时,从接口转换到类失败 - 为什么?
【发布时间】:2011-08-22 05:57:55
【问题描述】:

我偶然发现了在某些情况下从接口硬转换到类失败的情况。

考虑以下类型定义:

  IDummy<T> = interface
  end;
  TMyRecord = record
    Intf:IDummy<Byte>;
  end;
  TDummy = class(TInterfacedObject, IDummy<Byte>)
  public
  end;

  IThisBreaksIt = IDummy<Byte>; // <== this line triggers the error

现在是使用类型的简单代码:

var
  ARecord:TMyRecord;
  Item:IDummy<Byte>;

  ImplWorks,
  ImplBroken:TDummy;
begin
  ARecord.Intf:=TDummy.Create;
  Item:=ARecord.Intf;

  ImplWorks:=TDummy(Item);
  ImplBroken:=TDummy(ARecord.Intf); // <== resulting instance is broken
end;

所以我正在做的是将接口引用存储在记录中。现在我想通过硬转换将其转换回实现类。

这里有一个问题:如果我为我的接口定义别名 (IThisBreaksIt = IDummy&lt;Byte&gt;),这将失败。注释掉这一行,ImplBroken 不再被破坏。在损坏的情况下ImplWorksImplBroken的地址不同;而ItemImplBroken 的地址现在是相同的。似乎负责硬铸造的自动魔法无法发挥作用。

其他发现:将 TDummy(ARecord.Intf) 替换为 ARecord.Intf as TDummy 可以解决此问题。

这让我有些头疼,因为它隐藏在一堆代码中,我没想到会出现这种行为。这正常吗?

为 Cosmin 编辑

努力将接口转换为对象的示例。

在 XE 中测试:有效(StreamAdaptIntf 和 StreamAdaptImpl 的指针不同;断言成功) 2009年测试:失败(StreamAdaptIntf和StreamAdaptImpl的指针相同,Assertion失败)

uses ActiveX;

var
  Stream:TStream;
  StreamAdaptIntf:IStream;
  StreamAdaptImpl:TStreamAdapter;
begin
  Stream:=TMemoryStream.Create;
  StreamAdaptIntf:=TStreamAdapter.Create(Stream, soOwned);

  StreamAdaptImpl:=TStreamAdapter(StreamAdaptIntf);
  Assert(Integer(StreamAdaptImpl) <> Integer(StreamAdaptIntf));
end;

【问题讨论】:

  • 刚刚在Delphi XE中测试了示例代码;对于StreamAdaptImpl := TStreamAdapter(StreamAdaptIntf),Delphi 编译器生成了一个call @SafeIntfAsClass。我删除了我的答案,因为我假设 Delphi 永远不会在硬演员中做“聪明的事情”是错误的。
  • @Cosmin:这给我们留下了一个问题,为什么 Delphi 在上述情况下不再聪明。现在我已经习惯了:)

标签: delphi casting delphi-xe


【解决方案1】:

这可能没有帮助,这个问题是很久以前提出的,但是对于将来检查这个问题的任何人......

您可能刚刚发布了测试代码,但您的界面应该包含一个 GUID,该 GUID 唯一地定义了编译器的界面(在 Delphi 2009 中为 Ctrl+Shift+G)。

IDummy<T> = interface
  ['{F9EF740B-FF23-465A-A2E0-E2ACD5ABD90F}']
  procedure DoSomething;
end;

硬铸造通常是不安全的。只有在您知道对象类型正确的情况下,才能真正接受硬转换。转换时最好在转换之前检查其类型,如下所示...

var
  lTypeA: TTypeA;
begin
  if ObjectA is TTypeA then begin
    lTypeA := TTypeA(ObjectA);
  end;

更好的是我会执行“as”强制转换,我认为如果它无效会导致异常。这真的更可取!我已经编写了执行硬转换的代码,只是为了花费数小时来弄清楚我的转换实际上是错误的...... Delphi 不会告诉你如果你转换为错误的类型,那么当你以后使用该对象时,你最终会在整个调试的噩梦中。 as 会引发异常并引导您解决代码中的问题

var
  lTypeA: TTypeA;
begin
  if ObjectA is TTypeA then begin
    // Will raise an exception if ObjectA is not TTypeA, 
    // in this simple case the ObjectA is TTypeA test is redundant
    lTypeA := ObjectA as TTypeA;
  end;

我真的只会在 delphi 中的对象之间进行转换。 Delphi 有一个有用的函数“支持”,它将确定一个对象是否实现了一个接口,并返回一个该接口的实例。然后,您可以使用返回的局部变量从界面执行您需要的任何功能

if Supports(ImplBroken, IThisBreaksIt, lObjInterface) then
begin
  lObjInterface.DoSomething;
end;

完整代码...

type
  // Generic IDummy interface
  IDummy<T> = interface
    ['{F9EF740B-FF23-465A-A2E0-E2ACD5ABD90F}']
    procedure DoSomething;
  end;

// Don't alias interfaces if possible
// This is a more specific version of your interface
IThisBreaksIt = interface(IDummy<Byte>)
  ['{76EFA371-4674-4190-8A4B-06850103C1D8}']
end;

TMyRecord = record
  // I would suggest, if you can avoid it don't store interfaces 
  // in a record, just simple types - just my opinion, delphi doesn't stop you
  Intf: IDummy<Byte>;
end;

// Remember this is interfaced, so the object only exists while refcount > 0
TDummy = class(TInterfacedObject, IDummy<Byte>)
protected
  { IDummy<T> }
  // interface methods should be protected
  // So ideally we refer to the interface of this object, 
  // not publicly available methods
  procedure DoSomething;
public
end;


var
  ARecord: TMyRecord;
  Item: IDummy<Byte>; // Think this should be IThisBreaksIt

  ImplWorks:TDummy;
  ImplBroken: IThisBreaksIt;
  lObjInterface: IThisBreaksIt;
begin
  ARecord.Intf := TDummy.Create;
  Item := ARecord.Intf;

  // Nasty hard cast here - what if your interfaced object is destroyed
  // before you reach this code section?
  ImplWorks := TDummy(Item);

  // This line compiles, buts it's really not the right way to get the interface
  ImplBroken := IThisBreaksIt(ARecord.Intf);

  // I believe this is the right way to get the interface for an object
  if Supports(ARecord.Intf, IThisBreaksIt, lObjInterface) then
  begin
    lObjInterface.DoSomething;
  end;
end;

【讨论】:

    猜你喜欢
    • 2010-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-03
    相关资源
    最近更新 更多