Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

最近の作業部屋活動履歴

2023-04-16
2023-03-24
2023-03-22
2023-01-25

最近のWikiの更新 (Recent Changes)

2023-04-16
2023-03-24
2023-01-25
2023-01-08
2023-01-07

Wikiガイド(Guide)

サイドバー (Side Bar)

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 拡張をソースからビルドして導入する方法は TclTk + Incr Tcl + Incr Tk + Incr Widgets + TclVfs + Metakit を VisualC++ を使ってソースから導入するメモ が役に立つかもしれません。 Tcl/Tkのビルドからのやり方を記載しています。

('13/12/18 時点でダウンロードできる最新版 tclvfs-20080503 は、TclTk 8.5.10、8.5.11 でビルドOK、8.5.14、8.5.15でビルドNGを確認しました。 対策は、 VS2008でTclVFSをtcl.8.5.15に対応させる方法をご参照下さい。)

Memchan 拡張の導入(Visual Studio で自力ビルドする場合)

  1. まず、Memchan 拡張を次のURLからダウンロードします。 http://memchan.sourceforge.net/
  1. 上記のメモで TclVfs 拡張をビルドする手順に合わせて、ソースファイル群のフォルダ構成を下記のようにします。
      work
       / tcl8.5.10 / winとかunixとか...
       / tk8.5.10 / winとかunixとか...
       / tclvfs-20080503 / winとかmacとか...
       / Memchan2.3 / winとかmacとか...
    
  2. work / Memchan2.3 / win / に、 mk_memchan.bat を配置します。
  3. VisualStudio2008コマンドプロンプトで work / Memchan2.3 / win / に cd で移動します。
  4. Visual Studio や Tcl/Tk Memchan のバージョンがこの記事と異なる場合は、 mk_memchan.bat の先頭に書かれている環境変数の内容を適宜書き換えます。
  5. mk_memchan.bat を実行して下さい。コマンドライン引数が2つ必要です。ビルドが実施され、 work / inst フォルダの中に実行時に必要なファイル群が配置されます。

<mk_memchan.bat のコマンドライン引数の説明>

1つ目 : Release版か、Debug版かを指定します。  Release または Debug を指定してください。
2つ目 : ビットモードを指定します。 x86 または x64 を指定してください。
実行例:
  mk_memchan.bat Release x86

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 関数を追記することになります。

下記の例のソースコードは こちらからダウンロード することもできます。 著作権、ライセンス表示(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();

	// 2017/1/23 追記 ... Tcl_FindExecutable を下に移動したからここでは呼びたくない・・・が、コメントアウトしても大丈夫だろうか・・・
	// 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);

	// 2017/1/23 追記 ... Tcl、Tk 初期化後でないと、 system encoding の登録が利かない模様
	Tcl_FindExecutable(argv[0]);

	// デモを動かす
	Tcl_Eval(interp, "source $tcl_library/../tk8.5/demos/widget;");
	Tk_MainLoop();
	return 0;
}

実行可能ファイル群の配置

上記ソースをビルドした結果作成される exe と、 tar ファイルを同一のフォルダに配置します。 また、vfs 拡張や memchan 拡張の Cソースをビルドした部分を dll として扱いたい場合は、先ほど tar 化する際に除いた lib フォルダ下の各 dll ファイルも、 exe ファイルと同じディレクトリに配置して下さい。

参考サイト

なもなも 様 のWebサイトは、 Tcl/Tk を C or C++ から呼ぶプログラムを作成する際、大変お世話になっております。
tar を直接読む方法 (read_vfs_script 関数) のコーディングで参考になりました。
さらに、dll 群(tcl85t.dll、tk85t.dll、...等)も1つにまとめてしまうことを考えてみました。
8.5.15 とTclVFS の組み合わせや、 64bitモードで TclVFSのビルドが通らない問題の対策を書いておきました。

更新履歴

  • 2013-12-22 : mk_memchan.bat の 64bit 対応、及びRelease / Debug、 32bit / 64bit 切り替え機能の追加
  • 2017-01-23 : systemencoding が正しく登録されるように修正