●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