Beginners using interfaces often wonder how to get a reference to the implementing object behind an interface reference. One of the first things they try is to type-cast the interface reference back to the class type, like in Listing 1, but that will always fail.
Listing 1
A beginner’s attempt at resolving an interface’s underlying object
var
Intf: IUnknown;
Obj: TObject;
begin
Intf := TInterfacedObject.Create;
Obj := TObject(Intf);
end;
Internal interface layout
An interface reference does not really point to an object. For every
interface a class is declared to implement, the compiler adds a hidden
field to the class. That field holds a pointer to a list of function
pointers for the corresponding interface. There is also a hidden field that
all classes have, no matter whether they implement any interfaces.
That field is a pointer to the class’s VMT. Think of a TObject
reference as being a pointer to a record like the following.
Listing 2
Memory layout of TObject
type
TObject = ^TObjectLayout;
TObjectLayout = record
VMT: Pointer;
end;
The VMT holds pointers to all the
class’s virtual methods, as well as pointers to other information as
described in System.pas; find the
declaration of TObject there, and then look at all the
vmt constants declared above it.
Getting back to interfaces: TInterfacedObject descends from
TObject, and it is declared as implementing
IUnknown, too. It also has one declared field for the
reference count. Based on the model above, TInterfacedObject
is layed out as below.
Listing 3
Memory layout of TInterfacedObject
type
TInterfacedObject = ^TInterfacedObjectLayout;
TInterfacedObjectLayout = record
VMT: Pointer;
FRefCount: Integer;
FIUnknown: PIUnknownMT;
end;
There I’ve introduced another type, a pointer to an
IUnknown method table. That’s sort of like a
VMT. Imagine it’s declared like this:
Listing 4
IUnknown’s method table
type
PIUnknownMT = ^TIUnknownMT;
TIUnknownMT = record
QueryInterface: TQueryInterface;
_AddRef: TAddRef;
_Release: TRelease;
end;
That record holds pointers to the implementations of the interface’s
methods. For IUnknown, the method pointers could be declared
like this:
Listing 5
Method-pointer definitions for TIUnknownMT
type
PPIUnknownMT = ^PIUnknownMT;
TQueryInterface = function(Self: PPIUnknownMT; const IID: TGUID; out Obj): HResult; stdcall;
TAddRef = function(Self: PPIUnknownMT): Integer; stdcall;
TRelease = TAddRef;
Remember the extra field implicitly added to
TInterfacedObject, which points to the IUnknown
method table? When you have a variable of type
TInterfacedObject, it holds a pointer to the first
field of the TInterfacedObjectLayout record. When you have a
variable of type IUnknown, it holds a pointer to the
third field of the record—a pointer to the
TInterfacedObjectLayout.FIUnknown field.
So now you should be able to see that if you cast an interface
reference to an object reference, you’re not really pointing at the
same thing anymore. You’re at least eight bytes off from where
you’re supposed to be, corresponding to SizeOf(Pointer) +
SizeOf(Integer) for the two fields that come before
FIUnknown.
Calling a method on an interface
When your code contains a call to an interface’s method, it works
very similarly to calling an object’s method. The reference to the
interface is passed as the implicit first parameter to the method, which is
why the methods above don’t look quite like they do in the declaration
of IUnknown in System.pas.
Consider the two lines in the listing below. They perform identical operations. Yes, there really are two pointer dereferences for every interface method you call. (But the same applies to normal Delphi virtual methods.)
Listing 6
Equivalent ways of calling a method on an interface reference
Unk._AddRef
PPIUnknownMT(Unk)^^.AddRef(PPIUnknown(Unk));
The AddRef field isn’t quite
pointing to TInterfacedObject._AddRef. That method expects to
have a TInterfacedObject reference in its hidden first
parameter, but as you can see by how the call to
Unk._AddRef got translated in Listing 6, the hidden
first parameter was a PPIUnknownMT, which, as we now know, is
eight bytes away from the object reference
TInterfacedObject._AddRef is expecting. For that reason, the
AddRef field actually points to a “stub” object
generated internally by the compiler. It works a little like this:
Listing 7
Stub implementation of IUnknown._AddRef for TInterfacedObject
function IUnknown_AddRef_Stub(Self: PPIUnknownMT): Integer;
const
Offset = SizeOf(Pointer) + SizeOf(Integer);
var
Obj: TInterfacedObject;
begin
Obj := TInterfacedObject(Integer(Self) - Offset);
Result := Obj._AddRef;
end;
Since Delphi knows how to translate interface method calls into object method calls, you may be wondering why it can’t translate interface references into object references. It only translates the methods when it’s a Delphi object implementing those methods. When the interface is implemented by a C++ object (as is most likely when you have a COM object from the operating system), it’s code generated by the C++ compiler that translates interface method calls into calls that the C++ object is expecting. Sometimes, there is no object at all. Delphi comes with an demo of interfaces implemented without the use of objects. The project is QRegister.dpr; the exact location of the file depends on your Delphi version. It defines records just like the ones described in this article. Most of the method implementations are not stubs, though, since there are no object references to resolve.
Delphi generates the same code no matter how you call an interface method
because it has no idea what’s really behind that
interface. All it knows about is the method table for that interface, and
that’s the whole idea behind interfaces: Everything you need is exposed
by the interface declaration. If you need something else that the interface
doesn’t provide, then you either don’t have a good design, or you
need to call QueryInterface to get a reference to a more
appropriate interface.
So the reason Delphi can’t cast an interface reference to an object reference is because it has no way of knowing where the object reference—if there even is one—resides, relative to the interface reference.
If you know that the interface reference you have is
implemented by a Delphi object, then there are some functions in the JCL that can help you get the object reference. The
JclSysUtils.GetImplementorOfInterface function works by
following one of the method-table pointers and looking at the code in the
stub function to see what value is subtracted from the interface reference
to get the object reference.