Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2022年04月13日] [更新 2022年04月13日] Delphi 一般・その他
   
FastScript を使用してアプリにスクリプト機能を実装
動作確認等 Windows 10 ビルド 19042 + Delphi XE5(UP2) Pro VCL-32
[整理番号 - 1120]







FastScript を使用してアプリにスクリプト機能を実装


FastScript の単純なサンプルです.Delphi XE5 Pro 版には Fast Report がバンドルされています.Fast Report には FastScript が実装されています.
FastScript を使用すると,自アプリにスクリプトの実行機能を実装することができます.スクリプトのコードは,実行時に FastScript ライブラリが独自にコンパイルして実行します.その機能は,実アプリをコンパイルする時に EXE に組み込まれます.

Fast Report のインストール先の Doc フォルダに PDF のマニュアルがありますが,主に Fast Report で利用する時の説明になっています.古いパーションのものですが,以下に PDF のマニュアルがあります.



本ページのサンプルでスクリプトを開始すると,下図の,未登録バージョンであるというダイアログが現れます.バンドル版の Fast Report に実装されている FastScript は試用版のようです.



図1
スクリプト実行開始時のダイアログ
  • 試用版のようだ

このようなスクリプトのライブラリは他にもあります.VBScript は,VB, つまり Visual Basic ライクなコードを実行中の EXE から実行する機能です.一方,PPA や FastScript は,文法的に Delphi と同じようなコードを実行します.
S cript のライブラリによっては,DLL を使用する場合があります.そのような Script ライブラリでは,実行環境にその DLL が必要です.



[備考]
スクリプト (Script) の語源はラテン語の「書かれたもの」で,直訳すれば「台本」てす.プログラムの世界ではプログラムコードの総称ですが,一般的には,コードをインタプリタ方式で実行するコード全体を示す意味で使われています.そのような機能のプログラム言語をスクリプト言語とも言います.
JavaScript は代表的なスクリプト言語と言えます.





01_ShowMessage でダイアログを表示するだけのサンプル


ShowMessage 手続きを実行する例です.
このサンプルでは,スクリプトのコードを,スクリプトを実行する側,つまり,実アプリである Delph のコード内に書いています.
FastScript を使用するには,uses に fs_ipascal と fs_iinterpreter を追加して,TfsScript の変数を宣言してそのインスタンスを生成します. 正規版ては,FastScript がコンポーネントパレットに登録されるようですが,確認はしていません.



図2
ShowMessage を実行するコード
  • begin で始まり end. で終了
  • フォームを使用しないコード (.dpr) と同じ構造

ダイアログを閉じてスクリプトのコードを変更してみます.そして Button1 をクリックします.下図のように実行結果が変わります.



図3
実行時にコードを書き換えて再実行
  • 当然,結果も変わる

リスト1
ShowMessage でダイアログを表示
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
  fs_ipascal, fs_iinterpreter;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    FScript : TfsScript;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

var
  FForm : TForm1;

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  fScript := TfsScript.Create(Self);
  // FastScript を Delphi で使用するには SyntaxType を PascalScript にする
  FScript.SyntaxType := 'PascalScript';
  // Delphi の標準の型と関数を使用するための設定 (全てが使用可能になるわけではない)
  FScript.Parent := fsGlobalUnit;

  Memo1.Lines.Clear;
  Memo1.Lines.Add('begin');
  Memo1.Lines.Add('  ShowMessage(''Hello !!'');');
  Memo1.Lines.Add('end.');  // 最後は . (ピリオド)
end;

//=============================================================================
//  スクリプトのコードをコンパイルして実行
//  スクリプトのコードは TMemo にセット済みとする
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  FScript.Clear;
  FScript.Lines.Text := Memo1.Lines.Text;

  if FScript.Compile then begin
    FScript.Execute;
  end;
end;

end.




02_FastScript でフォームを表示して TButton を追加


スクリプトで TForm のインスタンスを生成するサンプルです.生成したフォームに TButton も追加して<
その OnClick イベントも実装してみました.実アプリの Button1 をクリックするとフォームを表示します.そのフォームの TButton をクリックするとフォームの背景色が変わります.
このスクリプトのコードを code.pas の名前で保存して,実行時に Memo1 (TMemo) に読み込んで使用しています.

スクリプトのメインルートンは begin で始まり end. で終了です.begin, end. 内で使用する手続きや関数は,begin よりも上に記述します.これは,Delphi のプロジェクトのソースコード (dpr のコード) と同様の記述方法です.



リスト2
フォームを表示して TButton も追加する FastScript のコード
var
  LForm : TForm;
  LBtn  : TButton;
  LBmp  : TBitmap;

