为接口引入附加名称时,从接口到类的转换失败 - 为什么?

发布于 2024-11-06 03:10:25 字数 1607 浏览 1 评论 0原文

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

考虑以下类型定义:

  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),则会失败。注释掉这一行,ImplBroken 就不再损坏了。在损坏的情况下,ImplWorksImplBroken 的地址是不同的;相反,ItemImplBroken 的地址现在是相同的。似乎负责硬转换的 automagic 无法启动。

其他发现:用 ARecord.Intf 替换 TDummy(ARecord.Intf) 作为 TDummy 修复了它。

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

Cosmin 编辑

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

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

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;

I stumbled upon a case where hard-casting from interface to class fails under certain circumstances.

Consider the following type definitions:

  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

And now the simple code that uses the types:

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;

So what I am doing is storing an interface reference inside a record. Now I want to cast this back to the implementing class with a hard cast.

Here is the catch: this fails if I define an alias for my interface (IThisBreaksIt = IDummy<Byte>). Comment out this line and the ImplBrokenis not broken anymore. In the broken case the addresses of ImplWorks and ImplBroken are different; instead the addresses of Item and ImplBroken are now the same. It seems like the automagic responsible for hard-casting fails to kick in.

Additional finding: Replacing TDummy(ARecord.Intf) by ARecord.Intf as TDummy fixes it.

This gave me some headache because it was buried in a bunch of code and I wasn't expecting this behavior. Is this normal?

Edit for Cosmin:

Example for working hard cast of interface to object.

Tested in XE: works (the pointers of StreamAdaptIntf and StreamAdaptImpl differ; Assertion succeeds)
Tested in 2009: fails (the pointers of StreamAdaptIntf and StreamAdaptImpl are the same; Assertion fails)

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;

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

我不是你的备胎 2024-11-13 03:10:25

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

您可能刚刚发布了测试代码,但您的界面应该包含一个 GUID,GUID 唯一定义了编译器的接口(Ctrl在 Delphi 2009 中为 +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 有一个有用的函数“supports”,它将确定一个对象是否实现了一个接口并返回该接口的一个实例。然后,您可以使用返回的局部变量来执行接口

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;

It's probably no help, this question was asked ages ago, but for anyone checking this out in future...

You've probably just posted test code but your interface should contain a GUID, the GUID uniquely defines the interface to the compiler (Ctrl+Shift+G in Delphi 2009).

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

Hard casting is generally unsafe. A hard cast is only really acceptable where you know that an objects type will be correct. It's preferable when casting to check its type before the cast as follows...

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

Even better I'd perform an "as" cast, which I think will cause an exception if it is invalid. This REALLY is preferable! I've written code that performs a hardcast only to spend hours and hours figuring out that actually my cast was wrong... Delphi won't tell you if you cast to the wrong type, then when you later use the object you end up in a whole debugging nightmare. The as will raise an exception and guide you to the problem in your code

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;

I'd really only cast between objects in delphi. Delphi has a helpful function "supports" which will determine whether an object implements an interface and give you back an instance of that interface. You can then use the local variable returned to perform whatever function you need from the interface

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

Code in full...

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;
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文