1

This weekend, I updated my code base from DWScript SVN. I used Preview 2.7 and now I'm using up-to-date trunk version.

I recompile my application and now the OnAfterInitUnitTable is no more triggered. Actually TdwsUnit.InitUnitTable is not called at all. BTW: TDWSunit is created at runtime by code and then two classes are exposed using ExposeRTTI. In need to expose one instance of each class.

What are - now - the prerequisites to have OnAfterInitUnitTable triggered?

Any help appreciated.

EDIT: Sample code to reproduce:

program ExposeTest;

{$APPTYPE CONSOLE}

{$R *.res}

uses
    SysUtils, Classes, TypInfo,
    dwsRTTIExposer, dwsExprs, dwsComp;

type
    TScriptApplication = class(TPersistent)

    end;

    TTestClass = class(TThread)
    private
        FScript                  : IdwsProgram;
        FDelphiWebScript         : TDelphiWebScript;
        FUnit                    : TdwsUnit;
        FScriptApplication       : TScriptApplication;
        FSuccess                 : Boolean;
        procedure ExposeInstancesAfterInitTable(Sender: TObject);
    public
        constructor Create;
        destructor Destroy; override;
        procedure Execute; override;
    end;

var
    Test : TTestClass;


{ TTestClass }

constructor TTestClass.Create;
begin
    inherited Create(TRUE);
    FScriptApplication              := TScriptApplication.Create;
    FDelphiWebScript                := TDelphiWebScript.Create(nil);
    FUnit                           := TdwsUnit.Create(nil);
    FUnit.UnitName                  := 'Test';
    FUnit.Script                    := FDelphiWebScript;
    FUnit.ExposeRTTI(TypeInfo(TScriptApplication), [eoNoFreeOnCleanup]);
    FUnit.OnAfterInitUnitTable      := ExposeInstancesAfterInitTable;
end;

destructor TTestClass.Destroy;
begin
    FreeAndNil(FScriptApplication);
    FreeAndNil(FUnit);
    FreeAndNil(FDelphiWebScript);
    inherited;
end;

procedure TTestClass.Execute;
begin
    WriteLn('Test 1');
    FSuccess     := FALSE;
    FScript      := FDelphiWebScript.Compile('Unit Test; var I: Integer; I := 0;');
    if FSuccess then
        WriteLn('   Success')
    else
        WriteLn('   Failure');
    WriteLn('Test 2');
    FSuccess     := FALSE;
    FScript      := FDelphiWebScript.Compile('var I: Integer; I := 0;');
    if FSuccess then
        WriteLn('   Success')
    else
        WriteLn('   Failure');
    WriteLn('Test Done');
end;

procedure TTestClass.ExposeInstancesAfterInitTable(Sender: TObject);
begin
    FUnit.ExposeInstanceToUnit('Application', 'TScriptApplication', FScriptApplication);
    WriteLn('OnAfterInitUnitTable called');
    FSuccess     := TRUE;
end;

begin
    Test := TTestClass.Create;
    Test.Start;
    Sleep(1000);
    WriteLn('Hit enter to quit');
    ReadLn;
    Test.Free;
end.

EDIt2: Other version to show the new issue using suggestion by Eric Grange in answer 1 below;

program ExposeTest;

{$APPTYPE CONSOLE}

{$R *.res}

uses
    SysUtils, Classes, TypInfo,
    dwsRTTIExposer, dwsFunctions, dwsExprs, dwsComp;

type
    TScriptApplication = class(TPersistent)
    published
        procedure Demo;
    end;

    TTestClass = class(TThread)
    private
        FScript                  : IdwsProgram;
        FDelphiWebScript         : TDelphiWebScript;
        FUnit                    : TdwsUnit;
        FScriptApplication       : TScriptApplication;
        FSuccess                 : Boolean;
        procedure ExposeInstancesAfterInitTable(Sender: TObject);
        function NeedUnitHandler(const UnitName   : UnicodeString;
                                 var   UnitSource : UnicodeString): IdwsUnit;
    public
        constructor Create;
        destructor Destroy; override;
        procedure Execute; override;
    end;

var
    Test : TTestClass;


{ TTestClass }

constructor TTestClass.Create;
begin
    inherited Create(TRUE);
    FScriptApplication              := TScriptApplication.Create;
    FDelphiWebScript                := TDelphiWebScript.Create(nil);
    FDelphiWebScript.OnNeedUnit     := NeedUnitHandler;
    FUnit                           := TdwsUnit.Create(nil);
    FUnit.UnitName                  := 'Test';
    FUnit.Script                    := FDelphiWebScript;
    FUnit.ExposeRTTI(TypeInfo(TScriptApplication), [eoNoFreeOnCleanup]);
    FUnit.OnAfterInitUnitTable      := ExposeInstancesAfterInitTable;
end;

destructor TTestClass.Destroy;
begin
    FreeAndNil(FScriptApplication);
    FreeAndNil(FUnit);
    FreeAndNil(FDelphiWebScript);
    inherited;
end;

procedure TTestClass.Execute;
begin
    WriteLn('Test 1');
    FSuccess     := FALSE;
    FScript      := FDelphiWebScript.Compile('Unit Test; var I: Integer; I := 0;');
    WriteLn(FScript.Msgs.AsInfo);
    if FSuccess then
        WriteLn('   Success')
    else
        WriteLn('   Failure');
    WriteLn('Test 2');
    FSuccess     := FALSE;
    FScript      := FDelphiWebScript.Compile('uses Other;');
    WriteLn(FScript.Msgs.AsInfo);
    if FSuccess then
        WriteLn('   Success')
    else
        WriteLn('   Failure');
    WriteLn('Test Done');
