●Tcl/Tk とC言語
- Tcl/Tk は簡単に GUI を構築できるが、Tcl に不満がないわけではない
- Tcl はシェルスクリプトを拡張したコマンド言語、いわゆる簡易言語にすぎない
- 大規模なプログラム開発には不向きだと思われる
- インタプリタなので実行速度も期待できない (Python や Ruby よりも遅い)
- もともと Tcl はアプリケーションやツールの拡張言語として設計された
- C言語で書かれた関数を Tcl/Tk にリンクして呼び出すことは簡単にできる
- Windows であれば DLL, Unix 系 OS であれば共有ライブラリを実行時にロード (動的リンク) することも可能
- また、C言語で作ったアプリケーションから Tcl/Tk を利用することもできる
- 本稿では、C言語で Tcl コマンドを作成する方法を簡単に説明する
- 実行環境は Unix 系 OS (Xubuntu など) と Cygwin, コンパイラには gcc を使う
- このほかに、Tcl/Tk の開発用パッケージが必要になる
- Debian 系 OS では以下のコマンドでインストールできる
sudo apt install tcl-dev tk-dev
Cygwin ではパッケージ tcl-tk-devel がインストールされいていれば OK
次のコマンドで Tcl/Tk 関連のパッケージがインストールされているか確認できる
$ cygcheck -c -d | grep tcl
tcl 8.6.8-1
tcl-devel 8.6.8-1
tcl-tix 8.4.3-3
tcl-tk 8.6.8-1
tcl-tk-devel 8.6.8-1
入っていない場合は追加インストールする
●Tcl インタプリタと Tcl オブジェクト
- まず最初に、Tcl インタプリタを起動してスクリプトを評価するプログラムを作る
- 主な Tcl ライブラリ関数
- Tcl_Interp *Tcl_CreateInterp(void), インタプリタの作成
- void Tcl_DeleteInterp(Tcl_Interp *ip), インタプリタの削除
- int Tcl_Eval(Tcl_Interp *ip, char *script), インタプリタ ip でスクリプト script を評価する
- int Tcl_EvalFile(Tcl_interp *ip, char *filename), インタプリタ ip でファイル filename のスクリプトを評価する
- Tcl_Interp はインタプリタを表す構造体
- Tcl_Eval, Tcl_EvalFile の返り値はスクリプトの完了コード (TCL_OK : 0, TCL_ERROR : 1 など)
- 評価結果を文字列で取得するときは Tcl_GetStringResult を使う
- const char *Tcl_GetStringResult(Tcl_Interp *ip)
- Tcl の内部では値 (数値などのデータ) を Tcl オブジェクト (Tcl_Obj 構造体) として保持している
- 値から Tcl オブジェクトを生成する、逆に Tcl オブジェクトから値を取り出す関数が用意されている
- 生成関数の名前は Tcl_NewXxxObj (Xxx はデータ型)
- アクセス関数の名前は Tcl_GetXxxFromObj, Tcl_SetXxxObj
- 整数 (int) の例
- Tcl_obj *Tcl_NewIntObj(int value), 整数 -> Tcl オブジェクト
- int Tcl_GetIntFromObj(Tcl_Interp *ip, Tcl_Obj *obj, int *p), Tcl オブジェクト -> 整数 (p に格納)
- 整数であれば TCL_OK が、そうでなければ TCL_ERROR が返される
- Tcl_Obj *Tcl_SetIntObj(Tcl_Obj *obj, int value), Tcl オブジェクトに整数 value をセットする
- 評価結果を Tcl オブジェクトで取得するときは Tcl_GetObjResult を使う
- Tcl_obj *Tcl_GetObjResult(Tcl_Interp *ip)
- スクリプトを評価するライブラリ関数はこのほかにもある
- 詳細は Tcl_Eval manual page を参照
リスト : C言語から Tcl インタプリタを呼び出す (sample01.c)
#include <stdio.h>
#include <tcl/tcl.h> /* Cygwin は #include <tcl.h> とする */
#define BUFF_SIZE 1024
int main()
{
char buff[BUFF_SIZE];
Tcl_Interp *ip = Tcl_CreateInterp();
printf("> ");
while (fgets(buff, BUFF_SIZE, stdin) != NULL) {
if (Tcl_Eval(ip, buff) != TCL_OK) {
fprintf(stderr, "%s\n", Tcl_GetStringResult(ip));
} else {
printf("%s\n", Tcl_GetStringResult(ip));
}
printf("> ");
}
Tcl_DeleteInterp(ip);
return 0;
}
$ gcc -O sample01.c -o sample01 -ltcl
$ ./sample01
> set a 10
10
> set b
can't read "b": no such variable
> set b 20
20
> expr $a + $b
30
> CTRL-D で終了
●Tcl コマンドの作成
- Tcl コマンドの新規作成は関数 Tcl_CreateObjCommand で行う
Tcl_Command Tcl_CreateObjCommand(
Tcl_Interp *ip,
char *name,
Tcl_ObjCmdProc *proc,
ClientData clientData,
Tcl_CmdDeleteProc *deleteProc);
引数 name が Tcl コマンドの名前、proc がコマンド本体 (ポインタ)
clientData はコマンドで使用するメモリ領域 (ポインタ)
deleteProc は clientData を解放する関数 (ポインタ)
一般的なコマンドでは clientData と deleteProc の値は NULL でよい
コマンド本体 (関数) の仕様
typedef int Tcl_ObjCmdProc(
ClientData clientData,
Tcl_Interp *ip,
int objc,
Tcl_Obj *const objv[]);
引数 clientData には Tcl_CreateObjCommand で指定した clientData が渡される
配列 objv の先頭は実行するコマンド、それ以降にコマンドの引数がセットされる
引数 objc の値は引数の個数 + 1
返り値は完了コード (正常終了ならば TCL_OK, 異常終了ならば TCL_ERROR)
評価結果は関数 Tcl_SetObjResult でセットする
void Tcl_SetObjResult(Tcl_Interp *ip, Tcl_Obj *obj);
簡単な使用例
リスト : Tcl コマンドの作成 (sample02.c)
#include <stdio.h>
#include <string.h>
#include <tcl/tcl.h>
#define BUFF_SIZE 1024
/* コマンド */
static int proc_foo(ClientData clientData, Tcl_Interp *ip, int argc, Tcl_Obj *const argv[])
{
char *err = "wrong # args: should be \"foo arg1 arg2 ...\"";
if (argc < 3) {
Tcl_SetObjResult(ip, Tcl_NewStringObj(err, strlen(err)));
return TCL_ERROR;
}
for (int i = 0; i < argc; i++) {
int len;
printf("%s ", Tcl_GetStringFromObj(argv[i], &len));
}
printf("\n");
Tcl_SetObjResult(ip, Tcl_NewIntObj(argc));
return TCL_OK;
}
int main()
{
char buff[BUFF_SIZE];
Tcl_Interp *ip = Tcl_CreateInterp();
/* コマンドの登録 */
Tcl_CreateObjCommand(ip, "foo", proc_foo, NULL, NULL);
printf("> ");
while (fgets(buff, BUFF_SIZE, stdin) != NULL) {
if (Tcl_Eval(ip, buff) != TCL_OK) {
fprintf(stderr, "%s\n", Tcl_GetStringResult(ip));
} else {
printf("%s\n", Tcl_GetStringResult(ip));
}
printf("> ");
}
Tcl_DeleteInterp(ip);
return 0;
}
- Tcl_NewStringObj は文字列から Tcl オブジェクトを生成する
Tcl_Obj *Tcl_NewStringObj(char *src, int len);
引数 len は文字列の長さ (src から len バイトコピーする)
Tcl_GetStringFromObj は Tcl オブジェクトから文字列を取り出す
char *Tcl_GetStringFromObj(Tcl_Obj *obj, int *lenPtr);
lenPtr に文字列の長さがセットされる
簡単な実行例
$ gcc -O sample02.c -o sample02 -ltcl
$ ./sample02
> foo
wrong # args: should be "foo arg1 arg2 ..."
> foo 1 2
foo 1 2
3
> foo 1 2 3 4
foo 1 2 3 4
5
>
●パッケージの作成
- Tcl/Tk は共有ライブラリ (.so や .dll) をパッケージとして扱うことができる
- 共有ライブラリはコマンド load でロードする
- package require ... でロードすることも可能
1. load filename
2. load filename pkgName
このとき、パッケージ内にある初期化関数を呼び出す (パッケージ初期化関数という)
1 の場合、共有ライブラリ filename が libxxxx.so とすると、関数名は Xxxx_Init となる
2 の場合、パッケージ名 pkgName が xxxx とすると、関数名は Xxxx_init となる
つまり、xxxx の先頭文字を大文字とし、残りの文字を小文字にしたものが関数名になる
本稿では 1 の方法でパッケージを作成する
リスト : パッケージ初期化関数
int Xxxx_Init(Tcl_Interp *ip)
{
/*
* コマンドの登録などの初期化処理
*
*/
return Tcl_PkgProvide(ip, "パッケージ名", "バージョン番号");
}
Tcl_PkgProvide はコマンド package provide ... と同じ働きをする関数
あとは共有ライブラリを作成して、load libxxxx.so でパッケージをロードするだけ
簡単な使用例
リスト : パッケージの作成 (libfoo.c)
#include <tcl.h>
#include <string.h>
/* コマンド */
int proc_foo(ClientData clientData, Tcl_Interp *ip, int argc, Tcl_Obj *const argv[])
{
char *err = "wrong # args: should be \"foo arg1 arg2 ...\"";
if (argc < 3) {
Tcl_SetObjResult(ip, Tcl_NewStringObj(err, strlen(err)));
return TCL_ERROR;
}
for (int i = 0; i < argc; i++) {
int len;
printf("%s ", Tcl_GetStringFromObj(argv[i], &len));
}
printf("\n");
Tcl_SetObjResult(ip, Tcl_NewIntObj(argc));
return TCL_OK;
}
/* Tcl コマンドの初期化 */
int Foo_Init(Tcl_Interp *ip)
{
Tcl_CreateObjCommand(ip, "foo", proc_foo, NULL, NULL);
return Tcl_PkgProvide(ip, "foo", "1.0");
}
$ gcc -O -shared -fPIC -o libfoo.so libfoo.c -ltcl
$ tclsh
% load libfoo.so
% foo
wrong # args: should be "foo arg1 arg2 ..."
% foo 1 2
foo 1 2
3
% foo 1 2 3 4 5
foo 1 2 3 4 5
6
オプション -ltcl はなくてもコンパイルできる
Cygwin での実行例
$ gcc -O -shared -o libfoo.dll libfoo.c -ltcl
$ tclsh
% load libfoo.dll
% foo
wrong # args: should be "foo arg1 arg2 ..."
% foo 1 2
foo 1 2
3
% foo 1 2 3 4
foo 1 2 3 4
5
Cygwin の場合、オプション -ltcl は必要で、-fPIC は不要
- package require ... を使用する場合、パッケージ で説明した手順とほぼ同じ
- pkg_mkIndex に渡すファイル名を共有ライブラリ名にするだけ
- xubuntu での実行例
$ tclsh
% ls foo1.0
libfoo.c libfoo.so
% pkg_mkIndex foo1.0 libfoo.so
% ls foo1.0
libfoo.c libfoo.so pkgIndex.tcl
% cat foo1.0/pkgIndex.tcl
# Tcl package index file, version 1.1
# This file is generated by the "pkg_mkIndex" command
# and sourced either when an application starts up or
# by a "package unknown" script. It invokes the
# "package ifneeded" command to set up package-related
# information so that packages will be loaded automatically
# in response to "package require" commands. When this
# script is sourced, the variable $dir must contain the
# full path name of this file's directory.
package ifneeded foo 1.0 [list load [file join $dir libfoo.so]]
% lappend auto_path [pwd]
・・・省略・・・
% package require foo
1.0
% foo
wrong # args: should be "foo arg1 arg2 ..."
% foo 1 2 3 4
foo 1 2 3 4
5
%
Cygwin での実行例
$ tclsh
% ls foo1.0
libfoo.c libfoo.dll
% pkg_mkIndex foo1.0 libfoo.dll
% ls foo1.0
libfoo.c libfoo.dll pkgIndex.tcl
% cat foo1.0/pkgIndex.tcl
# Tcl package index file, version 1.1
# This file is generated by the "pkg_mkIndex" command
# and sourced either when an application starts up or
# by a "package unknown" script. It invokes the
# "package ifneeded" command to set up package-related
# information so that packages will be loaded automatically
# in response to "package require" commands. When this
# script is sourced, the variable $dir must contain the
# full path name of this file's directory.
package ifneeded foo 1.0 [list load [file join $dir libfoo.dll]]
% lappend auto_path [pwd]
・・・省略・・・
% package require foo
1.0
% foo
wrong # args: should be "foo arg1 arg2 ..."
% foo 1 2 3
foo 1 2 3
4
%
●名前空間
- 名前空間は関数 Tcl_CreateNamespace で生成する
Tcl_Namespace *Tcl_CreateNamespace(
Tcl_Interp *ip,
char *name,
ClientData clientData,
Tcl_CmdDeleteProc *deleteProc);
Tcl_Namespace は名前空間を表す構造体
引数 name に名前空間を指定する
あとの引数は Tcl_CreateObjCommand と同じ
clientData と deleteProc は NULL でよい
名前空間 name にコマンド cmd を定義するときは、Tcl_CreateObjCommand に渡すコマンド名を "name::cmd" とする
名前空間 name が存在しない場合、Tcl_CreateObjCommand は自動的に name を生成する
- Tcl の場合、名前空間 name が定義されていないと、 proc name::cmd {} { ... } はエラーになる
- name が定義されていれば OK
- コマンドをイクスポートするとき名前空間が必要になる
- あらかじめ名前空間を作成しておいたほうが便利
- 名前空間を探索することもできる (関数 Tcl_FindNamespace を使う)
コマンド名のイクスポートは関数 Tcl_Export を使う
int Tcl_Export(
Tcl_Interp *ip,
Tcl_Namespace *np,
const char *pattern,
int resetListFirst);
イクスポートするコマンド名は引数 pattern で指定する (glob 形式のメタ文字が利用可)
resetListFirst が真 (1) ならば、イクスポートする名前のリストをリセットする (通常は 0 でよい)
簡単な使用例
リスト : 名前空間の使用例
#include <tcl/tcl.h>
#include <string.h>
/* コマンド */
int proc_foo(ClientData clientData, Tcl_Interp *ip, int argc, Tcl_Obj *const argv[])
{
char *err = "wrong # args: should be \"foo::foo arg1 arg2 ...\"";
if (argc < 3) {
Tcl_SetObjResult(ip, Tcl_NewStringObj(err, strlen(err)));
return TCL_ERROR;
}
for (int i = 0; i < argc; i++) {
int len;
printf("%s ", Tcl_GetStringFromObj(argv[i], &len));
}
printf("\n");
Tcl_SetObjResult(ip, Tcl_NewIntObj(argc));
return TCL_OK;
}
/* Tcl コマンドの初期化 */
int Foo_Init(Tcl_Interp *ip)
{
Tcl_Namespace *np = Tcl_CreateNamespace(ip, "foo", NULL, NULL);
Tcl_CreateObjCommand(ip, "foo::foo", proc_foo, NULL, NULL);
Tcl_Export(ip, np, "foo", 0);
return Tcl_PkgProvide(ip, "foo", "1.0");
}
$ gcc -O -shared -fPIC -o libfoo.so libfoo.c -ltcl
$ tclsh
% load libfoo.so
% foo::foo 1 2 3
foo::foo 1 2 3
4
% foo 1 2 3
invalid command name "foo"
% namespace import foo::*
% foo 1 2 3
foo 1 2 3
4
%
●リスト
1. Tcl_Obj *Tcl_NewListObj(int objc, Tcl_Obj **objv);
2. Tcl_Obj *Tcl_NewListObj(Tcl_Obj *obj, int objc, Tcl_Obj **objv);
3. int Tcl_ListObjAppendList(Tcl_interp *ip, Tcl_Obj *listPtr, Tcl_Obj *elemListPtr);
4. int Tcl_ListObjAppendElement(Tcl_Interp *ip, Tcl_Obj *istPtr, Tcl_Obj *obj);
5. int Tcl_ListObjGetElements(Tcl_Inter *ip, Tcl_Obj *listPtr, int *objcPtr, Tcl_Obj **objv);
6. int Tcl_ListObjLength(Tcl_Interp *ip, Tcl_Obj *listPtr, int *intPtr);
7. int Tcl_ListObjIndex(Tcl_Interp *ip, Tcl_Obj *listPtr, int index, Tcl_Obj **objPtr);
8. int Tcl_ListObjReplace(Tcl_Interp *ip, Tcl_Obj *listPtr, int first, int count, int objc, Tcl_Obj **objv);
- 配列 objv の要素 (objc 個) を格納したリストを生成する
- 配列 objv の要素 (objc 個) を格納したリストを生成し、それを obj にセットする
- リスト listPtr にリスト elemListPtr を結合する
- リスト listPtr に要素 obj を追加する
- リスト listPtr の要素を配列 objv に格納する, 要素数は objcPtr にセットされる
- リスト listPtr の要素数を intPtr にセットする
- リスト listPtr の index 番目の要素を objPtr にセットする
- リスト listPtr の first 番目から count 個の要素を配列 objv の要素 (objc 個) に置き換える
Tcl_ListObj*() は正常終了であれば TCL_OK、異常終了であれば Tcl_ERROR を返す
これらの関数の動作は Tcl のリスト操作コマンドと同じ
引数がリストでなくても動作する関数がある (TCL_ERROR は返さない) ので注意
リスト : リスト操作関数の使用例 (mylist.c)
#include <tcl/tcl.h>
#include <string.h>
/* リストの生成 (コマンド list と同じ) */
int proc_mylist(ClientData clientData, Tcl_Interp *ip, int argc, Tcl_Obj *const argv[])
{
Tcl_SetObjResult(ip, Tcl_NewListObj(argc - 1, argv + 1));
return TCL_OK;
}
/* 数列の生成 */
int proc_iota(ClientData clientData, Tcl_Interp *ip, int argc, Tcl_Obj *const argv[])
{
int n, c;
char *err = "wrong # args: should be \"iota start count\"";
if (argc < 3 ||
Tcl_GetIntFromObj(ip, argv[1], &n) != TCL_OK ||
Tcl_GetIntFromObj(ip, argv[2], &c) != TCL_OK) {
Tcl_SetObjResult(ip, Tcl_NewStringObj(err, strlen(err)));
return TCL_ERROR;
}
Tcl_Obj *xs = Tcl_NewListObj(0, NULL); /* 空リスト */
while (c-- > 0) {
Tcl_ListObjAppendElement(ip, xs, Tcl_NewIntObj(n++));
}
Tcl_SetObjResult(ip, xs);
return TCL_OK;
}
/* 2 つのリストを連想リスト (alist) にまとめる */
int proc_zip(ClientData clientData, Tcl_Interp *ip, int argc, Tcl_Obj *const argv[])
{
int xlen, ylen;
char *err = "wrong # args: should be \"zip xs ys\"";
if (argc != 3) {
Tcl_SetObjResult(ip, Tcl_NewStringObj(err, strlen(err)));
return TCL_ERROR;
}
Tcl_ListObjLength(ip, argv[1], &xlen);
Tcl_ListObjLength(ip, argv[2], &ylen);
if (xlen > ylen) xlen = ylen;
int i;
Tcl_Obj *buff[2];
Tcl_Obj *zs = Tcl_NewListObj(0, NULL); /* 空リスト */
for (i = 0; i < xlen; i++) {
Tcl_ListObjIndex(ip, argv[1], i, buff);
Tcl_ListObjIndex(ip, argv[2], i, buff + 1);
Tcl_ListObjAppendElement(ip, zs, Tcl_NewListObj(2, buff));
}
Tcl_SetObjResult(ip, zs);
return TCL_OK;
}
/* 連想リスト (alist) を 2 つのリストに分解する */
int proc_unzip(ClientData clientData, Tcl_Interp *ip, int argc, Tcl_Obj *const argv[])
{
int len;
char *err = "wrong # args: should be \"unzip alist\"";
if (argc != 2) {
Tcl_SetObjResult(ip, Tcl_NewStringObj(err, strlen(err)));
return TCL_ERROR;
}
Tcl_ListObjLength(ip, argv[1], &len);
Tcl_Obj *buff[2];
buff[0] = Tcl_NewListObj(0, NULL);
buff[1] = Tcl_NewListObj(0, NULL);
int i;
for (i = 0; i < len; i++) {
int k;
Tcl_Obj *zs[3];
Tcl_ListObjIndex(ip, argv[1], i, zs);
Tcl_ListObjLength(ip, zs[0], &k);
if (k != 2) {
Tcl_SetObjResult(ip, Tcl_NewStringObj(err, strlen(err)));
return TCL_ERROR;
}
Tcl_ListObjIndex(ip, zs[0], 0, zs + 1);
Tcl_ListObjIndex(ip, zs[0], 1, zs + 2);
Tcl_ListObjAppendElement(ip, buff[0], zs[1]);
Tcl_ListObjAppendElement(ip, buff[1], zs[2]);
}
Tcl_SetObjResult(ip, Tcl_NewListObj(2, buff));
return TCL_OK;
}
int Mylist_Init(Tcl_Interp *ip)
{
Tcl_CreateObjCommand(ip, "mylist", proc_mylist, NULL, NULL);
Tcl_CreateObjCommand(ip, "iota", proc_iota, NULL, NULL);
Tcl_CreateObjCommand(ip, "zip", proc_zip, NULL, NULL);
Tcl_CreateObjCommand(ip, "unzip", proc_unzip, NULL, NULL);
return Tcl_PkgProvide(ip, "mylist", "1.0");
}
$ gcc -O -shared -fPIC -o libmylist.so mylist.c -ltcl
$ tclsh
% load libmylist.so
% mylist 1 2 3 4
1 2 3 4
% mylist {1 2} {3 4}
{1 2} {3 4}
% mylist
% iota
wrong # args: should be "iota start count"
% iota a b
wrong # args: should be "iota start count"
% iota 1 10
1 2 3 4 5 6 7 8 9 10
% zip
wrong # args: should be "zip xs ys"
% zip {1 2 3 4} {5 6 7 8}
{1 5} {2 6} {3 7} {4 8}
% set a [zip [iota 1 5] [iota 11 15]]
{1 11} {2 12} {3 13} {4 14} {5 15}
% unzip
wrong # args: should be "unzip alist"
% unzip $a
{1 2 3 4 5} {11 12 13 14 15}
%
●Tcl 変数のアクセス
- Tcl 変数にアクセスするための基本的な関数を簡単に説明する
1. Tcl_Obj *Tcl_SetVar2Ex(Tcl_Interp *ip, const char *name1, const char *name2, Tcl_Obj *value, int flags);
2. Tcl_Obj *Tcl_GetVar2Ex(Tcl_Interp *ip, const char *name1, const char *name2, int flags);
3. int Tcl_UnsetVar2(Tcl_Interp *ip, const char *name1, const char *name2, int flags);
引数 name1 は変数名
name1 が配列の場合、引数 name2 が添字を表す (配列でなければ name2 の値は NULL とする)
flags に指定できる値 (bit)
- TCL_GLOBAL_ONLY, 大域変数を参照する
- TCL_NAMESPACE_ONLY, 現在の名前空間を参照する
- TCL_LEAVE_ERR_MSG, 操作に失敗したとき、評価結果にエラーメッセージをセットする
- TCL_APPEND_VALUE, 新しい値を上書きせずに、元の値の後ろに追加する
- TCL_LIST_ELEMENT, 代入や追加を行う前に、新しい値をリストの要素に変換する
リスト : グローバル変数専用アクセス関数 (gset.c)
#include <tcl/tcl.h>
#include <string.h>
/* グローバル変数専用版 */
int proc_gset(ClientData clientData, Tcl_Interp *ip, int argc, Tcl_Obj *const argv[])
{
char *err = "wrong # args: should be \"gset varName ?value?\"";
if (argc < 2 || argc > 3) {
Tcl_SetObjResult(ip, Tcl_NewStringObj(err, strlen(err)));
return TCL_ERROR;
}
int len;
char *name = Tcl_GetStringFromObj(argv[1], &len);
if (argc == 2) {
Tcl_Obj *var = Tcl_GetVar2Ex(ip, name, NULL, TCL_GLOBAL_ONLY | TCL_LEAVE_ERR_MSG);
if (var == NULL) {
return TCL_ERROR;
} else {
Tcl_SetObjResult(ip, var);
}
} else {
if (Tcl_SetVar2Ex(ip, name, NULL, argv[2], TCL_GLOBAL_ONLY | TCL_LEAVE_ERR_MSG) == NULL) {
return TCL_ERROR;
}
}
return TCL_OK;
}
int Gset_Init(Tcl_Interp *ip)
{
Tcl_CreateObjCommand(ip, "gset", proc_gset, NULL, NULL);
return Tcl_PkgProvide(ip, "gset", "1.0");
}
$ gcc -O -shared -fPIC -o libgset.so gset.c -ltcl
$ tclsh
% load libgset.so
% gset
wrong # args: should be "gset varName ?value?"
% gset a
can't read "a": no such variable
% gset a 1
% gset a
1
% proc foo {} {
set a 10
puts $a
gset a 100
puts [gset a]
puts $a
}
% foo
10
100
10
% gset a
100