こんにちは、高木です。
前回はTcl_Interp型の最低限のラッパークラスを作りました。今回からそれをもとに機能を追加していくことにします。
最初にやらないといけないのは、新たなインタープリターの構築と後始末です。素直な設計であれば、interpreterクラスのコンストラクターで構築を行い、デストラクターで解体を行うことになります。しかし、interpreterクラスの典型的な用途は、ライブラリーから渡されたTcl_Interp型へのポインターをラップすることですので、そういった方法はとれません。代わりにcreateとdisposeという2つのメンバー関数を追加することにします。本当はdeleteという名前にしたいのですが、予約語なので使えませんから。
実際にコードを見ていく前に、Tclのインタープリターを構築する方法について紹介しておくことにします。Tclのインタープリターを構築するには主に2種類の方法があります。ひとつはマスターのインタープリターを構築するためのTcl_CreateInterp関数を使う方法であり、もうひとつはスレーブのインタープリターを構築するためのTcl_CreateSlave関数を使う方法です。
昨今ではマスターやスレーブという表現を嫌って親と子という表現を使うようにあらためたのか、Tcl_CrrateSlave関数にはTcl_CreateChildという別名があります。我々日本人にとってはマスターやスレーブという表現に差別的な意図はありませんので、伝統的な関数名であるTcl_CreateSlaveを使っていくことにします。
Tclではもともと複数のインタープリターを作ることができます。複数のインタープリターを作ることで擬似的な並列処理ができる(もちろんプリエンプティブではありません)ようになります。また、ユーザー定義のスクリプトを実行する機能を提供する際に、プログラム本体のインタープリターをユーザーに見せなくても済むので、適切に本体のインタープリターを保護することができます。
マスターのインタープリターを複数構築した場合、お互いほかのインタープリターは見えませんので互いに干渉することはできません。もちろん、C言語レベルのコードを書いて強引に干渉させることはできますが、Tcl言語の範囲では無理ということです。一方で、スレーブのインタープリターはマスターから見えますので、マスターで動くスクリプトからスレーブのインタープリターを呼び出すことができます。
また、スレーブのインタープリターは構築時にセーフモードを指定することができます。セーフモードを指定すれば、適切に機能を制限することで、ユーザースクリプトがうっかりシステムを破壊するようなことを避けることができます。マスターでもスレーブでも、あとからセーフモードに変更することはできます。
それでは今回のコードを見ていきましょう。
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
public: // マスターの構築 static interpreter create(bool tk = true) { interpreter interp(Tcl_CreateInterp()); if (!interp.initialize(tk)) throw std::runtime_error("tcl::interpreter::create"); return interp; } // スレーブの構築 interpreter create_slave(const char8_t* name, bool tk = true, bool safe = true) { interpreter interp(Tcl_CreateSlave(this->interp_, reinterpret_cast<const char*>(name), safe)); if (!interp.initialize(tk)) throw std::runtime_error("tcl::interpreter::create"); return interp; } // 解体 void dispose() { Tcl_DeleteInterp(this->interp_); Tcl_Release(this->interp_); this->interp_ = nullptr; } private: // マスターとスレーブに共通の初期化処理 bool initialize(bool tk) { preserve(); if (Tcl_Init(this->interp_) != TCL_OK) goto failure; if (tk) { if (Tk_Init(this->interp_) != TCL_OK) goto failure; } return true; failure: Tcl_DeleteInterp(this->interp_); release(); return false; } |
マスターを構築するcreateメンバー関数は静的ですが、create_slaveメンバー関数は非静的であることに注意してください。スレーブの構築にはマスターを指定する必要があるため、マスターのメンバー関数として呼び出すことになります。
また、構築と解体には前回作ったpreserveおよびreleaseメンバー関数を使っています。このあたりは定型句のようなものなので、ユーザーがいちいち自分で呼び出さなくてもいいようにしておきましょう。
また、createおよびcreate_slaveメンバー関数に共通の処理はinitializeメンバー関数としてまとめておきました。この中ではTcl_Init関数やTk_Init関数を呼び出しています。スクリプトの中で環境変数やコマンドライン引数を使うのであれば、initialize関数の中で展開しておく必要があります。
次回はいよいよスクリプトの評価部分を作っていくことにします。