end;

procedure TTestClass.ExposeInstancesAfterInitTable(Sender: TObject);
begin
    FUnit.ExposeInstanceToUnit('Application', 'TScriptApplication', FScriptApplication);
    WriteLn('OnAfterInitUnitTable called');
    FSuccess     := TRUE;
end;

function TTestClass.NeedUnitHandler(
    const UnitName   : UnicodeString;
    var   UnitSource : UnicodeString): IdwsUnit;
begin
    Result := nil;
    if SameText(UnitName, 'Other') then
    UnitSource := 'unit Other;' + #13#10 +
                  'procedure Func;' + #13#10 +
                  'begin' + #13#10 +
                  '  Application.Demo;' + #13#10 +
                  'end;' + #13#10
    else
        UnitSource := '';
end;

{ TScriptApplication }

procedure TScriptApplication.Demo;
begin

end;

begin
    Test := TTestClass.Create;
    Test.Start;
    Sleep(1000);
    WriteLn('Hit enter to quit');
    ReadLn;
    Test.Free;
end.
fpiette
  • 11,983
  • 1
  • 24
  • 46
  • It's still called for the test units that can be found in UdwsUnitTests & URTTIExposeTests. Would you have more details on reproducing the issue? – Eric Grange May 28 '13 at 09:20
  • Trying to write a simple sample, I found that it happens when you compile a script having 'unit' in the first line. For example, compiling "unit Test; var I : Integer; begin I := 0; end;" makes the OnAfterInitTable event not called. Remove "Unit Test;" from the line and suddently the event is called. – fpiette May 28 '13 at 12:40
  • Hmmm... the ExplicitUses test in UdwsUnitTests does something very close to that and its OnAfterInitUnitTable is called. Do you have StaticSymbols set to true? If so the event will be called only once. If it's False, it'll be called as each compilation. – Eric Grange May 28 '13 at 13:40
  • Changing StaticSymbols value doesn't change anything. – fpiette May 29 '13 at 13:44
  • Edited the question to add sample code to repoduce the issue. – fpiette May 29 '13 at 14:21
  • Thanks, I had misunderstood your issue. Posting answer. – Eric Grange May 31 '13 at 08:36

1 Answers1

0

When encountering a "unit" as main program, the compiler currently assumes it's just a compilation for IDE purposes, ie. to check for syntax errors, build a symbol map, provide suggestions, etc. and the resulting program isn't fully initialized as a consequence.

So if you want to compile the unit and make an executable program, you can have a main program that'll just be something like:

uses Test;

This will compile a program comprised of your unit, for which executions can be created and where functions can be called though exec.Info, classes can be instantiated, etc.

Edit2: For the second test case, it works if "uses Test;" is added. For full cross-compilability with Delphi, you'll also need interface/implementation sections (when targeting script only, they are not necessary)

unit Other;

interface

uses Test;

procedure Func;

implementation

procedure Func;
begin
  Application.Demo;
end;

and if RTTI is generated for the methods with the $RTTI directive, at least with

{$RTTI EXPLICIT METHODS([vcPublished])}
TScriptApplication = class(TPersistent)
published
    procedure Demo;
end;

otherwise you get an error about "Demo" not being found.

Eric Grange
  • 5,931
  • 1
  • 39
  • 61
  • Not sure I understand. Do you mean I have to compile a script having a single line "uses Test;" which will request the actual script in unit "Test". Unit Test can be a full blown source file starting with "unit Test;" and having a complete script? This was not the case with DWScript Preview 2.7 and it is annoying. Should I use keyword "program" instead of "unit"? – fpiette May 31 '13 at 14:45
  • If you unit is actually a program, and you don't intend to "uses" it anywhere else, you can just leave out the "unit" statement. I wasn't aware units could be used as programs back then :( – Eric Grange May 31 '13 at 15:02
  • This way doesn't work much better. ExposeInstancesAfterInitTable is called but the exposed object is not known by the compiler. I edited the original question to add a second code sample to show that new issue which triggers a compilation error saying the exposed object name is not known (In the demo, the object name is "Application"). – fpiette Jun 03 '13 at 07:04
  • Forgot to say that the ultimate goal is to have a script with a 100% compatible Delphi syntax so that it can be compiled by Delphi and linked in the application. – fpiette Jun 03 '13 at 07:30
  • Updated answer with a Delphi-compilable version of the "Other" unit – Eric Grange Jun 03 '13 at 15:26
  • My code was compilable with Delphi XE4. Maybe you used to test an old one? By the way, what have you changed? Have you reproduced the issue? – fpiette Jun 04 '13 at 13:05
  • I'm referring to the code of the "Other" unit (in TTestClass.NeedUnitHandler) which didn't have an interface/implementation, can XE4 compile that? Or did I misundertand your need about that unit being compilable in DWS and Delphi? The rest of the Delphi code I didn't change. I'm compiling in XE. – Eric Grange Jun 05 '13 at 07:26
  • I made a very small sample code to show DWScript bug, not to show how to achieve my final goal which is irrelevant here. You have not answered to the question: do you reproduce the issue? – fpiette Jun 06 '13 at 19:57
  • No I don't reproduce it, as I posted in the Edit2 of my answer, once I fix your "Other" unit by adding "uses Test" and added the explicit $RTTI directive to the Delphi program, it worked. – Eric Grange Jun 07 '13 at 09:56
  • Explicit $RTTI directive is not required as long as the ancestor is TPersitent or inherits from TPersistent. – fpiette Jun 11 '13 at 08:01