procedure OnFormShow(Sender: TObject);
begin
  LForm.Left := Form1.Left + 150;
  LForm.Top  := Form1.Top - 150;
end;

procedure OnFormPaint(Sender: TObject);
begin
  LForm.Canvas.Draw(120, 20, LBmp);
end;

procedure OnBtnClick(Sender: TObject);
begin
  LForm.Color := clGreen;
end;

begin
  LForm := TForm.Create(nil);
  try
    LForm.OnShow  := @OnFormShow;
    LForm.OnPaint := @OnFormPaint;

    LBtn := TButton.Create(LForm);
    LBtn.Parent  := LForm;
    LBtn.Left    := 20;
    LBtn.Top     := 20;
    LBtn.Caption := 'Button1';
    LBtn.OnClick := @OnBtnClick;

    LBmp := TBitmap.Create;
    LBmp.LoadFromFile('smail_27.bmp');

    LForm.ShowModal;
  finally
    LForm.Free;
    LBmp.Free;
  end;
end.


スクリプトの実行結果です.



図4
[Butto1] クリックでスクリプト実行
  • フォームを生成して表示する
  • そのフォームに画像を描画
  • 画像はフォームの OnPaint イベント内で描画
図5
表示したフォームのボタンをクリック
  • フォームの背景色が変わる

Delphi のクラス等を FastScript で使用するには,関連するクラス処理用のユニット名を uses に追加します.ユニットを追加しても,それらのクラスのプロパティやメソッドの全てが利用可能になるわけではありません.



リスト3
フォームを表示するスクリプトを実行する側の Delphi のコード
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
  fs_ipascal, fs_iinterpreter;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
    FScript : TfsScript;
  end;

var
  Form1: TForm1;

implementation

uses
  // ユニット (.pas または .dcu) は Delphi XE5 Pro の場合,以下にある
  // C:\Program Files (x86)\FastReports\LibD19
  fs_icpp,
  fs_ibasic,
  fs_ijs,
  fs_iclassesrtti,
  fs_igraphicsrtti,
  fs_iformsrtti,
  fs_iextctrlsrtti,
  fs_idialogsrtti,
  fs_idbrtti,
  fs_idbctrlsrtti,
  fs_ibdertti,
  fs_iadortti,
  fs_imenusrtti;

{$R *.DFM}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  fScript := TfsScript.Create(Self);
  FScript.SyntaxType := 'PascalScript';
  FScript.Parent    := fsGlobalUnit;

  Memo1.Lines.LoadFromFile('code.pas');
end;

//=============================================================================
//  スクリプトのコードをコンパイルして実行
//  スクリプトのコードは TMemo にセット済みとする
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
begin
  // これがないと実行時のスクリプトのコードの変更が反映されない
  FScript.Clear;

  // スクリプト側で Form1 のプロパティを使用可能にする
  // uses に fs_iformsrtti が必ず必要
  FScript.AddComponent(Form1);

  // スクリプトのコードをセット
  FScript.Lines.Text := Memo1.Lines.Text;

  // FScript.Run ( = コンパイル + 実行) も使用可能
  if FScript.Compile then begin
    FScript.Execute;
  end;
end;

end.




03_FastScript に外部の関数を実行する機能を追加


スクリプトで Windows API の関数類や Delphi の手続き等を実行する際の参考です.
前項のサンプルと違うのは赤の下線の 1 行です.これは,実アプリの RandomColor という関数を実行します.
code.pas の名前でこのスクリプトのコードを保存しています.そのファイルを実行時に Memo1 (TMemo) に読み込んで使用しています.



リスト4
Windows API の関数や Delphi の手続きを実行する FastScript のコード
var
  LForm : TForm;
  LBtn  : TButton;
  LBmp  : TBitmap;

procedure OnFormShow(Sender: TObject);
begin
  LForm.Left := Form1.Left + 150;
  LForm.Top  := Form1.Top - 150;
end;

procedure OnFormPaint(Sender: TObject);
begin
  LForm.Canvas.Draw(120, 20, LBmp);
end;

procedure OnBtnClick(Sender: TObject);
begin
  LForm.Color := RandomColor(255);
end;

begin
  LForm := TForm.Create(nil);
  try
    LForm.OnShow  := @OnFormShow;
    LForm.OnPaint := @OnFormPaint;

    LBtn := TButton.Create(LForm);
    LBtn.Parent  := LForm;
    LBtn.Left    := 20;
    LBtn.Top     := 20;
    LBtn.Caption := 'Button1';
    LBtn.OnClick := @OnBtnClick;

    LBmp := TBitmap.Create;
    LBmp.LoadFromFile('smail_27.bmp');

    LForm.ShowModal;
  finally
    LForm.Free;
    LBmp.Free;
  end;
