开发者

Cast from interface to class fails when introducing additional name for interface - why?

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 t开发者_运维知识库o 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;


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;
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