Pascal Script はフリーだけどちょっと面倒。FastScript は有料だけど、使いやすい。
と言う結論が出たままそのままになっていましたが、ようやく作業に取りかかり。実アプリのクラスへのアクセスでいろいろと面倒だけど、これも最初だけなんで、Pascal Script を使うことにしました。
一応うまいこといきましたが、64bit プラットフォームでエラーになる。
AddFunction で戻り値が文字列の場合と RegisterPropertyHelper で登録したローカル手続きへのアクセスでエラーになります。
バグフィックスを待ってられないので、FastScript を採用することにしました。残念ながら Pascal Script 不採用となったわけですが、数日さわってみて覚えたことなどを FastScript も含めて簡単に書いてみました。
Pascal Script
スクリプトは Pascal のみサポートだが、Delphi Prism を作ってるだけあってかなり気合いが入っており、クラスを作ったりできる。伝統的に実アプリへのアクセスはコンパイル時と実行時の二回設定する必要があったりするので使い勝手はあまりよくない。
procedure TForm1.RunBtnClick(Sender: TObject); var a: integer; begin PSScript.Script.Text := Memo1.Text; if PSScript.Compile then begin MessageListBox.Items.Add('Success compiled'); if PSScript.Execute then begin MessageListBox.Items.Add('Success executed'); end else begin MessageListBox.Items.Add('Error: ' + PSScript.ExecErrorToString); end; end else begin for a := 0 to PSScript.CompilerMessageCount - 1 do MessageListBox.Items.Add(PSScript.CompilerMessages[a].MessageToString); end; end; // このイベントでクラス等を登録する。 procedure TForm1.PSScriptCompImport(Sender: TObject; x: TPSPascalCompiler); var ctc: TPSCompileTimeClass; begin // スクリプトに定数を追加する。 x.AddConstantN('kTest', 'integer').Value.ts32 := 1023; // スクリプトに列挙型を追加する。 x.AddTypeS('TEnum', '(eA, eB)'); // スクリプトに集合型を追加する。 x.AddTypeS('TSets', 'set of TEnum'); // スクリプトにレコード型を追加する。 x.AddTypeS('TRec', 'record x,y: integer; end;'); // スクリプトにメソッドポインタを追加する。'of object' は不要 // 未確認 // スクリプトにクラスを追加する。 ctc := x.AddClass(x.FindClass('TOBJECT'), TData); with ctc do begin RegisterMethod('Constructor Create'); RegisterMethod('function GetValue : integer'); // スクリプトにクラスのプロパティを追加する。 RegisterProperty('fValue', 'integer', iptrw); RegisterProperty('Value', 'integer', iptrw); RegisterProperty('Value2', 'integer', iptrw); end; with x.AddClassN(x.FindClass('TBUTTONCONTROL'), 'TButton') do begin // スクリプトにクラスのプロパティを追加する。 // 実際は SIRegisterTBUTTON(x); を呼び出すと主要なプロパティは全部登録してくれる。 RegisterProperty('CAPTION', 'String', iptrw); RegisterProperty('ONCLICK', 'TNotifyEvent', iptrw); end; end; // 実アプリの変数や関数の登録。 procedure TForm1.PSScriptCompile(Sender: TPSScript); begin // スクリプトにグローバル変数を追加する。 PSScript.AddRegisteredVariable('gA', 'integer'); PSScript.AddRegisteredVariable('Button', 'TButton'); // 実アプリの変数をグローバル変数としてスクリプトから利用できるようにする。 PSScript.AddRegisteredPTRVariable('gB', 'integer'); // 実アプリの手続きあるいは関数をスクリプトから利用できるようにする。 PSScript.AddFunction(@TestFunction, 'function TestFunction(a,b: integer) : integer;'); PSScript.AddFunction(@GetString, 'function GetString : string;'); // 実アプリのメソッドを手続きあるいは関数としてスクリプトから利用できるようにする。 PSScript.AddMethod(Self, @TForm1.OutputPS, 'procedure ShowMessage(const s: string);'); PSScript.AddMethod(Self, @TForm1.GetDataPS, 'function GetData : TData'); end; // 実行時前に発生するイベント。クラスのメソッドやフィールドへのアクセス手段を設定。 procedure TForm1.PSScriptExecImport(Sender: TObject; se: TPSExec; x: TPSRuntimeClassImporter); procedure TDatafValue_W(Self: TData; const T: integer); begin Self.fValue := T; end; procedure TDatafValue_R(Self: TData; var T: integer); begin T := Self.fValue; end; procedure TDataValue2_W(Self: TObject; const T: integer); var propInfo: PPropInfo; begin if Self is TData then begin TData(Self).Value := T; end; end; procedure TDataValue2_R(Self: TData; var T: integer); begin T := Self.fValue; end; begin // クラスのプロパティにアクセス出来るようにする。 with x.Add(TData) do begin RegisterConstructor(@TData.Create, 'Create'); RegisterMethod (@TData.GetValue, 'GetValue'); RegisterPropertyHelper(@TDatafValue_R, @TDatafValue_W, 'fValue'); // RegisterPropertyHelper(@TDataValue_R, @TDataValue_W, 'Value'); // published は RTTI があるので不要。 RegisterPropertyHelper(@TDataValue2_R, @TDataValue2_W, 'Value2'); end; x.Add(TButton); end; // 実アプリの変数等のセット。 procedure TForm1.PSScriptExecute(Sender: TPSScript); begin PSScript.SetVarToInstance('Button', TestButton); PSScript.SetPointerToData('gB', @fB, PSScript.FindBaseType(bts32)); end;
FastScript
Pascal, C++, JScript, Basic をサポートし、動作が速いのが特徴です。実アプリの関数の呼び出しは出来ず、メソッドのみでパラメータはソースの 'Data_GetValue' の形式で固定になっています。
スクリプトの言語仕様は複数言語をサポートする関係でかなり割り切っており、type, record, class, set, as, is などが使用不可。
その他制限事項としては、実アプリの変数にアクセス不可。タイプキャスト不可。
procedure TForm1.TestButtonClick(Sender: TObject); var fsc: TfsClassVariable; begin fsScript.Clear; fsScript.Parent := fsGlobalUnit; // スクリプトに定数を追加する。 fsScript.AddConst('kTest', 'integer', 1024); // スクリプトにグローバル変数を追加する。 fsScript.AddVariable('gVar', 'integer', 0); // スクリプトに列挙型を追加する。 fsScript.AddEnum('TEnum', 'eA, eB'); // スクリプトに集合型を追加する。 fsScript.AddEnumSet('TSets', 'eA, eB'); // スクリプトにレコード型を追加する。 // 未サポート // スクリプトにクラスを追加する。 fsc := fsScript.AddClass(TData, 'TObject'); with fsc do begin AddConstructor('constructor Create', Data_Create); AddMethod ('function GetValue : integer', Data_GetValue); // スクリプトにクラスのプロパティを追加する。 AddProperty('fValue', 'integer', GetProp, SetProp); AddProperty('Value2', 'integer', GetProp, SetProp); end; // スクリプトにオブジェクトを登録する。 fsScript.AddObject('Button', TestButton); fsScript.Lines.Assign(Memo1.Text); if fsScript.Compile then begin fsScript.Execute; end else begin Memo2.Lines.Add(Format('%s %s', [fsScript.ErrorMsg, fsScript.ErrorPos])); end; end; function TForm1.Data_GetValue(Instance: TObject; ClassType: TClass; const MethodName: string; var Params: variant) : variant; begin Result := TData(Instance).fValue1; end; function TForm1.GetProp(Instance: TObject; ClassType: TClass; const PropName: String): Variant; begin Result := 0; if PropName = 'FVALUE' then Result := TData(Instance).fValue1 else if PropName = 'VALUE2' then Result := TData(Instance).fValue1 end; procedure TForm1.SetProp(Instance: TObject; ClassType: TClass; const PropName: String; Value: Variant); begin if PropName = 'FVALUE' then TData(Instance).fValue1 := Value else if PropName = 'VALUE2' then TData(Instance).fValue1 := Value; end;
0 件のコメント:
コメントを投稿