delphidwscript

How to declare a method of a Delphi object called from a script having a procedure type argument


In have a Delphi application running a DWS script. The Delphi application exposes an object instance, let's call it "MyApplication", to the script. The exposed object has a method which has one argument being a procedure.

Fundamentally by goal is to have a Delphi method doing some computation and stopping this computation when a callback procedure says it is done. The callback procedure is Inside the script.

I have implemented this by passing the name of the callback function as a string. It works nicely except that no type checking is done at script compile time. I would like to pass an actual procedure so that the script compiler can catch any error at compile time.

How to do that?

To help the reader understand what I mean, I show some - not working - code:

First a simplified verion of the Delphi side:

Interface
type
    TAppCheckProc = procedure (var Done : Boolean);

TMyApplication = class(TPersistent)
published
    procedure Demo(CheckProc : TAppCheckProc);
end;

Implementation

TMyApplication.Demo(CheckProc : TAppCheckProc);
var
    Done : Boolean;
begin
    Done := FALSE;
    while not Done do begin
        // Some more code here...
        CheckProc(Done);
    end;
end;

Second, on the script side I have this (Also simplified):

procedure CheckProc(
    var Done : Boolean);
var
    Value : Integer;
begin
    DigitalIO.DataIn(1, Value);
    Done := (Value and 8) = 0;
end;

procedure Test;
begin
    MyApplication.Demo(CheckProc);
end;

It is likely that Demo method argument should be declared differently and should be called differently. That is the question...

Edit: Removed extra Tag argument (Error when simplified the code, this is not the question).


Solution

  • I put this together quickly and it works. It gives a compile error when the parameters for the callback aren't correct. You need to create a delegate and use that as the type.

    Example using standalone function

    dwsUnit is the TdwsUnit that is being used for the custom Delphi methods.

    procedure TMainForm.FormCreate(Sender: TObject);
    var
      delegate: TdwsDelegate;
      func: TdwsFunction;
      parm: TdwsParameter;
    begin
      // Create a delegate
      delegate := dwsUnit.Delegates.Add;
      delegate.Name := 'TAppCheckProc';
      parm := delegate.Parameters.Add;
      parm.Name := 'Done';
      parm.DataType := 'Boolean';
      parm.IsVarParam := True;
    
      // Create our function and link it to the event handler
      func := dwsUnit.Functions.Add;
      func.Name := 'Demo';
      func.OnEval := dwsUnitFunctionsDemoEval;
      parm := func.Parameters.Add;
      parm.Name := 'CheckProc';
      parm.DataType := 'TAppCheckProc';
    end;
    

    The script that I used to test this is as follows:

    procedure CheckProc(
        var Done : Boolean);
    begin
      if Done then
        SayHello('World');
    end;
    
    Demo(CheckProc);
    

    If I change the parameter from a Boolean to an Integer I get a compile error on the script.

    My event handler for completeness looks like this:

    procedure TMainForm.dwsUnitFunctionsDemoEval(info: TProgramInfo);
    begin
      info.Vars['CheckProc'].Call([True]);
    end;
    

    Example using classes

    If you want to use classes then the code would be slightly different. Assuming you are using the CustomClasses demo and wanted to use the TEarth class then this would be the code to create the method and delegate.

    procedure TMainForm.FormCreate(Sender: TObject);
    var
      delegate: TdwsDelegate;
      method: TdwsMethod;
      parm: TdwsParameter;
    begin
      // Create a delegate
      delegate := dwsUnit.Delegates.Add;
      delegate.Name := 'TAppCheckProc';
      parm := delegate.Parameters.Add;
      parm.Name := 'Done';
      parm.DataType := 'Boolean';
      parm.IsVarParam := True;
    
      // Create our method and link it to the event handler
      method := TdwsClass(dwsUnit.Classes.Symbols['TEarth']).Methods.Add;
      method.Name := 'Demo';
      method.OnEval := dwsUnitFunctionsDemoEval;
      parm := method.Parameters.Add;
      parm.Name := 'CheckProc';
      parm.DataType := 'TAppCheckProc';
    end;
    

    The script to use this would be:

    procedure CheckProc(
        var Done : Boolean);
    begin
      if Done then
        PrintLn('Called with true')
      else
        PrintLn('Called with false');
    end;
    
    var earth: TEarth;
    earth:=TEarth.Create;
    earth.Demo(CheckProc);
    

    The event handler is as follows:

    procedure TMainForm.dwsUnitFunctionsDemoEval(info: TProgramInfo; ExtObject:
        TObject);
    begin
      info.Vars['CheckProc'].Call([True]);
    end;
    

    As with the standalone version, changing the script parameter type produces a "compiler" error.

    As SpeedFreak indicates in the comments. You can also do this through the IDE designer instead of in code.