Orthoclaseに言語切り替え機能を付けたい
JScriptだけじゃなくて,VBScriptとか,PHPScript, RubyScript, PerlScript, Haskell Script etc.なんかも使えたら楽しそうじゃないか・・・?と思ったので,早速実装を始めた.
言語種別をソースコードから読み取るとなると,IActiveScriptParse::ParseScriptTextが呼び出されるまでスクリプトエンジンの起動を遅延させる必要がある.しかし,IActiveScriptParse::ParseScriptTextまでに幾つかの初期化コードがあるので,単純に遅らせることも出来ない.そこで,初期化シーケンスを実行せずに記録しておき,スクリプトエンジンの起動時に遅延的に評価するという方法を採ることにした.デザパタで言うとインタプリタパターンになるのだろう.
// 実行単位のインターフェース struct LimeExtInitOperation { virtual HRESULT Execute(IActiveScript *pengine, IActiveScriptParse* pparser, LimeExt *psite) = 0; }; // IActiveScriptEngine::SetScriptState class LimeExtSetScriptState : public LimeExtInitOperation { SCRIPTSTATE _ss; public: LimeExtSetScriptState(SCRIPTSTATE ss) : _ss(ss) {} HRESULT Execute(IActiveScript *pengine, IActiveScriptParse* pparser, LimeExt *psite) { return pengine->SetScriptState(_ss); } }; // LimeExt::SetScriptSite class LimeExtSetScriptSite : public LimeExtInitOperation { IActiveScriptSite *_pass; public: LimeExtSetScriptSite(IActiveScriptSite *pass) : _pass(pass) {} HRESULT Execute(IActiveScript *pengine, IActiveScriptParse* pparser, LimeExt *psite) { return psite->SetScriptSite(_pass); } }; // IActiveScriptEngine::AddNamedItem class LimeExtAddNamedItem : public LimeExtInitOperation { CStringW _name; DWORD _flags; public: LimeExtAddNamedItem(LPCOLESTR pstrName, DWORD dwFlags) : _name(pstrName), _flags(dwFlags) {} HRESULT Execute(IActiveScript *pengine, IActiveScriptParse* pparser, LimeExt *psite) { return pengine->AddNamedItem(_name, _flags); } }; // このようにして記録していく STDMETHODIMP SetScriptSite( /* [in] */ __RPC__in_opt IActiveScriptSite *pass) { if (!m_scriptSite) { CAutoPtr<LimeExtInitOperation> operation( new (std::nothrow) LimeExtSetScriptSite(pass)); if (!operation) return E_FAIL; m_initSequence.Add(operation); return S_OK; } return m_scriptSite->SetScriptSite(pass); }
ここまで実装したところで,JScriptとVBScriptの切り替えが出来ることが確認できた.なんか微妙に参照カウントの動きが1回分ずれてて怪しいんだけど,この際気にしないことにした.しかし,PHPScriptで実験してみると,動くには動くのだが,eventやらwindowやらLimeExtオブジェクトを認識してくれない.他のグローバルメンバも全てだ.ちょこちょこいじってみても原因がさっぱり分からず.
ソースコードが付いているRubyScriptなら何か分かるかも知れないと思い,公式サイトからダウンロードしてきた.公式はruby1.8+MSVC6を想定しているようなので,ruby1.9.1+MSVC8仕様にカスタマイズする必要があった.たとえば,forのカウンタ変数の扱いだとか,RArrayや幾つかのRuby変数周りとか.エンジンを登録してデバッグしてみると,原因が分かってきたような分かってこないようなそんな感じになってきた.どうやら,ActiveScriptSiteの保持に利用しているIGlobalInterfaceTable::GetInterfaceFromGlobalがE_UNEXPECTEDを返す所為で,AddNamedItemで登録した名前付きアイテムをOLEオブジェクトとしてスクリプトに見せる処理が失敗しているみたいだった.cscriptも同じコード実行の流れになるんだけど,こちらの場合はちゃんとS_OKになる.一体どういうことだ・・・
グーグル先生によれば,IGlobalInterfaceTable::GetInterfaceFromGlobalは登録時とは異なるアパートメントでインターフェースを受け取る場合,登録したインスタンスがマーシャリング可能(IMarshalを実装している)である必要があるらしい.しかし,LimeChatもcscriptも同じSTAで同じコードの流れなのに,一体どこで躓いているんだろう・・・? 大体今の実装じゃマーシャリング可能になんて出来ないしなぁ.