之前有人问到如何从 C 端访问 Tcl 变量,此处做一总结。庶几不失「现代」之名。有关 Tcl_Obj 的维护者资料击此。
于是终于回到了主线。《Tcl 现代方法》本来是作为记录 Tcl 现代用法的系列博文,一开始是介绍 C 库的,但是后来 Tcl 脚本的内容反倒更多,结果现在纯粹谈起来 C 库又会导致歧义,因此在标题前加上 lib 标记,以示内容是 C 库而非 Tcl 脚本。
背景
Tcl 自 8.0 起,引入了 Tcl_Obj 系统,该系统在保留 Tcl 「Everything is a String」原则的同时,大大提高了程序效率,可以总结成这样:Tcl 变量存在一个显类型(Tcl 端类型)和隐类型(C 端类型),程序员脑中又有一个实用类型,显类型一定是字符串,但实际上实用类型并不一定是显类型,而使用隐类型的一种或多种,如 for 循环中的计数器 i,实用整数类型,而显类型则是字符串,隐类型应当是 int。
在过去,显类型和隐类型都是字符串,导致计数器 i 每次循环都会走一遍「字符串->整数->字符串」,效率极差。Tcl_Obj 则将字符串表示和隐类型数据一起保存,每当需要字符串时,就直接返回字符串,需要隐类型数据时,就直接返回隐类型数据;如果两者不匹配,则将其中过期了的一种更新。如每次 for 循环中如果有 puts $i,则会每次都更新一次 i 的字符串表示然后打印;如果在 for 循环外执行 puts $i,则只会造成一次字符串表示更新。更新隐类型数据同理。
说来好玩,因为一开始没用 Tcl_Obj,所以 Tcl 有一套相当强大的字符串处理函数,叫做 Tcl_DString(dynamic string),因为不推荐用于变量,而且比较直观,一看就懂,故而此处不做讨论。
另外注意,我提到的隐类型、显类型、实用类型,都是为了讨论方便自己提出的,实际上 Tcl 界乃至编程界并没有这样的说法(或许会有类似的讲法,如果你知道请务必告诉我)。
看上去好像很复杂,其实规则很简单:dual-ported (internal representation and string representation); cache of each other and computed lazily.
不过絮叨一大串,其实本文不介绍 Tcl_Obj 用法,只是个背景而已。
示例代码
A piece of code is worth a thousand words.
#include <tcl.h> int z = 0; void ObjRW(Tcl_Interp *interp) { Tcl_Obj *xObj; double val; /* Read */ xObj = Tcl_GetVar2Ex(interp, "x", NULL, 0); /* not incr'ing its refcount */ Tcl_GetDoubleFromObj(interp, xObj, &val); /* not incr'ing its refcount */ printf("x = %lf\n", val); /* Write */ Tcl_SetDoubleObj(xObj, 6.28); xObj = Tcl_SetVar2Ex(interp, "x", NULL, xObj, 0); /* not incr'ing its refcount */ /* Read (again) */ Tcl_GetDoubleFromObj(interp, xObj, &val); Tcl_DecrRefCount(xObj); printf("x = %lf (new)\n", val); } /* Inefficient in most cases. Use Tcl_Obj instead. */ void StrRW(Tcl_Interp *interp) { /* the string representation of y will be updated and returned */ printf("y = %s\n", Tcl_GetVar(interp, "y", 0)); Tcl_SetVar(interp, "y", "42", 0); /* now the "real" data will be outdated */ printf("y = %s (new)\n", Tcl_GetVar(interp, "y", 0)); } void LinkedVar(Tcl_Interp *interp) { Tcl_Obj *zObj; int value; /* Original value */ zObj = Tcl_GetVar2Ex(interp, "z", NULL, 0); /* Tcl simply returns our z */ Tcl_GetIntFromObj(interp, zObj, &value); printf("z = %d\n", value); Tcl_DecrRefCount(zObj); /* delete the old obj */ /* C-side */ z = 10; zObj = Tcl_GetVar2Ex(interp, "z", NULL, 0); /* not incr'ing its refcount */ Tcl_GetIntFromObj(interp, zObj, &value); printf("z = %d (C new)\n", value); /* Tcl-side */ Tcl_SetIntObj(zObj, 100); Tcl_SetVar2Ex(interp, "z", NULL, zObj, 0); /* update the var */ Tcl_DecrRefCount(zObj); printf("z = %d (Tcl new)\n", z); /* our own z got updated */ } int main() { Tcl_Interp *interp; Tcl_Obj *var1, *var2; /* var1 double, var2 int */ /* Initialize */ interp = Tcl_CreateInterp(); /* Create Tcl_Obj's */ var1 = Tcl_NewDoubleObj(3.14); var2 = Tcl_NewIntObj(99); /* Create variables */ /* Not incrementing the refcount means we transfer the ownership */ Tcl_SetVar2Ex(interp, "x", NULL, var1, 0); /* NULL means x isn't an array */ Tcl_SetVar2Ex(interp, "y", NULL, var2, 0); Tcl_LinkVar(interp, "z", (void *)&z, TCL_LINK_INT); /* MAGIC! */ /* Read/write them */ ObjRW(interp); StrRW(interp); LinkedVar(interp); /* Finalize */ Tcl_UnsetVar(interp, "x", 0); Tcl_UnsetVar(interp, "y", 0); Tcl_UnsetVar(interp, "z", 0); Tcl_DeleteInterp(interp); return 0; }
参考文献
(写这么一节参考文献,应该能让我的博客看上去更专业吧)
- Tcl Source Code Wiki
- Tcl man pages
- 《Tcl/Tk 入门经典》,中文版第二版
Oct 23, 2014 08:56:57 AM
这么古老的东西,好玩么
Oct 25, 2014 10:27:05 AM
@rca: 很有意思的,和 Lisp 的可玩性差不多。不过毕竟确实老了些,没有闭包,但是其他东西都很全。
Aug 30, 2021 12:05:56 AM
I propose merely very good along with reputable data, consequently visualize it:
Sep 25, 2021 05:17:55 AM
Profit primarily prime quality items -- you can understand them all within: