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 ImplBroken
is 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;
精彩评论