Monday, September 11, 2006

Hack#11: Get the GUID of an interface reference

Recently Randy Magruder approached me with a very interesting sounding project he is working on:

"What I'd like to do is effectively add/remove new interfaces to a class at runtime, and invoke them. I have done enough that I can use the OTAServices model in Delphi to pass in additional interfaces, add them to an internal list, and override the QueryInterface behavior to return something in a contained interface list rather than having to declare it on the object."

That really sounds like a very cool (and complicated) project! He continues:

"But if the caller to the object only knows the GUID of the interface he wants, and wants to get back the appropriate interface, and invoke a method on it by name, how would I go about doing that?  Is the extended RTTI with IInvoker the way to go? "

Well, I'm not sure how that can be solved. AFAIK there is no easy mapping from an object implementing interfaces to the typeinfo of those interfaces. One solution might be to implement a registry mapping interface GUIDs to interface typeinfos.

"Also, do you know a way to extract a GUID from an interface instance at runtime, ..."

That should be possible, albeit a little complex. You may recall the little utility routine I wrote to convert a Delphi Win32 interface reference back into the object reference that implements it. It should be possible to morph that function into another function that returns the offset in the object that contains the
interface method table pointer. Combining this with a call to TObject.GetInterfaceTable to get a PInterfaceTable that contains a TInterfaceEntry record for each implemented interface. Compare the
calculated offset with the IOffset field - when you have a match you have the GUID in the IID field.

From System.pas:

 PInterfaceEntry = ^TInterfaceEntry;
TInterfaceEntry = packed record
IID: TGUID;
VTable: Pointer;
IOffset: Integer;
ImplGetter: Integer;
end;
PInterfaceTable = ^TInterfaceTable;
TInterfaceTable = packed record
EntryCount: Integer;
Entries: array[0..9999] of TInterfaceEntry;
end;
TObject = class
class function GetInterfaceTable: PInterfaceTable;

But that will only work for interfaces that are declared on the class at compile time, of course.


Randy continues his sentence above:



"...even if it's passed in as an IUnknown?"


Eh - you keep making it complicated, aren't you, Randy? ;) The GUID of IUnknown is fixed of course (it's {00000000-0000-0000-C000-000000000046}). And I don't think there is any trace of the interface/GUID that the IUnknown reference was converted from . OTOH, all interfaces inherit from IUnknown so if the IUnknown reference you have really is a sub-interface, it could work [it does -  as we will see in a moment].


After answering Randy's email, I took up the challenge and tried to implement my suggestion solution - getting the GUID of an interface reference. The starting point is the code for Hack#7: Interface to Object - which is able to convert an interface reference back into an object reference of the object that implements the interface (say that fast 5 times ;)).

function GetImplementingObject(const I: IInterface): TObject; 
const
AddByte = $04244483;
AddLong = $04244481;
type
PAdjustSelfThunk = ^TAdjustSelfThunk;
TAdjustSelfThunk = packed record
case AddInstruction: longint of
AddByte : (AdjustmentByte: shortint);
AddLong : (AdjustmentLong: longint);
end;
PInterfaceMT = ^TInterfaceMT;
TInterfaceMT = packed record
QueryInterfaceThunk: PAdjustSelfThunk;
end;
TInterfaceRef = ^PInterfaceMT;
var
QueryInterfaceThunk: PAdjustSelfThunk;
begin
Result := Pointer(I);
if Assigned(Result) then
try
QueryInterfaceThunk := TInterfaceRef(I)^. QueryInterfaceThunk;
case QueryInterfaceThunk.AddInstruction of
AddByte: Inc(PChar(Result), QueryInterfaceThunk.AdjustmentByte);
AddLong: Inc(PChar(Result), QueryInterfaceThunk.AdjustmentLong);
else Result := nil;
end;
except
Result := nil;
end;
end;

It does look like a lot of gobbledygook, but most of it is actually constant and type declarations to make the code more self-documenting and clear (yeah, right!). The code peaks into the code thunks generated by the compiler to convert an the Self: IInterface call-side parameter into the expected Self: TObject callee-side parameter.


While an object reference points to the first byte of the memory block allocated to hold the object instance fields, an interface reference points inside somewhere in the middle of the object memory block. So the only difference of the two types of reference pointers is a small offset. Our task is to find that offset. As it happens the compiler must fixup the Self reference of a interface method call - and it does this simply by adding a negative offset to the Self pointer before jumping to the actual method implementation. The code above reaches out into the QueryInterface code thunk and uses the offset there to convert the interface reference to an object reference.


