Tclのラッパーライブラリ(インタープリタ編)
高木です。おはようございます。
複数の連載とそれ以外の投稿を同時並行でやっているので、どれがどれか分からなくなってしまいそうです。
今回は連載「Tclのラッパーライブラリ」に関する投稿です。
前回は前提編ということで、ライブラリ開発の基本的な方針について書きました。
今回から実際のライブラリの中身についての話に入ります。
具体的な話に入る前に、現時点で入手可能な有益な情報を紹介していきましょう。
以後の内容もそれらの情報を踏まえて進めていくことになります。
まずは書籍からです。
『Tcl&Tkツールキット 』JohnK. Ousterhout著
Tcl/Tkの開発者自身による著書で、内容は古いですが現在でも十分役に立ちます。
若干バージョンは異なるかもしれませんが、原文のPDFがネット上で入手できます(あえてリンクは貼りません)。
次はWebサイトです。
Tcl8.6.9/Tk8.6.9 Documentation
本家本元の公式ドキュメントです。
APIのリファレンスになるので、これ無しでは何も話が進みません。
AM02:50 Tcl/Tk Scripting Laboratory
ジオシティーズのサービス終了によって失われてしまったサイトですが、アーカイブを閲覧することは可能です。
このサイトの「なもなも・拡張分室」はこのテーマを扱う上では必見です。
それでは本題に入っていきます。
最初に考えないと行けないのは、Tcl C APIの一番基本となるTcl_Interpの扱いについてです。
Tcl_Interpをどう扱うかによって、ほかの要素の扱いも大幅に影響を受けます。
Tcl_InterpへのポインタがTclのインタープリタを扱うためのハンドルとなります。
インタープリタの構築と解体は、Tcl_CreateInterp関数およびTcl_DeleteInterp関数を用いて行います。
ただ、実際にはもう少し複雑なことをやらないといけません。
公式ドキュメントに目を通すと、インタープリタの構築と解体の際には、Tcl_Preserve関数とTcl_Release関数を用いて保護しなければならないようです。
また、インタープリタを初期化する際には、Tcl_Init関数を呼び出す必要がありますし、Tkを使うならTk_Init関数も呼び出さなければなりません。
それらの一連の初期化に先立って、Tcl_FindExecutable関数を呼び出す必要もあるでしょう。
そうしたTcl_Interpにまつわる作法は、ほとんどがワンパターンでライブラリの中に閉じ込めてしまう方がいいはずです。
そして、いちいちインタープリタの存在を意識しなくてもいいほうが楽に扱うことができます。
しかし、もし複数のインタープリタを扱うことになったときには、現在作成しているライブラリ自体が使い物にならなくなってしまいます。
落とし所として、Tcl_Interpの簡易的なラッパークラスを用意して、それを介してインタープリタの操作を行うことにします。
Tcl_Preserve/Tcl_Release両関数の呼出しまで隠蔽することはしません。
ましてや、公司虎歌でのTcl_CreateInterp関数の呼出ややデストラクタでのTcl_DeleteInterp関数の呼出しも行いません。
詳細なメンバーは別として、Tcl_Interpのラッパークラスは次のようにしたいと思います。
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 | namespace tcl { class interp { public: interp(Tcl_Interp* handle) : handle_(handle) { } interp(interp const& src) = default; interp(interp&& src) = default; ~interp() { this->handle_ = nullptr; } interp& operaror=(interp const& src) = default; interp& operator=(interp&& src) = default; Tcl_Interp* get() const { return this->handle_; } private: Tcl_Interp* handle_; }; } |
何のひねりもない素直すぎる設計です。
そして、このクラスにpublicな静的メンバー関数をひとつ追加し、いつでもどこでも生のインタープリタを取り出せるようにしたいと思います。
グローバル変数的に扱えるようにすることには嫌悪感を抱く人も少なくないかもしれませんが、いろいろ考えた結果これが一番だという結論に達しました。
1 2 3 4 5 | static Tcl_Interp* global_handle() { static Tcl_Interp* handle = Tcl_CreateInterp(); return handle; } |
本当はthread_localを使って、そのスレッドが終了する際にTcl_DeleteInterp関数やTcl_Release関数を呼び出すようにしたかったのですが、少なくともMinGW-w64ではうまく動かないようなのでやめました。