Tuesday, November 16, 2004

Object-to-interface casts

There are several ways to check if an object reference implements an interface or not - but there are some platform differences. The as-cast works (almost) identically in both Win32 and .NET.

var 
MyObject: TMyObject;
MyInterface: IMyInterface;
begin
// ...
MyInterface := MyObject as IMyInterface; // may raise EInvalidCast
MyInterface.Foo;
end;

The platform difference is that on Win32, the declared object type must implement the IInterface (or IUnknown) interface (as TInterfaceObject does, for instance), while in .NET any object type will do. If the cast fails, an exception will be raised. This is often undesirable - often you need to treat objects differently depending on if they implement a specific interface or not, and handling exceptions is too slow and cumbersome. On .NET you can cast from any object to any interface, using a hard-cast syntax. If the cast fails, nil is returned instead.

var 
MyObject: TMyObject;
MyInterface: IMyInterface;
begin
...
MyInterface := IMyInterface(MyObject); // .NET specific
if Assigned(MyInterface) then
MyInterface.Foo;
end;

Currently, the Win32 compiler does not support this cast in a generic way. It does support it in the specific case when the cast can be determined to be valid at compile-time. This can be done when the declared object type statically implements the given interface. If this is not the case, the cast fails with a compile-time error; [Error]: Incompatible types: 'IMyInterface' and 'TInterfacedObject' To check if an object supports an interface without raising exceptions, you can first cast it to IInterface then call the QueryInterface method. This only exists on Win32:

 Intf := IInterface(MyObject); 
if Intf.QueryInterface(IMyInterface, MyInteface) = 0 then
MyInterface.Foo;

The Supports routine in the SysUtils unit gives is a more convenient and cross-platform way of doing the same thing:

 if Supports(MyObject,IMyInterface, MyInteface) then 
MyInterface.Foo;

Supports is also available on .NET, but it is implemented in a way that makes it much slower than using the (currently) .NET-specific hard-cast. This is because the .NET version is implemented in terms of the new 'type of interface' construct:

type 
TInterfaceRef = type of interface;
...
function Supports(const Instance: TObject; const IID: TInterfaceRef; out Intf): Boolean;
begin
Result := Instance is IID;
if Result then
Intf := Instance as IID;
end;

The 'type of interface' construct is pretty neat, actually. It allows you to pass any interface-type as a parameter to a method. This is used instead of a TGUID as an identifier or handle to the given interface. The Supports function itself compiles into the following IL:

