Friday, August 21, 2009

The “Nitty Gritty” details on Casting an Interface Reference in Delphi 2010

Malcolm Groves let loose on a new Delphi compiler feature where you can now cast an interface reference to the implementing Delphi class. Joe White was interested in the “gritty” details about how this was implemented. It turns out that it was deceptively easy once I figured out the internal compiler logic for handling “is” tests and “as” and hard casts. All three cases use the same internal mechanism implemented in the Delphi RTL.

Because all Delphi interfaces have three core methods, QueryInterface, _AddRef, and _Release, you know that you can always call QueryInterface on any interface reference. QueryInterface is declared thusly:

function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;

The last parameter is an “untyped out” parameter meaning that it will accept a reference to any variable of any type. What if you called QueryInterface with a special GUID (remember, GUID = Globally Unique IDentifier) and instead of assigning an interface reference to the Obj parameter, the implementing class instance was assigned instead? The base implementation of QueryInterface simply calls TObject.GetInterface. It is in that method that code will now look for this special GUID (which is declared in the implementation section of System.pas) and assign Obj the object reference instead of a new interface reference.

Granted, this technique does have a couple of potential issues. If, for some reason, the user decides to manually implement QueryInterface and never calls TObject.GetInterface, then none of the above casting and “is” testing will work. It will behave as if the implementing class was not implemented in Delphi. Another caveat, is if you’re doing cross-process marshaling of interfaces where both sides are Delphi 2010, you should not attempt to use this technique. In fact, if you’re doing any kinds of traditional COM programming, using this feature can yield strange results. This feature was a direct result of many requests from customers who are using interfaces for purposes outside COM.

8 comments:

  1. Very elegant, I like it!

    "Another caveat, is if you’re doing cross-process marshaling of interfaces where both sides are Delphi 2010, you should not attempt to use this technique."

    Just a thought, but if the internal GUID were changeable at runtime, couldn't this danger be averted? In fact, why not generate the GUID at application start up (a BPL would query its EXE for the right one)? While this would make casting across EXE and plain jane DLL boundaries not work by default, passing objects in that way is a dubious practice anyway of course.

    ReplyDelete
  2. [...] — will be removed in Delphi 2010, a thought strengthened when I read Allen Bauer’s subsequent post on the not particularly gory details. The one quibble I have, though, is how the new feature is [...]

    ReplyDelete
  3. [...] CR Getting down to the actual implementation of interface-to-object casts in D2010, Allen Bauer reports that the solution was actually quite simple. Basically, given interface casting already used [...]

    ReplyDelete
  4. Nice to see this as part of the language.

    It is possible to achieve the same functionality in older Delphi versions as well, as I blogged about in 2004:

    http://hallvards.blogspot.com/2004/07/hack-7-interface-to-object-in-delphi.html

    My solution is a bit more hacky, though. ;)

    ReplyDelete
  5. @Chris: A neat idea, but my first thought is that without a bit of finesse that would break the mechanism for both cross-process *and* in-process interfaces.

    The finesse required would be to find some way for in-process DLL's to share the special IID with the "container" process.

    At least currently the mechanism works and can be used for all those situations where the application developer know's it is safe to do so. It's entirely up to them if they want to shoot themselves in the foot with it.

    :)

    ReplyDelete
  6. Nice solution.

    I had mentioned the "special GUID" approach in a C++Builder article a few months ago (http://www.audacia-software.de/en/bcb/dynamic-cast-on-interfaces.htm#workaround). Of course, when implemented language integration in Delphi, it's way more elegant to use.

    (I miss an equivalent "interface_cast" in the C++Builder RTL though.)

    > Granted, this technique does have a couple of potential issues. If, for some reason, the user decides to manually implement QueryInterface and never calls TObject.GetInterface, then none of the above casting and “is” testing will work.

    The same happens when using the Supports(TClass,TGUID) overload, which just calls TObject.GetInterface(), so it's not uncommon in the Delphi world.

    (FWIW, I think class references and all their implications - virtual constructors, virtual class methods, the ability to determine the interfaces supported by a class without having an actual object etc. - are the greatest assets of the Delphi language.)

    ReplyDelete
  7. [...] set this up to load itself into my class RTTI inspector tool, and used Delphi 2010′s trick of casting an interface to an object, and it worked! And here’s what the object looks [...]

    ReplyDelete

Please keep your comments related to the post on which you are commenting. No spam, personal attacks, or general nastiness. I will be watching and will delete comments I find irrelevant, offensive and unnecessary.