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.

12 thoughts on “Adding scripting support to a Delphi application, again

    • Hi Pablo –

      Not sure what the problem is. In either demo, you should be able to do this:

      Dim List
      Set List = TStringList.Create
      List.CommaText = “1,2,3”
      //pass List to a method that takes a TStrings as a parameter…

      • Chris
        Thanks , i tried your example and work very well, but my real doubt is about calling a script from a programm without a form (a service for example) and return such type of value.

        Thanks again

        • Hmm, I’m still not quite clear on what the problem is. If you check out CCR.DispProxies.pas, you’ll see it doesn’t even include Forms in its uses clause – the proxy code is completely generic.

  1. My version of IActiveScript uses some ‘hand-made’ library proxys.
    Currently, you can write a entire application into a script (changing Application.Run into Script.Call(‘Main’, []))
    I even add support to ‘load’ dfm streams and bind into javascript objects (if the object have a BtnOKClick function, will be bind to “OnClick” for “BtnOK” on that dfm…)

    • Currently, you can write a entire application into a script

      What do you mean by this exactly? In coding what I did, the limitations and quirks of the new-style RTTI interface did not prevent me from coming up against the problem of certain Delphi-isms having no obvious IDispatch implementation (note I did manage to expose metaclasses in a generic fashion, so it wasn’t as if I got nowhere). In particular, sets and dynamic arrays can only be exposed in a sub-optimal manner, and another Delphi-ism – overloaded methods — was translatable only at the cost of not implementing named arguments (named arguments being a ‘script-ism’ if you will). That said, if you feel you have solved these problems, I’d be very interested in seeing a demo.

  2. That’s an awesome piece of code!

    It’s been very educational to look into the details.

    It’s so clean and yet full of cool tricks that this application should be included as a demo with Delphi.

    • Thanks for the compliments Wouter. If the opportunity arises, you might want to nudge Barry Kelly (or the Delphi team generally) though about filling the odd hole in the new RTTI system — for my code, the lack of RTTI for indexed properties was the one real biggie.

  3. Very nice! Have you ever thinked on “upgrade” your code to Delphi XE2? RTTI has a little bit more functionality there.

  4. Hi! Thanks for the code.
    Unfortunately, It has some problems with big numbers (Int64 type).

    In “Exposing everything demo” I make a little addition:

    type
    TfrmMain = class(TForm)
    private
    FTestInt: Int64;

    public

    property TestInt: Int64 read FTestInt write FTestInt;
    end;

    procedure TfrmMain.actRunExecute(Sender: TObject);
    begin

    ShowMessage(IntToStr(TestInt));
    end;

    When I run script “Self.TestInt = 10”, I get the MessageBox with text “10” – OK.
    But when I run script “Self.TestInt = 271029435038236”, I get exception “Invalid class typecast”.

    Has any idea?

    P.S. I’m trying with Delphi XE5 + Win8.1

    • Issue is that 64 bit integers were traditionally not supported by COM Automation. As such, either ActiveScripting itself or the MS Script Control wrapper (not sure which – may just be the latter) is interpreting the literal being assigned as a double (floating point value), which then causes a cast error on assignment to the Delphi property. For a fix, amend TCustomDispProxy.SetPropertyValue to look like this:

      procedure TCustomDispProxy.SetPropertyValue(Prop: TRttiProperty; const Value: TValue);
      begin
      if (Value.Kind = tkFloat) and (Prop.PropertyType.TypeKind = tkInt64) and (Frac(Value.AsExtended) = 0) then
      Prop.SetValue(SourceAsRefType, Trunc(Value.AsExtended))
      else
      Prop.SetValue(SourceAsRefType, Value);
      end;

Leave a reply to Andrew Cancel reply