.method public hidebysig static bool Supports(object Instance, [mscorlib]System.RuntimeTypeHandle IID, object& Intf) cil managed 
{
// Code Size: 50 byte(s)
.maxstack 3
.locals (
bool flag1,
[mscorlib]System.Type type1)
L_0000: ldarg.1
L_0001: call [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle([mscorlib]System.RuntimeTypeHandle)
L_0006: ldarg.0
L_0007: callvirt instance bool [mscorlib]System.Type::IsInstanceOfType(object)
L_000c: stloc.0
L_000d: ldloc.0
L_000e: brfalse.s L_0030
L_0010: ldarg.2
L_0011: ldarg.1
L_0012: call [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle([mscorlib]System.RuntimeTypeHandle)
L_0017: stloc.1
L_0018: ldloc.1
L_0019: ldarg.0
L_001a: callvirt instance bool [mscorlib]System.Type::IsInstanceOfType(object)
L_001f: brtrue.s L_0029
L_0021: ldarg.0
L_0022: ldloc.1
L_0023: call object Borland.Delphi.Units.System::@CreateCastException(object, [mscorlib]System.Type)
L_0028: throw
L_0029: ldarg.0
L_002a: castclass object
L_002f: stind.ref
L_0030: ldloc.0
L_0031: ret
}

Quite a bit of code!


With a little help from Reflector, I've found that it corresponds to something like this in Delphi code:

function Supports(Instance: TObject; IID: RuntimeTypeHandle; out Intf: TObject): boolean; 
var
InterfaceType: System.Type;
begin
Result := System.Type.GetTypeFromHandle(IID).IsInstanceOfType(Instance);
if Result then
begin
InterfaceType := System.Type.GetTypeFromHandle(IID);
if (not InterfaceType.IsInstanceOfType(Instance)) then
raise EInvalidCast.Create(SInvalidCast);
Intf := Instance;
end;
end;

Notice that the code is a little redundant. The precence of both the is-check and the as-check makes the compiler emit calls to GetTypeFromHandle and IsInstanceOfType twice each. The exception will never be raised in this method. So there is a little room for improvement here. I think that passing 'type of interface' parameters as System.Type instead of RuntimeTypeHandle would also make it a little more efficient - in most (all?) cases the call site has a Type reference anyway and first has to convert it into a RuntimeTypeHandle. The site calling Supports yields this IL:

   ldloc.1 
ldtoken Obj2IntfCastNET.IMyInterface
call [mscorlib]System.Type [mscorlib]System.Type::GetTypeFromHandle([mscorlib]System.RuntimeTypeHandle)
pop
ldtoken Obj2IntfCastNET.IMyInterface
ldloca.s obj2
call bool Borland.Vcl.Units.SysUtils::Supports(object, [mscorlib]System.RuntimeTypeHandle, object&)
stloc.0


By contrast, performing the same operation by using the object-to-interface hard-cast, gives this very simple and efficient IL:


    ldloc.s obj2
   isinst Obj2IntfCastNET.IMyInterface
   stloc.2


Can you guess which is faster? No prizes for this one! So you can use the cross-platform Supports function - which is fairly efficient in Win32, but relatively slow in .NET, or the .NET platform specific and efficient hard-cast. Most of the time, the slow .NET version of Supports does not really matter, but for some performance critical points, you might want to use conditional compilation to select the best solution for each platform, like this:

{$IFDEF WIN32} 
if Supports(MyObject,IMyInterface, MyInterface) then
{$ENDIF}
{$IFDEF CLR}
MyInterface := IMyInterface(MyObject);
if Assigned(MyInterface) then
{$ENDIF}
MyInterface.Foo;

I hope that a future version of the Win32 compiler can be extended to support hard-casts from object-to-interface with the same semantics as on .NET. The cast should return nil if the object does not support the interface (yes, this has been reported to Borland). In .NET you can also use the 'is' operator to check if an object implements an interface or not. This is not currently supported in Win32.

 if MyObject is IMyInterface then 
Writeln('Yup');

Friday, November 12, 2004

Machine Code Hacking

I'm not shy from performing the occasional hack, and feel comfortable working in the CPU view of the debugger. Microsoft hacker Raymend Chen seems to be in another league however. He claims he never uses source level debugging (its too mucked up by the optimizer anyway) and here he gives some tips on how to patch machine code bytes while debugging to make the debuggee do what you want. Useful for those low-level debugging sessions ;).

Tuesday, November 09, 2004

Rocket Scientist wanted!

Our company are looking for the best Delphi and C++ programmers out there. From our website: "Our highly qualified team of developers are looking for new colleagues. If you are an experienced developer looking for a challenging position in an informal organization we would very much like to hear from you." The details about the positions are available here (in Norwegian): "Rocket Scientist" - Borland Delphi / C/C++ So send us an email with your resumé - or tips your favorite Delphi or C++ guru... :) Updated: The job advert is now available in English: http://www.infront.no/pdf/jobad_programmer.pdf

Monday, November 08, 2004

Delphi 2005 Reviewer's Guide

Borland has just made a very comprehensive review guide for Delphi 2005 available. It is written by Cary Jensen and can be downloaded here: http://www.borland.com/products/white_papers/ pdf/delphi_2005_reviewers_guide.pdf If you wonder what's new and how the new features look like, check it out.

Interfaces - benfits and platform differences

Interfaces is a very powerful tool that can improve the quality of your code, if used properly. Interfaces can give the following benefits:

  • Reduced coupling between components
  • Strong encapsulation
  • High extensibility
  • Easy substitutability
  • Run-time discovery of object capabilities

Under the hood, interfaces are implemented very differently in .NET and Win32. Win32 interfaces follow the COM model, with an IUknown base interface that provides basic services for reference counted lifetimes and interface support querying.

A Win32 interface declaration needs a GUID to enable as-casts (or QueryInterface or Supports calls). GUIDs are not needed in .NET - they can optionally be used to expose interfaces to COM with a specific GUID. To generate a GUID in the editor use the Shift+Ctrl+G keyboard shortcut.

type 
IMyInterface = interface
['{6141D774-34AD-4E11-AB16-DD74EFE793F5}']
procedure Foo;
end;

.NET interfaces do away with reference counting and instead relies on the garbage collection mechanism that all object references enjoy. There is no base-interface that all other interfaces inherit from.


Instead of QueryInterface, IL instructions like castclass and isclass and methods of the Type class are used. At runtime, the reference value of an interface variable is identical to the object reference value that implements the interface. The only difference is the type of the variable and how the JIT compiler dispatches method calls on the interface reference. More on casting to and from interfaces differences shortly.



Copyright © 2004-2007 by Hallvard Vassbotn