= Tcl/Tk のファイル群を tar でまとめた状態で実行可能にする方法
== はじめに
通常、Tcl/Tk を使用する場合、 lib フォルダ下に encodingファイル群、msgsファイル群、tzdataファイル群、tclスクリプト群、... その他の沢山のファイルを配置する必要があります。
Tcl/Tk を使った C or C++ アプリケーションを配布したい場合、exe ファイルだけでなく、これら Tcl/Tk のファイルも一緒に配布する必要があるので、
ちょっとしたアプリを作ってインストール、という使い方をしたい場合は、いまいち使いづらいかもしれません。
これら Tcl/Tk ファイル群が1つにまとまっていたら通常の dll ライブラリと同じ感覚で扱え、より手軽になるはずです。
Tcl/Tk のファイルを全てまとめて1つの実行ファイルにする方法として、 Starpack 等がありますが、そこまでやらなくても、tarやzip等の一般的なアーカイブ形式で
上記ファイル群をまとめておき、実行時に必要な部分だけ読み込みできるだけでも、かなり手軽になると思います。
さらに、一般的なアーカイブ形式の方が拡張の追加や不要な部分の削除等の操作が容易になるというメリットも生じます。
TclVfs 拡張を用いることで、tar、zip等のアーカイブファイルの中身やhttp、ftp等で通信可能なWebサイトを
任意のディレクトリにマウントできるようなので、本稿ではこれを活用して、Tcl/Tk のファイル群を tar にまとめて、
起動時にマウントして読み込む方法を紹介します。
== 方法
=== 準備
tar ファイルをマウントして、中に入っているスクリプト等を読み込むためには、 TclVfs 拡張と Memchan 拡張が必要です。
従って、まず、Tcl/Tk に この 2 つの拡張を導入します。
==== TclVfs拡張の導入(Visual Studio で自力ビルドする場合)
Visual Studioを使っている方で、TclVfs 拡張をソースからビルドして導入する方法は
[http://blogs.yahoo.co.jp/mocchi_2003/62803696.html TclTk + Incr Tcl + Incr Tk + Incr Widgets + TclVfs + Metakit を VisualC++ を使ってソースから導入するメモ]
が役に立つかもしれません。 Tcl/Tkのビルドからのやり方を記載しています。
==== Memchan 拡張の導入(Visual Studio で自力ビルドする場合)
1. まず、Memchan 拡張を次のURLからダウンロードします。
http://memchan.sourceforge.net/
2. 上記のメモで TclVfs 拡張をビルドする手順に合わせて、ソースファイル群のフォルダ構成を下記のようにします。
{{{
work
/ tcl8.5.10 / winとかunixとか...
/ tk8.5.10 / winとかunixとか...
/ tclvfs-20080503 / winとかmacとか...
/ Memchan2.3 / winとかmacとか...
}}}
3. work / Memchan2.3 / win / に、 [http://sourceforge.jp/downloads/users/2/2896/mk.bat mk.bat] を配置します。
4. VisualStudio2008コマンドプロンプトで work / Memchan2.3 / win / に cd で移動します。
5. Visual Studio や Memchan のバージョンがこの記事と異なる場合は、 mk.bat の先頭に書かれている環境変数の内容を適宜書き換えます。
6. mk.bat を実行して下さい。 ビルドが実施され、 work / inst フォルダの中に実行時に必要なファイル群が配置されます。
=== tarファイルの作成
inst フォルダの中に、tcltk 実行のために必要なファイル群が全て入っていることを確認したら、 inst / lib フォルダ下の中身を tar アーカイブにします。
tar ファイル作成の際、下記事項に注意してください。
* *.dll ファイル、 *.lib ファイルは tar の中に入れない。
*.dllは、tar の中に入ったままの状態でダイナミックリンクライブラリとして読み込むことはできないはずなので、tar アーカイブに入れても恩恵が得られません。
*.lib ファイルについては、ビルド時に使用するものなので実行時には不要です。
* tar の中の各ディレクトリ、ファイルの相対パスが lib フォルダの下から始まるようにする。
tar 化したファイルをバイナリエディタ等で覗いた時、表示されるファイル名が下記のようになっていることを確認して下さい。
{{{
dde1.3/
dde1.3/pkgIndex.tcl
Memchan2.3/
...
}}}
下記のようになっている場合はNGです。
{{{
lib/dde1.3/
lib/dde1.3/pkgIndex.tcl
lib/Memchan2.3/
...
}}}
=== Tcl/Tk を初期化する処理のコーディング
Tcl_Init 実行時に、 tar ファイルをマウントして、その中にある init.tcl ファイルを読みに行かせたいため、その前の段階で vfs::tar が動くような仕掛けを導入します。
tar ファイルをマウントするコマンド vfs::tar::Mount を動作させるためには、 tarvfs.tcl と vfsUtils.tcl を先に読み込む必要があるようなので、
これらを tar アーカイブから直接抽出します。
コーディング例は下記の通りです。tar ファイルに、 tk のデモも一緒に入れたと想定して、そのデモファイルを実行するところまで
記述してあります。他の拡張も導入したい場合は、Tcl_StaticPackage や ~_Init 関数を追記することになります。
下記の例のソースコードは [http://sourceforge.jp/downloads/users/2/2898/load.cpp こちらからダウンロード] することもできます。
著作権、ライセンス表示(Boost ver.1 ライセンス)を追加した以外は同一のものです。
{{{
#include "tcl.h"
#include "tk.h"
#include <string>
#include <cstdio>
extern "C" {
int Vfs_Init(Tcl_Interp *);
int Memchan_Init(Tcl_Interp *);
}
std::string read_vfs_script(const char *tarfile){
if (!tarfile) return "";
FILE *fp = std::fopen(tarfile, "rb");
if (!fp) return "";
std::string script;
char buf[513];
buf[512] = '\0';
int datablocks = 0;
long filesize = 0;
bool add_to_script = false;
while(!std::feof(fp)){
if (datablocks == 0){ // header
size_t sz = fread(buf, 512, 1, fp);
char typeflag = buf[156];
switch(typeflag){
case '1': case '2': case '3': case '4': case '5': case '6': case '7':
continue;
}
filesize = std::strtol(buf + 124, 0, 8);
datablocks = (filesize + 511) / 512;
add_to_script = (std::strstr(buf, "vfs1.3/tarvfs.tcl") != 0) || (std::strstr(buf, "vfs1.3/vfsUtils.tcl") != 0);
}else if (add_to_script){
size_t sz = std::fread(buf, 512, 1, fp);
--datablocks;
if (!add_to_script) continue;
if (filesize < 512) buf[filesize] = '\0';
script += buf;
filesize -= 512;
}else{
std::fseek(fp, datablocks * 512, SEEK_CUR);
datablocks = filesize = 0;
}
}
std::fclose(fp);
return script;
}
int main(int argc, char *argv[]){
Tcl_Interp *interp = ::Tcl_CreateInterp();
Tcl_FindExecutable(argv[0]);
int rc;
::Tcl_StaticPackage(interp, "Tcl", Tcl_Init, 0);
::Tcl_StaticPackage(interp, "Tk", Tk_Init, 0);
::Tcl_StaticPackage(interp, "vfs", Vfs_Init, 0);
::Tcl_StaticPackage(interp, "memchan", Memchan_Init, 0);
// 実行ファイルのディレクトリを取得
std::string exedir = argv[0];
exedir = exedir.substr(0, exedir.find_last_of('\\')+1);
for (size_t i = 0; i < exedir.size(); ++i) if (exedir[i] == '\\') exedir[i] = '/';
::Tcl_SetVar2(interp, "tcl_library", 0, (exedir + "lib/tcl8.5/").c_str(), TCL_GLOBAL_ONLY);
// tarファイル をマウントするために必要なモジュール群をロード
rc = ::Vfs_Init(interp);
rc = ::Memchan_Init(interp);
// tarファイル をマウントするために必要なスクリプトをtarファイルの中から見つけて取得
std::string scr = read_vfs_script((exedir + "tcl_rt.tar").c_str());
rc = ::Tcl_Eval(interp, scr.c_str());
// tarファイル をマウント
rc = ::Tcl_Eval(interp, "package require vfs::tar;");
rc = ::Tcl_Eval(interp, "vfs::tar::Mount $tcl_library/../../tcl_rt.tar $tcl_library/../;");
// Tcl、Tk の初期化
rc = ::Tcl_Init(interp);
rc = ::Tk_Init(interp);
// デモを動かす
Tcl_Eval(interp, "source $tcl_library/../tk8.5/demos/widget;");
Tk_MainLoop();
return 0;
}
}}}
== 実行可能ファイル群の配置
上記ソースをビルドした結果作成される exe と、 tar ファイルを同一のフォルダに配置します。
また、vfs 拡張や memchan 拡張の Cソースをビルドした部分を dll として扱いたい場合は、先ほど tar 化する際に除いた lib フォルダ下の各 dll ファイルも、 exe ファイルと同じディレクトリに配置して下さい。
== 参考サイト
* [http://www.geocities.jp/urano343/dir4/tapi23.html C言語からTcl言語の解釈機能やTcl APIを使う] - なもなも 様
[http://www.geocities.jp/urano343/dir4/namo.html なもなも 様 のWebサイト]は、 Tcl/Tk を C or C++ から呼ぶプログラムを作成する際、大変お世話になっております。
* [http://www.redout.net/data/tar.html tar の構造] - インターネットの孤島 様
tar を直接読む方法 (read_vfs_script 関数) のコーディングで参考になりました。