自前のC++オブジェクトをjscriptで操作させたい

単純にCOMオートメーションサーバーを作成すれば事足ります。コードの再利用という点から考えてみてもそれがいいのですが・・・、一つのプログラム内で完結させたいといった時に調べてみました。

たとえば、自作の複雑なオブジェクトを持つアプリケーションがあって、jscriptなどのスクリプトでプラグインを書いてもらう、というようなケースなどでは、スクリプト環境をホストしなければなりません。

完全に自分用のメモで(^^;、 ネットなどで検索をしたり、MSDNドキュメントを拾い読みなどをまとめた備忘録です。あしからず。

で、やはりCOMのお世話にならないといけません。Visual Studio Express Editionでは全部自分でコーディングしないといけないので、退屈なコードを書かないといけないところが面倒です・・・。

手順としては、

  1. スクリプトから操作させたいオブジェクトをIDispatch を継承、もしくは、オブジェクトへのラッパークラスをIDispatch実装クラスでインプリメントする。
  2. スクリプトをホストするため(スクリプトからの様々な通知を受け取るため?)に必要なIActiveScriptSite の 実装クラスを作成

で、これらを使って、以下を順番に実行。

  1. CoCreateInstance APIでIActiveScriptのインスタンスを生成
  2. IActiteScript::SetScriptSite()へIActiveScriptSiteの実装クラスのインスタンスをnewして放り込む。
  3. IActiveScript::QueryInterface()でIActiveScriptParse インターフェイスポインタ(IID_IActiveScriptParse)を得る。
  4. IActiveScriptParse::InitNew()をコール
  5. IActiveScript::AddNamedItem()で、自前のC++オブジェクトの名前をつける。スクリプトではこの名前を使ってアクセスさせる。
  6. IActiveScriptParse::ParseScriptText()でスクリプト文を解析させる
  7. IActiveScript::SetScriptState(SCRIPTSTATE_CONNECTED)をコール
  8. IActiveScript::SetScriptState(SCRIPTSTATE_CLOSED)をコール
  9. インターフェイスポインタの後始末

という流れ。

まず、操作させたい自前のC++オブジェクトは、IDispatchインターフェイスを実装しなくてはいけません。C++とjscriptなどのスクリプトとの連携にはIDispatchインターフェイスが必ず絡んできます。

オブジェクトとIActiveScriptSiteの実装クラス

オブジェクトの仕様

  • オブジェクトは、ただ一つのパブリックプロパティ、Text を持つ。
  • オブジェクトは、ただ一つのパブリックメソッド、Warn() を持つ。
    メソッド・Warn()は、一つの文字列引数を持ち、Win32 APIの MessageBox() をコールしダイアログを表示する。
  • オブジェクトは、スクリプト内で Message という名前でグローバル変数として公開する。

上記様なオブジェクトをC++クラスで作成します。当然ながら、IDispatchを継承させます。あくまでテンプレート(スケルトン)的な備忘録が目的なので、主要な部分のみコードを起こしていくことにします。 以下のような感じです。

※コードについては参考程度のものです。無いと思いますがそのままコピペしての使用はやめた方がいいです。

IActiveScriptSiteの実装クラス

これも必要最小限のものだけ実装します。。。といいつつ、楽をしたいのでスクリプトエラーとか無視して、とりあえずは、ほとんど骨組みだけのスケルトン状態で。テスト目的では問題なしです。

手抜きですが、必要な実装は済みました。あとはこれを使ってスクリプトをホストするだけ。

スクリプト環境をホストする

この流れは上に書いたとおりにしていくだけで、以下のようなコーディングです。エラーチェックは必要最小限にしています。各インターフェイスメソッドの返値をチェックしてエラービットが立っていたら問答無用でクリーンナップコードブロックへ飛ばしています。

これでほとんど完成。

以下のようなスクリプトコードを上記関数に読み込ませてテスト。
うまく行きました。

実際組んで見ると、手順さえ分かれば、意外と簡単です。
IDispach::GetIDsOfNames/IDispatch::Invokeの実装が殆どでして、オブジェクトのプロパティ、メソッドの定義と実装は、BSTR,VARIANT,LONGといった型を使わないといけない・・・ということさえクリアすれば大して難しいことはないと思います。