To achieve the goal of finding the GUID of an interface reference, we should return the actual offset instead of just using it to generate an object reference. Lets rewrite the function above to do that.

function GetPIMTOffset(const I: IInterface): integer;
// PIMT = Pointer to Interface Method Table
const
AddByte = $04244483; // opcode for ADD DWORD PTR [ESP+4], Shortint
AddLong = $04244481; // opcode for ADD DWORD PTR [ESP+4], Longint
type
PAdjustSelfThunk = ^TAdjustSelfThunk;
TAdjustSelfThunk = packed record
case AddInstruction: longint of
AddByte : (AdjustmentByte: shortint);
AddLong : (AdjustmentLong: longint);
end;
PInterfaceMT = ^TInterfaceMT;
TInterfaceMT = packed record
QueryInterfaceThunk: PAdjustSelfThunk;
end;
TInterfaceRef = ^PInterfaceMT;
var
QueryInterfaceThunk: PAdjustSelfThunk;
begin
Result := -1;
if Assigned(Pointer(I)) then
try
QueryInterfaceThunk := TInterfaceRef(I)^.QueryInterfaceThunk;
case QueryInterfaceThunk.AddInstruction of
AddByte: Result := -QueryInterfaceThunk.AdjustmentByte;
AddLong: Result := -QueryInterfaceThunk.AdjustmentLong;
end;
except
// Protect against non-Delphi or invalid interface references
end;
end;

As you can see, this is basically the same function as above, but it returns an integer instead of a TObject. The function name is a little geeky - PIMT is short for pointer to interface method table. This is a special compiler generated "field" that is added to an object instance by the compiler when you declare that the class implements an interface. The "field" is a pointer to a kind of virtual method table for the methods declared on the interface. The function returns the offset of this field. Note that the compiler uses an ADD assembly instruction to adjust the Self parameter - but the value added is actually negative. That's why we return the negated value of the adjustment offset.


Now we can rewrite the original GetImplementingObject function in terms of this new, more basic function.

function GetImplementingObject(const I: IInterface): TObject;
var
Offset: integer;
begin
Offset := GetPIMTOffset(I);
if Offset > 0
then Result := TObject(PChar(I) - Offset)
else Result := nil;
end;

A neat little function - nice to get rid of the duplication. Note that PChar is the only pointer type that allows pointer arithmetic and we cast to it to turn the interface and offset into an object reference.


We're still only the first step towards getting the interface GUID (or IID which is the formally correct name). Now we're able to get the PIMT offset, but the offset by itself isn't very useful. What makes it useful is that we can use the offset to compare it with the offsets stored as part of the InterfaceEntry records the compiler generates for all the interfaces a class implements. As indicated above, we can use the TObject class function called GetInterfaceTable to get a pointer to this table. With that knowledge, let's write a function that tries to find the InterfaceEntry of an interface reference.

function GetInterfaceEntry(const I: IInterface): PInterfaceEntry;
var
Offset: integer;
Instance: TObject;
InterfaceTable: PInterfaceTable;
j: integer;
CurrentClass: TClass;
begin
Offset := GetPIMTOffset(I);
Instance := GetImplementingObject(I);
if (Offset >= 0) and Assigned(Instance) then
begin
CurrentClass := Instance.ClassType;
while Assigned(CurrentClass) do
begin
InterfaceTable := CurrentClass.GetInterfaceTable;
if Assigned(InterfaceTable) then
for j := 0 to InterfaceTable.EntryCount-1 do
begin
Result := @InterfaceTable.Entries[j];
if Result.IOffset = Offset then
Exit;
end;
CurrentClass := CurrentClass.ClassParent
end;
end;
Result := nil;
end;

First we use the the utility functions above to get both the object instance and the offset of the PIMT field. Then we loop across this class and all parent classes looking for an InterfaceEntry that has the same offset as our PIMT field. When we find a match, we return a pointer to the InterfaceEntry record. This record contains both the PIMT offset and the IID. Let's write a simple wrapper function to extract the IID.

function GetInterfaceIID(const I: IInterface; var IID: TGUID): boolean;
var
InterfaceEntry: PInterfaceEntry;
begin
InterfaceEntry := GetInterfaceEntry(I);
Result := Assigned(InterfaceEntry);
if Result then
IID := InterfaceEntry.IID;
end;

There - that was a no-brainer. Ok, now we have all the functions and utility punitions in place - now we just need to test that they actually work. Here is my simple test program.

program TestInterfaceGUID;

{$APPTYPE CONSOLE}

uses
SysUtils,
HVInterfaceGUID in 'HVInterfaceGUID.pas';

