Something new for something old — the code

I’ve finally put up the IDispatch wrapper code I mentioned in my previous post — see here for some information and the CodeCentral link. Compared to the compiled demo I posted earlier, I’ve implemented a few more things, e.g. read-only dynamic array support and the automatic exposure of most enumerators, the latter enabling For Each in VBScript where you would use for/in in Delphi.

Nonetheless,whether it’s practically useful or not I don’t really know — writing it just scratched a very old itch really, having played around with the script control many years ago. Moreover, as a demo of the new RTTI, my code is not exactly the best, since most of it concerns implementing IDispatch rather than using Rtti.pas — though of course, that’s as much a tribute to the latter as it is a criticism of my code qua demo of it.

Having said that, writing the proxy classes did make explicit certain limitations of the new RTTI — basically, while its coverage is very good, it isn’t perfect:

  • Where TRttiMethod.Invoke accepts a TValue, object or class as its first parameter, TRttiProperty.GetValue only accepts a pointer. This probably just refects underlying limitations, but nonetheless, the interface should match Invoke IMO.
  • Indexed properties are not surfaced at all.
  • You can’t tell whether such-and-so property is a default property.
  • Class vars are not surfaced.
  • Method pointers (= events) seem tricky (even impossible) to work with when put into a TValue . (Note they aren’t put in as TMethod records, but anonymous method interfaces, or at least, seem to be. Ignore that — I must have been testing incorrectly. They are in fact stored as TMethod records, as you would expect. See my post here for how to invoke an event handler using RTTI.)
  • Sets aren’t exposed as nicely as arrays are.
  • An interface type requires the explicit addition of $M+ (or be derived from IInvokable) for its methods to be surfaced. Even then however, interface properties are ignored. (I’m guessing this is yet another couple of quirks due to the Delphi interface type’s origins as a COM support feature.)

Overall though, the new RTTI is still a very impressive feature and one I think people should use in confidence.

[Update: Barry Kelly, the author of the new RTTI, explains the limitations just listed in the comments.]

3 thoughts on “Something new for something old — the code

  1. > Indexed properties are not surfaced at all.

    Yes, I’ve missed them as well.

    > An interface type requires the explicit addition of $M+ (or be derived from IInvokable) for its methods to be surfaced.

    Yes, now all classes have RTTI by default, but why not interfaces?

  2. TRttiMethod.Invoke takes object or class because method invocation is only supported on objects or classes, not records. If it supported records, then it would also have a Pointer overload. Both currently funnel to the TValue overload, but could operate differently in future to avoid the effective “boxing” that’s currently done. Having the TValue overload available means you don’t have to unbox and rebox if your instance is already in a TValue.

    TRttiField.Get/SetValue takes a pointer because when you’re modifying a record, it’s important that you don’t pass it by value. TRttiProperty matches TRttiField for symmetry. I can see an argument for adding object and class overloads, but it would only be for documentation, as both would simply call the Pointer overload.

    Yes, indexed properties aren’t surfaced as RTTI properties currently use the same format as published properties, with a little extra for attributes. This is actually somewhat problematic, as calling convention isn’t encoded, so to set properties using RTTI, getter and setter must use register CC. If indexed property support was added, indicating default would be easy too.

    Method pointers can be retrieved at a low level, you can extract the data into a TMethod, or alternatively typecast TValue to TValueData and access FAsMethod directly. There is actually typeinfo for method pointer types sufficient to invoke them, but it is not exposed in the API due to time constraints.

    Yes, the set support when contained in a TValue could be nicer. Also, large sets (larger than register, more than 32 members) could be supported, as could discontiguous enumerations – enumerations with explicit initializers that don’t map to 0..n. I did have a kind of support for the enumerations at one point, but I didn’t like the look of how it was shaping up, so I left it out for now.

    Interfaces don’t have RTTI by default because I had to stop somewhere. I could easily have applied $M+ to IInterface, but using TLB auto-generated files would result in fairly substantial increases in size for using e.g. the IE control – and actually the data required is in the TLB, technically the RTTI ought to be able to pull it out of there, but that would require quite a bit more work.

    Other things like class vars – essentially oddly-named global variables from the compiler’s perspective – properties on interfaces, methods and properties on records, global variables, functions and procedures, I had to stop somewhere. These were the original goals:

    -) A unified API that exposes all RTTI that we embed, within reason.

    -) A way to iterate over all RTTI “roots”

    -) “Invokable”-level RTTI & API for methods

    -) A basic implementation of attributes

    -) RTTI for fields sufficient for binary serialization

    I believe these goals were met in the large, and I think many, if not most, of the scenarios people use reflection for in other languages can be implemented to a large degree using the new API.

    • Barry – thanks for the rather lengthy comment!

      Method pointers can be retrieved at a low level, you can extract the data into a TMethod, or alternatively typecast TValue to TValueData and access FAsMethod directly.

      Hmm, my bad — I could have sworn TValue.DataSize was returning 4 when it contained an event handler, but no, just trying it again, it returns 8, as expected. Scratch one quirk off my list!

      I believe these goals were met in the large, and I think many, if not most, of the scenarios people use reflection for in other languages can be implemented to a large degree using the new API.

      Well, I wasn’t intending to disagree with that. Indeed, I was coming from the angle of being surprised at just how far you had got given the new RTTI interface is an evolution of the old one (albeit a radical evolution!), and from there, just noting the few things that were left out. The only big-ish one in my view is indexed properties, simply because they get used all the time in ‘regular’ VCL code (TStrings.Strings in particular comes to mind).

Leave a comment