よりばれにくい占いのために
 ちょっと脱線します。 C言語によるプログラミングに熟達されている方はご存知と思いますが、 ソースコードの文字列定数はバイナリエディタやUNIX標準のstringsというコマンドをかければ、 C言語のプログラムをコンパイルして作ったEXEからでも抽出できます。 ですからCのプログラムでもどんなメッセージを用意しているのか、 など裏舞台がばれる危険はあります。 より安全には必要に応じて暗号化などをかけなければいけません。

 さて、C言語でTcl/Tk処理系を拡張し、 「uranai」というTclコマンドを追加してみましょう。 ほんとにやります。げーなんて言わずに!簡単ですから。ほんとですから (まるで英会話の勧誘) ここでは、最も基本的なやり方だけ載せています。 より詳しくはなもなもがお役にたてば幸いです。

 プログラムはこんな感じです。「uranai.c」とでも名づけて保存しておきます。

#include "tcl.h"
static int uranaiHandleProc(ClientData data, Tcl_Interp* interp,
			    int argc, char* argv[]){
#define MESSAGE_MAX 7
  static char* messages[] = {
    "ちょっとした発言が、思わぬ誤解につながることも。",
    "金運なし。友人に借りたCDは返すな。",
    "張り込みには富士銀行の脇が吉。",
    "言わないことは伝わらない。はっきり意見を言おう。",
    "ラッキー食品はてりやきチキン。",
    "緑の濃い野菜はスタミナの源です。",
    "部屋を掃除するといいことがあります。"
  };
  int month, day;
  char ereason[1024];
  Tcl_DString ds;
  char* p;
  if(argc < 2){
    sprintf(ereason, "too few arguments, should be:%s month day", argv[0]);
    Tcl_SetResult(interp, ereason, TCL_VOLATILE);
    return TCL_ERROR;
  }
  if(Tcl_GetInt(interp, argv[1], & month) == TCL_ERROR) return TCL_ERROR;
  if(Tcl_GetInt(interp, argv[2], & day)  == TCL_ERROR) return TCL_ERROR;
  p = Tcl_ExternalToUtfDString(NULL, messages[(month+day) % MESSAGE_MAX],
			       -1, & ds);
  Tcl_SetResult(interp, p, TCL_VOLATILE);
  Tcl_DStringFree(& ds);
  return TCL_OK;
}

DLLEXPORT int Uranai_Init(Tcl_Interp* interp){
  Tcl_CreateCommand(interp, "uranai", uranaiHandleProc, NULL, NULL);
  return TCL_OK;
}
 超基本というわけにはいかなくて、ちょっと複雑なことをしています。 それは、Tcl/Tkは内部の文字列処理がUnicodeの一種UTF-8になっているので、 日本語の文字列をUTF-8に変換する処理が入っているためです。 「Tcl_******」 というのが処理系にもれなくついてくる Tclライブラリで提供される関数(Tcl/Tk API)です。  ここで出てくるものを簡単に紹介すると、
  • Tcl_SetResult…Tclコマンドの「戻り値」をセットする関数です。
  • Tcl_CreateCommand…Tclコマンドを新しく処理系に登録する関数です。
  • Tcl_GetInt…ANSI標準のsscanf関数のように、文字列から数値に変換します。
あと2つありますが、 これらは1行ではとても紹介しきれないので別ページに譲りましょう。

 さて、次にこれをコンパイルして共有ライブラリ (MS-Windowsではダイナミックリンクライブラリ(DLL))を作ります。 コマンドラインで指定するのはちょっと面倒なので、 Makefileを作ってmake(NMAKE)でコンパイルをさせます。

 UNIX上でgccコンパイラを使う場合にはこんな感じになります。

CC	= gcc
SHLD	= gcc -shared
CFLAGS	= -I/usr/local/include -O2 -fPIC
LDFLAGS	= -L/usr/local/include
.c.o:
	$(CC) -o $@ $(CFLAGS) -c $*.c
liburanai.so: uranai.o
	$(SHLD) -o $@ $(LDFLAGS) $?

 MS-Windows上Microsoft Optimizing Compiler-Incremental Linker (つまり、VC++など)を使う場合にはこんな感じになります。

TCLROOT	= C:\Program Files\Tcl
CC	= cl
SHLD	= cl /LD
CFLAGS	= /I"$(TCLROOT)\include" /I"$(TCLROOT)\include\X11"
LIBS    = "$(TCLROOT)\lib\tcl83.lib" "$(TCLROOT)\lib\tk83.lib"
.c.obj:
	$(CC) /o $@ $(CFLAGS) /c $*.c
liburanai.dll: uranai.obj
	$(SHLD) /o $@ uranai.obj $(LIBS)
なぜそんな指定になるのか、なぜそんな書き方をするのか、など細かいところや、 「私インストールディレクトリが違うんだけど」という場合は、 なもなもでこうるさく書いているので、 こちらをご覧ください。

 つつがなくコンパイルができたら、実行してみます。

shell% tclsh8.3
% load liburanai[info sharedlibextension]
% uranai 1 15
張り込みには富士銀行の脇が吉。
文字化けしてしまった場合は、ソースプログラムがUNIXなら日本語EUCで、 WindowsならシフトJISで書かれているか確認してみてくださいね。 どちらで書かれていても正しく変換できる方法もあるのですが、 ちょっと複雑になりすぎるのでこれも別ページにまわしましょう。

 というわけで、いかがですか?簡単ですね。 実はTcl/Tk APIは100以上の関数から成る大規模なライブラリなのですが、 そのうち少々、とくにTcl_Eval という関数1つでも覚えていればTcl言語とC言語を組み合わせてこのようにいろいろ拡張することができます。 世界の拡張レビュー分室で触れている有名なTcl拡張ライブラリも、 このようにしてTcl/Tkに独自のコマンドを追加して素晴らしい機能を提供してくれるものです。 我らがインチキ占い拡張も素晴らしい機能を提供してく… ちょっと後ろの方、お静かに… 誰ですか、害悪にしかならないなどとそこでコソコソ言っているのは!

やねうらの top
(first uploaded 2000/03/25 last updated (not ever) , Urano398 - K.Ushirodani )