end.


スクリプトを実行するとフォームを表示します.そのフォームの Button1 をクリックすると,実アプリのメソッドが実行されます.その結果,Button1 をクリックする度にフォームの背景色が変わります.
スクリプトに登録した関数を実行することは,実アプリのメソッドを実行することです.これは,スクリプトから実アプリを操作できることを意味しています.



図6
[Butto1] クリックでスクリプト実行
  • そのフォームに画像を描画
  • 画像はフォームの OnPaint イベント内で描画
図7
表示したフォームのボタンをクリック
  • RandomColor という関数が実行されてフォームの背景色が変わる
  • ボタンをクリック度に色が変わる
  • RandomColor 関数の実行コードは,実アプリ側にある


スクリプトのコード側では Windows API の関数類や実アプリのメソッドは使用できません.それらを使用するには実アプリ側に,処理する機能を実装します.参考リンクの記事にあるように, TfsCallMethodEvent 型のメソッドを実装して,そのメソッド内で処理します.そして,関数名をスクリプトに登録します.前項のサンプルにその機能を追加しています.


type
  TfsCallMethodEvent = function(Instance: TObject; ClassType: TClass;
    const MethodName: String; var Params: Variant): Variant of object;


リスト5
Windows API の関数や Delphi の手続きを実行するスクリプトを実行する Delphi 側のコード
unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
  fs_ipascal, fs_iinterpreter;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
    function AddFunc(Instance: TObject; ClassType: TClass;
      const MethodName: string; var Params: Variant): Variant;
  public
    { Public 宣言 }
    FScript : TfsScript;
  end;

var
  Form1: TForm1;

implementation

uses
  // ユニット (.pas または .dcu) は Delphi XE5 Pro の場合,以下にある
  // C:\Program Files (x86)\FastReports\LibD19
  fs_icpp,
  fs_ibasic,
  fs_ijs,
  fs_iclassesrtti,
  fs_igraphicsrtti,
  fs_iformsrtti,
  fs_iextctrlsrtti,
  fs_idialogsrtti,
  fs_idbrtti,
  fs_idbctrlsrtti,
  fs_ibdertti,
  fs_iadortti,
  fs_imenusrtti;

{$R *.DFM}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  fScript := TfsScript.Create(Self);
  FScript.SyntaxType := 'PascalScript';
  FScript.Parent    := fsGlobalUnit;

  Memo1.Lines.LoadFromFile('code.pas');
end;

//=============================================================================
//  スクリプトのコードをコンパイルして実行
//  スクリプトのコードは TMemo にセット済みとする
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
   LLL : TfsCallMethodEvent;
begin
  // これがないと実行時のスクリプトのコードの変更が反映されない
  FScript.Clear;

  // スクリプト側で Form1 のプロパティを使用可能にする
  // uses に fs_iformsrtti が必ず必要
  FScript.AddComponent(Form1);

  // スクリプトに RandomColor という関数を登録
  // 関数が実行された時の処理は AddFunc メソッドが行う
  // 登録する関数の数,引数の数と型は自由だがスクリプト側で使用可能な型に限る
  // 引数は参照渡しも可能.引数は必ず 1 つは必要  
  // procedure (戻り値なし) も登録可能  

  FScript.AddMethod('function RandomColor(AByte: Byte): Cardinal', AddFunc);

  // スクリプトのコードをセット
  FScript.Lines.Text := Memo1.Lines.Text;

  // FScript.Run ( = コンパイル + 実行) も使用可能
  if FScript.Compile then begin
    FScript.Execute;
  end;
end;

//-----------------------------------------------------------------------------
//  TfsCallMethodEvent 型のメソッドの実装部
//  引数は Variant 型の 1 つだけだが Params[0], Params[1], ... で取得できる
//  関数名とパラメータの数と型は TfsScript.AddMethod を記述する時に決める
//  この AddFunc メソッドで処理する関数を複数登録した場合,
//  どの関数が実行されたかは MethodName で判定する
//  
//  実アプリ側での処理なので,RGB 等の Windows API の関数類も使用可能
//-----------------------------------------------------------------------------
function TForm1.AddFunc(Instance: TObject; ClassType: TClass;
  const MethodName: string; var Params: Variant): Variant;
var
  LByte : Byte;  
begin
  if SameText(MethodName, 'RandomColor') then begin
    LByte  := Params[0];
    Result := RGB(Random(LByte), Random(LByte), Random(LByte));
  end;
end;

end.