ScriptGate
一個前の記事で ScriptGate という JavaScript と Delphi の相互呼び出しを実現するライブラリを説明しました。
今回は、そこに Eval を追加しました。
Eval
元々 ScriptGate では JavaScript の関数が呼べるため、以下のようにすれば JavaScript の式を評価できました。
FScriptGate.CallScript('eval("alert(\"test\")")');
これを動かしてみると… macOS, iOS, Android は正しく動作しましたが Windows だけ動作しませんでした。
Windows で何故動かないか
ScriptGate では呼び出した JavaScript の関数の戻値を取得できます。
そのため Windows では一般的な IHTMLWindow2.execScript を使っていません。
その代わり IHTMLDocument2.Script を使っています。
ここには関数名とパラメータを分けて渡す必要があるため、内部で TStringList.CommaText を使って引数をパーシングしました。
そのため、"" が付いているとパラメータを分けられず、実行が失敗していました。
Eval の追加
上記のように Windows だけ動作が変わってしまうため、Eval を作る事にし実装しました。
TScriptGate.Eval(
const iScript: String;
const iResultProc: TScriptGateResultProc);
第一引数は評価したい式、第二引数は式の戻り値を取得するハンドラです。
例えば、下記の様にすると HTML のソースが取得できます。
FScriptGate.Eval(
'document.getElementsByTagName("html")[0].outerHTML',
procedure (const iResult: String)
begin
ShowMessage(iResult); // HTML のソースが入っている
end
);
Windows で JavaScript 関数の戻り値を取る
Windows で JavaScript 関数の戻り値を取るのは非常に面倒くさい上にウェブに良い文献がありません(特に Delphi ユーザー向けには)。
そこで、Windows で JavaScript 関数の戻り値を取得する方法を簡単に解説します。
詳しくは、ソースを見て欲しいのですが、簡単な手順は下記の通りです。
1.関数名とパラメータを別々に用意する
例えば
<script>
function Foo(param1) {...}
Foo("bar");
</script>
というような関数なら "Foo" と渡す引数 "bar" を分解して別々に用意しておきます。
2.DispID の取得
後述の Scrint.Invoke では ID でオブジェクトを指定するため関数の ID が必要です。
Foo 関数の ID を取得するには、下記の様にします。
IHTMLDocument.Script.GetIdsOfNames(
GUID_NULL,
@Name, // 関数名へのポインタ、ここでは Name には 'Foo' が入っている
1,
LOCALE_SYSTEM_DEFAULT,
@DispID // 結果を受け取る DispID は Integer 型
)
これで、Foo 関数を ID で指定できるようになりました。
3.パラメータ用のメモリを確保
パラメータは TDispParams として渡します。
TDispParams.cArgs に引数の個数を指定し、TDispParams.rgvarg は cArgs * SizeOf(TVariantArg) 分メモリを確保します。
rgvarg は配列なので下記の様にループを回して値を入れていきます。
var
Params: TArray<OleVariant>;
begin
// Params に引数を入れるコードなど省略
// メモリを確保
GetMem(DispParams.rgvarg, DispParams.cArgs * SizeOf(TVariantArg));
// 値を代入
for i := 0 to High(Params) do
begin
DispParams.rgvarg[i].vt := varVariant or varByRef;
TVarData(DispParams.rgvarg[i]).VPointer := @Params[i];
end;
end;
ここで、注意したいのは渡す 引数の順序が逆、ということです。
引数の順序が逆です。
大切なのことなので、もう一度いいました。
例えば引数に Foo(1, 2); と渡す場合は 2, 1 と rgvarg に入れなければいけません。
これについては、サンプルコードの多くが1つの引数のみを扱っており、ネット上のどこにも書いていないため非常に苦労しました。
4.実行
あとは、関数名、引数、戻り値を受け取る OleVariant 型の変数を指定して Invoke するだけです。
if
Doc.Script.Invoke(
DispIDs, // Foo の ID
GUID_NULL,
0,
DISPATCH_METHOD,
DispParams, // パラメータ配列
@V, // 結果を受け取る OleVariant
@ExcepInfo, // 実行時の例外を受け取る EXECPINFO 構造体
nil)
= S_OK
then
CallResult(V); // OleVariant は String にそのまま代入可能
ざっくり説明すると、このような感じです。
まとめ
Android が一番の鬼門かと思いきや Windows もなかなか大変でした。