type
IMyInterface = interface
['{ABDA7685-DB67-43C1-947F-4B9535142355}']
procedure Foo;
end;
TMyObject = class(TInterfacedObject, IMyInterface)
procedure Foo;
end;

procedure TMyObject.Foo;
begin
end;

var
MyInterface: IMyInterface;
Unknown: IUnknown;
Instance: TObject;
IID: TGUID;
begin
MyInterface := TMyObject.Create;
Instance := GetImplementingObject(MyInterface);
Writeln(Instance.ClassName);
if GetInterfaceIID(MyInterface, IID) then
writeln('MyInterface IID = ', GUIDToString(IID));
Unknown := MyInterface;
if GetInterfaceIID(Unknown, IID) then
writeln('Dereived IUnknown IID = ', GUIDToString(IID));
Unknown := TMyObject.Create;
if GetInterfaceIID(Unknown, IID) then
writeln('Pure IUnknown IID = ', GUIDToString(IID));
readln;
end.

This program declares an interface with a method Foo and a class that implements it. Then it creates an instance of the class - assigning it directly to an interface reference. First we test the GetImplementingObject function and write out the name of the implementing class. Then we call GetInterfaceIID three times and print out the resulting GUID. In the first call we use the actual interface directly - if our code is correct, this should work fine. In the second call we have transferred the interface reference into an IUnknown reference. Depending on how the compiler implements interface assignment between assignment compatible interfaces, this may or may not work. We'll see. In the final call we assign a new IUnknown reference a new instance of the class. In this case, we expect the GUID of IUnknown to be returned.


When we run the code we get the following output:

TMyObject
MyInterface IID = {ABDA7685-DB67-43C1-947F-4B9535142355}
Derived IUnknown IID = {ABDA7685-DB67-43C1-947F-4B9535142355}
Pure IUnknown IID = {00000000-0000-0000-C000-000000000046}

Looks like the code works ;). We get the expected results for the class name and the first interface IID. It is also interesting (and useful) that the second "Derived IUnknown IID" returns the IID of the original interface. Unsurprisingly the IID of IUnknown is the one defined by Microsoft. The reason the second IID is preserved is that the Unknown reference is a pure copy of the MyInterface reference. This is the assembly code the compiler generates for the assignment.

  Unknown := MyInterface;  
mov eax,$0040a7a4
mov edx,[MyInterface]
call @IntfCopy

This code calls the RTL routine to copy interfaces, System._IntfCopy, which handles the reference counting of the source and destination interfaces references. So the actual reference stays the same - it only affects the reference count (by calling _AddRef). That's why we get the desirable result with the IMyInterface GUID instead of the IUnknown GUID in the second case.


If the IUnknown interface is assigned using an as-cast, the result it different.

  Unknown := MyInterface as IUnknown;  
if GetInterfaceIID(Unknown, IID) then
writeln('As IUnknown IID = ', GUIDToString(IID));

In this case the output is

  As IUnknown IID = {00000000-0000-0000-C000-000000000046} 

We get the IUnknown GUID, not the IMyInterface GUID. The reason is that the compiler generates the as-cast and assignment like this:

  Unknown := MyInterface as IUnknown;  
mov eax,$0040a7a4
mov edx,[MyInterface]
mov ecx,$00408b0c
call @IntfCast

This version uses System._IntfCast to do the cast and assignment - and looking at the source it uses QueryInterface to perform the conversion. QueryInterface will return another new interface reference (the PIMT field, remember) that TInterfacedObject added for the IUnknown (aka IInterface) interface. This interface has its own IID of course, so we don't get the original IID of IMyInterface that Randy wanted in his original question.


The long string of hex numbers that a GUID consists of isn't very human readable. Some interfaces (particularly COM interfaces), have their IIDs and corresponding name string registered in the registry. For instance, HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Interface\{00000000-0000-0000-C000-000000000046} = IUnknown. It is possible to lookup a GUID in the registry to try and find the name string. This is left as an exercise for the reader ;).

3 comments:

Chris said...

This is very interesting. I have a related question. Is it possible to get the Interface Name string (eg the string 'IUnknown') from the Interface reference or the IID?

Hallvard Vassbotn said...

Chris: AFAIK, the interface name string is not stored as part of the RTTI. But some interfaces are possible to lookup in the registry - see the last paragraph of the blog post above ;).

alamaison said...

That you *so* much for that last paragraph :P COM newbie here and I was desperately trying to work out how to find which interface was being requested. I naively assumed the would be stored with the CLSIDs. Silly me. Thanks :D



Copyright © 2004-2007 by Hallvard Vassbotn