Frequently used words (click to add to your profile)

javaandroidc++linuxc#objective-ccocoa誰得qtrubypythonbathyscaphephpgamewindowsguic翻訳omegattwitterframeworktestbtronarduinovb.net計画中(planning stage)directxpreviewerゲームエンジンdom

最近の作業部屋活動履歴

2020-02-26
2020-02-10
2020-02-09
2019-12-30

最近のWikiの更新 (Recent Changes)

2020-02-26
2020-02-10
2019-12-30
2019-04-30
2019-04-02

Wikiガイド(Guide)

サイドバー (Side Bar)

Tcl/Tkのdll群も1つにまとめる方法

はじめに

Tcl/Tk のファイル群を tar でまとめた状態で実行可能にする方法 で、Tcl/Tkの実行に必要な、dll以外のファイルを1つのtarファイルにまとめた状態で実行できるようになりました。 ここでは、さらにdll群も1つにまとめる、ということを考えていきます。

Tcl/Tkはオープンソースなので、全てのソースをまとめてビルドするためのvcprojファイルやメイクファイル等を作ってしまえばもちろん実現できますが、 各モジュールがバージョンアップされたときにそれらを毎回作り直すことになり、ほんの少しだけ面倒です。

そこで、Visual Studioで、Tcl/Tkやその拡張のスタティックライブラリから dll を作成する方法を試してみました。

方法

dllに入っているTcl_Init、Tk_Init等の関数を呼べるようにするためには、通常、下記2通りのどちらかの方法を使うことになります。

  1. defファイルを作成する。
  2. 関数の宣言時に __declspec(dllexport) をつける。

Tcl/Tkの各モジュールは 2. の方法をとっていて、dll を作成したいときは __declspec(dllexport) が付くように、 また、スタティックライブラリを作成したいときは __declspec(dllexport) が付かないように、マクロで切り替えられるようになっています。

スタティックライブラリの中にはコンパイルした大量の obj ファイルが入っていて、これをリンカに通してdllを作成することになりますが、 これらの obj ファイルの関数の宣言には __declspec(dllexport) がついていませんので、dll 作成のために def ファイルを用意する必要があります。

def ファイルを自動生成する方法として、スタティックライブラリから宣言を抜き出してdefファイルのフォーマットにしたり、ヘッダファイルから生成する等、いくつか 方法が考えられます。確実で簡単な方法はないか探してみたところ、dll から def ファイルを作成できる、 pexports というとても便利なツールを見つけてしまいましたので、 今回はこれを活用し、一度、それぞれのdll(tcl85.dll、tk85.dll 等) を通常通りの方法で作成した後、その dll 群にpexports を掛けてdef ファイルを作成する、という方法をとってみました。

手順

  1. TclTk + Incr Tcl + Incr Tk + Incr Widgets + TclVfs + Metakit をVisualC++を使ってソースから導入するメモ と同じ方法で Tcl/Tk のビルド環境を整える。
  2. pexports の実行ファイルをダウンロードし、下記のような階層にする。
     work
        / tcl8.5.11 / winとかunixとか...
        / tk8.5.11 / winとかunixとか...
        / tclvfs-20080503 / winとかmacとか...
        / Memchan2.3 / winとかunixとか...  
        / blt2.5 / winとかunixとか...  
        / pexports / pexports.exe
                          mk_def_from_static_libs.bat
      ... その他、使いたい拡張があれば、同じように配置
    
  3. Visual Studio 2008 コマンドプロンプトを開き、 pexports.exe のあるディレクトリまで cd し、 mk_def_from_static_libs.bat を実行する。
  4. Tcl/Tk を使用するプログラムを作成する際、下記のようにTcl/Tk各モジュールを初期化する。
    /// Copyright (C) 2013, Mocchi
    /// License: Boost ver.1
    
    #include "tcl.h"
    #include "tk.h"
    #include <string>
    #include <cstdio>
    #include <windows.h>
    
    extern "C" {
    int Vfs_Init(Tcl_Interp *);
    int Memchan_Init(Tcl_Interp *);
    int Blt_Init(Tcl_Interp *);
    void TkWinSetHINSTANCE(HINSTANCE hInstance);
    }
    
    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);
    	::Tcl_StaticPackage(interp, "BLT", Blt_Init, 0);
    
    	::TkWinSetHINSTANCE(::GetModuleHandle("tclmodules.dll"));
    	
    	// 実行ファイルのディレクトリを取得
    	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;
    }
    

配布物

参考サイト

pexports のことを知るきっかけとなったページです。便利なツールを教えて下さりありがとうございます。
BLT2.5を Visual Studio 2008 でビルドする方法が書かれています。