Tcl/Tk同Python一样,是一种高级语言。所不同的是,Python追求高大全,几乎我们能想得到的所有计算机领域都有Python的库,甚至如CUDA调用,符号计算,3D显示等都可以。Tcl却恰恰相反,它追求的是小巧精致,尽管没有那么华丽的库,但这也是它的优点。整个Tcl/tk库可以简单而无缝的嵌入到任何人自己开发的程序而不使程序臃肿变大很多,这使得程序功能的提升具有无限的潜力。在很多EDA工具中都嵌入了Tcl环境。一些科学应用的程序如Hyperchem也嵌入了Tcl,做的最好的当属VMD。可以说,如果没有tcl,VMD的功能得打折90%!
这篇文章主要的内容,就是试图介绍一下如何将Tcl嵌入到自己编写的程序中。这里,默认读者已经熟悉Tcl/tk语言和简单的C语言编程。当然,我们不会将Tcl的全部API介绍一遍,这里只介绍一些最重要的函数。我们使用Tcl8.5.
如果需要tcl/C的更详细的文档,可以阅读Tcl.h中的注释。
1 准备
Tcl/tk是横跨Windows/Linux/Mac的语言,在各个环境下它的目录结构都类似。对于编程人员,最重要的是/lib和/include,它们分别是Tcl/tk的链接库和头文件文件夹。因此,在编译程序时,记得将这些文件夹加入到-L和-I选项中,并且开启-ltcl85 -ltk85开关。如果使用gcc,直接将这些选项加入到命令行即可,如果用Visual C++,记得修改Project的setting。
2 “Hello World!”
现在我们开始编写第一个Tcl/C混合程序。
好啦,evod已经诞生了。
1) 我们在Tcl_AppInit添加了evod这个命令:
然后在evod_proc中实现,我们发现,对argc argv的使用方法和一般C语言的使用方法一样,就不再赘述。如果命令错误,则返回TCL_ERROR,如果顺利则返回TCL_OK。
2) 对于输出的字符串,我们使用Tcl_SetResult(interp, buffer, TCL_XX);其中buffer是要显示的字符串的缓冲区。第三个参数是结果的“释放方式”。如果是静态字符串,通常可以用TCL_STATIC,如果是某种堆栈变量,就用TCL_VOLATILE。如果这个字符串是通过Tcl_Alloc动态分配的,就设为TCL_DYNAMIC。
3) Tcl_GetInt是一个读取整数的函数。
4 扩展新命令:Tcl_Obj方法
这种单纯使用字符串的方法比较简单,但是原始。TCL8.5似乎更倾向使用Tcl_Obj的方法。请看下面的代码,它可以实现与上述代码完全相同的功能:
}
我们对比一下,
1) 命令的创建由Tcl_CreateCommand改为Tcl_CreateObjCommand;
2) 命令的格式:
int cmdName(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
这里,CONST是TCL定义的一个宏,可以理解为一个跨平台的const。
3) 设置结果时,Tcl_SetResult变为Tcl_SetStringObj,Tcl_SetIntObj或Tcl_SetDoubleObj等。其中第一个参数是interp->result的指针,我们用Tcl_GetObjResult来得到它。至于Tcl_SetStringObj,它不仅可以接受字符串,还可以是任意的二进制串;第三个参数是表明串中止的符号。对于通常的字符串,如果以’\0’结尾,则为-1。
4) Tcl_WrongNumArgs是一个简单的函数,直接格式化成标准的“参数个数不对”的字符串。
5) Tcl_Obj方法的好处是可以很容易的在各种格式间转换。事实上,对于任意一个类型XX,都存在
Tcl_SetXXObj(resultPtr, value)
Tcl_GetXXFromObj(interp, objPtr, valuePtr)
Tcl_NewXXObj(resultPtr, value)
如果自己定义了一些特殊结构,则可以定义相应的这类函数来直接操作Tcl_Obj。已经内置的XX有String,Int,Double等。
5 扩展新命令:使用列表
也许有人会对Tcl_Obj的使用感到厌恶。但是我强烈建议在编写Tcl/C程序是使用这种方法。它使得编程变得更加统一。看一个例子。我们知道,tcl中有一个非常好用的数据结构list,我们可以通过Tcl_Obj的方法很容易的在底层访问list。如果用字符串的方法,这将非常麻烦。
现在改进evod,使它的参数变为一个列表,返回也是一个列表:偶数为0,奇数为1,非整数为-1.代码见下:
#include <tcl.h>
#include <stdio.h>
int evod_objproc(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
{
int num;
int a, i;
Tcl_Obj **list;
Tcl_Obj *result = Tcl_NewListObj(0, NULL);
if(objc != 2)
{
Tcl_WrongNumArgs(interp, 1, objv, “?list?”);
return TCL_ERROR;
}
else
{
if(Tcl_ListObjGetElements(interp, objv[1], &num, &list) != TCL_OK)
{
Tcl_SetStringObj(Tcl_GetObjResult(interp), “Internal Error!”, -1);
return TCL_ERROR;
}
for(i = 0; i < num; i++)
{
if(Tcl_GetIntFromObj(interp, list[i], &a) != TCL_OK)
Tcl_ListObjAppendElement(interp, result, Tcl_NewIntObj(-1));
else
Tcl_ListObjAppendElement(interp, result, Tcl_NewIntObj(a%2));
}
Tcl_SetObjResult(interp, result);
return TCL_OK;
}
}
int Tcl_AppInit(Tcl_Interp *interp)
{
if(Tcl_Init(interp) != TCL_OK) return TCL_ERROR;
//Tcl_CreateCommand(interp, “evod”, evod_proc, (ClientData)0, 0);
Tcl_CreateObjCommand(interp, “evod”, evod_objproc, (ClientData)0, 0);
return TCL_OK;
}
int main(int argc, char** argv)
{
printf(“— Tcl Third-Party Shell Start —\n”);
Tcl_Main(argc, argv, Tcl_AppInit);
printf(“— Tcl Third-Party Shell End —\n”);
return 0;
}
1) 我们用Tcl_ListObjGetElements得到列表。可见第四个参数是一个Tcl_Obj指针数组(三重指针),他就是列表,第三个参数返回了列表长度.
2) Tcl_ListObjAppendElement向列表添加Tcl_Obj.注意我们用了Tcl_NewListObj和Tcl_NewIntObj轻易的产生了Tcl_Obj。
3) Tcl_SetObjResult可将任意Tcl_Obj导入结果中。
可见使用Tcl_Obj具有很大的优越性!
6 离开Tcl_Main
我们以上的程序似乎只是个TCL的shell。如果把主程序改成如下的样子,就可以更加的灵活:
int main(int argc, char** argv)
{
printf(“— Tcl Third-Party Shell Start —\n”);
//Tcl_Main(argc, argv, Tcl_AppInit);
Tcl_Interp *interp;
interp = Tcl_CreateInterp();
Tcl_CreateObjCommand(interp, “evod”, evod_objproc, (ClientData)0, 0);
Tcl_Eval(interp, “puts [evod {56 37 love -5.8}]”);
printf(“— Tcl Third-Party Shell End —\n”);
return 0;
}
好了,经过这些讲解,原则上你可以将tcl嵌入你自己编的任何程序了,只要你再多熟悉一些API。