Adding scripting support to a Delphi application, again

I’ve updated the Delphi 2010 IDispatch proxy code I posted a couple of weeks ago, fixing a few bugs, improving how Delphi exceptions are handled in my IDispatch.Invoke implementation, and adding some set type support. For the latter, set values are now surfaced as objects with Empty and Contains methods, with the creation of new set values in script being made possible via a new descendent of TCustomDispProxy specially for set types.

That said, I’ve also added a more realistic demo in the form of a scriptable version of the old text editor standby. Being ‘more realistic’ meant two things in particular: drastically cutting down the scope for scripts creating Delphi objects (the issue here being Delphi’s lack of garbage collection in combination with VBScript’s lack of a try/finally equivalent), and creating some script-specific classes to abstract from the application’s internals – basically, you don’t really want to be exposing an application’s internals directly.

One thing I should mention is that because I was finishing it right at the end of my trial period, there’s the odd bug in the new demo’s UI that I didn’t have the time to clean up – in particular, I forgot to add disabled images for the formatting actions, which causes their icons go a bit funny when the app loses the focus. Nonetheless, the proxy code itself should be pretty solid. If you want to see it in action, download the revised code from here.

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.]

Something new for something old: Delphi 2010’s RTTI and Active Scripting

I’ve been playing around with the new RTTI stuff in the D2010 trial, and I have to say, I’m impressed. But for what it surfaces for method pointers (= event types) and sets, it seems both very complete and very intuitive to use.  Not only have I managed to use it without having reference to either source code or an API reference (though let’s not encourage Embarcadero on the latter, eh?), but it has been generally very solid — do something wrong, and an appropriate exception is cleanly raised with a message that tells you straighaway what the issue is. The one (er) exception to this is TValue not doing quite enough verification when attempting a cast to a Variant, though as the latter is an explicit operation, it’s easy enough to work around if and when it bites.

That said, reading the small amount of documentation, I happened across mention of an Invoke method, which set me thinking — is it finally possible to cleanly (and generically!) wrap Delphi objects into IDispatch ones for use in Active Scripting?  Well, apart from the events issue and the difficulties of surfacing set types in a script-friendly manner, my answer is: for sure! Check out this compiled EXE if you’re interested (screenshot here).

Basically, what I’ve done is to write a TCustomDispProxy class with TObjectDispProxy and TClassDispProxy descendants; in the demo, these are then used to directly surface the Application, Mouse and Screen objects, together with the form instance (object properties and fields are handled automatically) and some metaclasses (TButton, TLabel, etc.).The idea is that no specific object or class type requires a specific wrapper — all just use TObjectDispProxy and TClassDispProxy respectively, with wrappers for sub-objects created on the fly. Now unlike Allen Bauer, I’m not a masochist in these things, and so have used the MS Script Control rather than implemented the Active Scripting interfaces directly; because of this, be warned that if the control isn’t installed (which is unlikely these days, though could be the case), the EXE won’t run.

Anyhow, if anyone’s interested in the actual code, add a comment saying so and I’ll endeavour to do it up for public consumption. Alternatively, if you’re not interested, then don’t leave a comment and I won’t bother…

[Update: I’ve put up the code now, adding enumerator support and a few other bits compared to the original demo — see here.]