C言語でTclコマンドを実行するには
 C言語とTcl/Tkを混ぜて使うとき、大きくわけて2つのスタイルがあります。 それは簡単に言うと「自分でmain関数を書くか書かないか」 というわけで、ここではまずmain関数を書く方から行きます。 つまり「C言語のプログラム中から、Tcl/Tkのコマンドやスクリプトを 呼び出す」というC言語主導型の例ですが、 実際にここでやっているのは 「C言語世界の変数の値をTcl言語世界の変数にセットする。 次にその変数の値を再度C言語世界に取り出して表示する」というだけのものです。
/* サンプル271(main関数をもつ独立実行プログラム) */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "tcl.h" /* (1) */

static void application(Tcl_Interp* interp, char* myname);

int main(int argc, char* argv[]){
  Tcl_Interp* interp;
  char* myname = "Posa";

  interp = Tcl_CreateInterp();       /* (2) */
  Tcl_FindExecutable(argv[0]);       /* (3) */
  if(Tcl_Init(interp) == TCL_ERROR){ /* (4) */
    fprintf(stderr, "Tcl interpreter initialization failed.\n");
    return 1;
  }
  /* (5) */
  application(interp, myname);
  Tcl_DeleteInterp(interp);
  return 0;
}

static void application(Tcl_Interp* interp, char* myname){
  char buf[256];
  char* p;

  Tcl_SetVar(interp, "name", myname, 0);             /* (6) */
  strcpy(buf, "set msg \"Hello, World and $name\""); /* (7) */
  if(Tcl_Eval(interp, buf) == TCL_ERROR){
    fprintf(stderr, "Interpretaion error: %s\n",
	    Tcl_GetStringResult(interp) );           /* (8) */
    return;
  }
  if((p = Tcl_GetVar(interp, "msg", 0)) != NULL)     /* (9) */
    fprintf(stderr, "<%s>\n", p);
}
  • (1) Tcl APIを使うにはtcl.hだけをインクルードすればOKです。Tk のコマンドやAPIも使う場合にはtk.hもインクルードします。
  • (2) Tcl_CreateInterpでTcl_Interp 型の変数のポインタを1つ取得します。 以後コマンドの解釈実行などにはこの変数が必要になります。 この変数1つが1つの「Tclインタープリタ」となります。 この実験室では、Tcl_Interp型のポインタ変数の変数名は 「interp」で統一しています。 処理系や多くの拡張のソースプログラムでもこの名前になっているので、 普通は必ずこの名前にすることをおすすめします。 通常interpは一度に1つだけ作ればOKです。 Tclインタープリタがいらなくなったら、Tcl_DeleteInterp を呼ぶとメモリの整理などをして自爆します。
  • (3) Tcl_FindExecutableは8.1b2以降の版で Tcl_Init を呼ぶ前にはほぼ必ず呼ぶ必要があります。以前のバージョンでは不要です。
  • (4) Tcl_Initは普通最初に呼びます。 ただし、このTcl_Init は処理系にもれなくついてくるスクリプト init.tclをsourceしているだけなので、 init.tclで追加されるプロシージャやセットされる大域変数を自作のアプリケーションがまったく使わないことがはっきりしている場合には、 Tcl_Initを使う必要はありません。 (つまりこの例では本当は不要です) Tkのコマンドや APIも使う場合はこの直後に同様にしてTk_Init も呼びます。そして、Tk_Initを呼ぶときには必ず Tcl_Initも使わなければいけません。
  • (5) あとで出てくる「なんとか拡張」の(なんとか)_Initという関数をここで呼び出せば、 その「なんとか拡張」をTclインタープリタに組み込むことができます。
  • (6) Tcl_SetVarはそのTclインタープリタ内のTcl変数に何か値 (文字列として指定)をセットするポピュラーなAPIです。このAPIでは、セットする値としてバイナリデータは使えません。バイナリデータをTcl変数にセットするには Tcl_SetVar2ExというAPIが必要です。 これはずっと後で出てきます。
  • (7) Tcl_Evalは数あるTcl API 関数群の中でも最もポピュラーなAPIで、 文字列で指定したTclコマンドを解釈実行するAPIです。 このAPIはTclコマンドが正常に完了したらTCL_OKという記号定数を、 エラーが発生したらTCL_ERRORという記号定数を返してきます。 重大な注意ですが、第2引数で指定するTclコマンドの文字列は、 例のように必ず書きこみ可能な文字列領域を指定しないといけません。
      Tcl_Eval(interp, "puts {Hello, World}");
    
    のように文字列定数を直接代入すると必ず異常終了します。
  • (8) Tcl_EvalなどいくつかのAPIを使った直後に、 Tcl_GetStringResultというAPI を使えば、正常終了の場合はそのコマンドの解釈した「値」が、 エラーの場合はエラーメッセージがCの文字列として取得できます。 このAPIでは、コマンドの「値」としてバイナリデータを受け取ることができません。 バイナリデータを受け取るには、 Tcl_GetObjResultというAPIが必要です。
  • (9) Tcl_GetVarはTcl変数の値を取り出すポピュラーなAPI です。値はC言語型の(0x00で終わる)文字列で返され、変数が存在しない場合はNULL が返ってきます。このAPIでは、変数の値にバイナリデータが入っている場合、 それを取り出すことができません。バイナリデータをTcl 変数から取り出したい場合には、 Tcl_GetVar2ExというAPIが必要です。

 Tcl_EvalEx(*1)は、Tcl_Eval と同じようにコマンドを解釈実行しますが、

  Tcl_EvalEx(interp, command, -1, TCL_EVAL_GLOBAL);
のように第4引数に記号定数TCL_EVAL_GLOBALを指定すると いつ呼ばれてもグローバルレベルでコマンドを解釈させることができます。 第3引数は通常 strlen(command) を指定しますが、 -1とすると自動的にその値になります。 また一般に、Tcl_EvalEx のほうがTcl_Evalよりも高速です。
 このほかに、Tcl_EvalFile というAPIも便利です。これは、Tcl言語のスクリプトファイルを
  Tcl_EvalFile(interp, filename);
のように指定すると、そのファイル全体をsourceコマンドのように読んで実行し、 途中でエラーがなければTCL_OK、エラーがあればそこで止まって TCL_ERRORを返してきます。
(*1)Tcl_EvalExはTcl 8.0〜Tcl 8.1b1までは Tcl_Eval2 という名前でしたが8.1b2で改名されました。

 次の例はもっと簡単なプログラムです。 これでTcl_Evalと Tcl_GetStringResult の使い方が理解できれば、いろいろなプログラムが作れることでしょう。

/* サンプル272(main関数をもつ独立実行プログラム) */
#include <stdio.h>
#include "tcl.h"

int main(int argc, char* argv[]){
  char command[1024];
  Tcl_Interp* interp;

  if(argc != 3){
    fprintf(stderr, "usage: %s num1 num2\n", argv[0]);
    return 1;
  }
  interp = Tcl_CreateInterp();
  sprintf(command, "expr %s+%s\n", argv[1], argv[2]);
  if(Tcl_Eval(interp, command) == TCL_ERROR)
    fprintf(stderr, "ERROR: %s\n", Tcl_GetStringResult(interp));
  else
    fprintf(stdout, "%s\n", Tcl_GetStringResult(interp));
  return 0;
}
/* end. */

なもなも top
(first uploaded 1999/03/12 last updated 2000/04/02 , Urano398 )