http://lauwd.blog.163.com/blog/static/7605696920107429017/
TCL是一个解释型的语言,但是功能相当强大,一个重要原因就在于它的扩展性,现有的诸如http,socket,xml,oratcl等等,使得tcl可以轻松处理字符串、文件、通信以及数据库等等多方面的工作,甚至支持多线程。
TCL的扩展可以通过调用tcl library来完成,具体的开发语言可能有多种,本文第一部分将以c/c++为例讨论。
此外,缘于客户化的需要,也有可能在c/c++代码中调用tcl的咚咚,例如执行脚本啊,配置文件什么的,具体可见第二部分。
一.tcl中调用c/c++
主要是利用c/c++处理复杂逻辑的能力,对于tcl来说,实际上就是一个扩展,因为你可以通过Tcl_CreateCommand函数,创建出一个新的tcl命令。
具体操作方法需要分情况:
1.如果当前tcl版本支持load命令
语法:load libpackage.so
意义:在tcl中,当一个动态库libpackage.so被装载时,tcl会调用其中名为package_Init的函数,记住,名字一定不能错,包括大小写。这样,你就获得了一个入口,可以进入c/c++啦,你可以干任何事,当然,最重要的还是Tcl_CreateCommand了。
函数原型:Tcl_Command Tcl_CreateCommand(interp, cmdName, proc, clientData, deleteProc)
意义:创建一个新的tcl命令cmdName,对应的操作函数指针为proc。
这里以一个阶乘算法为例:目的是提供一个名为myfract的tcl命令,只接收一个参数,例如:
myfract 10
表示计算10的阶乘。
【fract.c】:
#include "tcl.h"
int Tcl_myfract(ClientData notUsed, Tcl_Interp *interp, int argc, char **argv)
{
int i, j;
double res=1.0;
char re[30];
if (argc > 2)
{
Tcl_SetResult(interp, "wrong args: should be myfract", TCL_VOLATILE);
return TCL_ERROR;
}
if (Tcl_GetInt(interp, argv[1], &i) != TCL_OK)
{
return TCL_ERROR;
}
for (j=1;j<=i;j++)
res *= j;
sprintf(re,"%le",res);
Tcl_SetResult(interp, re, TCL_VOLATILE);
return TCL_OK;
}
int Fract_Init(Tcl_Interp *Interp) {
Tcl_CreateCommand (Interp, "myfract", (Tcl_CmdProc *)Tcl_myfract, (ClientData)0, 0);
return TCL_OK;
}
【Makefile】:
t = libfract.so
all: $t
clean:
rm -f $t core
libfract.so: fract.c
gcc -I. -shared -o $@ fract.c
【test.tcl】:
#!/usr/bin/tclsh
load ./libfract.so
set tcl_flag [catch {myfract 2} return_str]
if {$tcl_flag != 0} {
puts "OK:$return_str"
} else {
puts "Error:$return_str"
}
运行:
linux:~/test/tcl # ./test.tcl
2.000000e+00
【小结】:这里有两点需要注意,第一,Fract_Init是由TCL到c/c++的入口,而Tcl_CreateCommand则是由c/c++到TCL的入口;第二,编译动态库时不必链接tcl的开发库,因为这里仅仅需要引用。
还有swig可以使用
【fract.i】:
%module fract
extern double myfract(int);
swig的语法这里就不详述了,具体可参见 www.swig.org。
第一行表示模块名称叫做fract,对应前面的c/c++代码,就是说要创建的动态库叫做libfract.so;
第二行表示将要导出的函数,这个函数是需要你自己定义的。
【fract.c】:
double myfract(int n)
{
double res=1.0;
int j;
for (j=1;j<=n;j++)
{
res *= j;
}
return(res);
}
【Makefile】:
t = fract_wrap.c libfract.so
all: $t
clean:
rm -f $t core *.doc *.o
fract_wrap.c: fract.i
swig -tcl fract.i
libfract.so:fract_wrap.c
gcc -c fract.c fract_wrap.c
运行:
linux:~/test/tcl/swig # ./test.tcl
2.0
2.如果当前tcl版本不支持load命令
我不知道哪个版本会这么原始,但即便如此,仍然有解决方法,那就是把扩展模块静态链接到执行文件中去,具体操作如下:
【main.c】:
#include <tcl.h>
#include <stdio.h>
int Tcl_AppInit(Tcl_Interp *interp);
int main(int argc, char *argv[]) {
Tcl_Main(argc, argv, Tcl_AppInit);
}
int Tcl_AppInit(Tcl_Interp *interp) {
/* Initialize Tcl */
if (Tcl_Init(interp) == TCL_ERROR) {
return TCL_ERROR;
}
/* Initialize our extension */
if (Fract_Init(interp) == TCL_ERROR) {
return TCL_ERROR;
}
return TCL_OK;
}
【Makefile】:
t = main
TCL_LIBS = -L/usr/lib -ltcl8.4
all: $t
clean:
rm -f $t core
main:main.c fract.c
gcc -I. ${TCL_LIBS} -o $@ main.c fract.c
运行:
linux:~/test/tcl # ./main
% myfract 8
4.032000e+04
% exit
linux:~/test/tcl # ldd main
linux-gate.so.1 =>
(0xffffe000)
libtcl8.4.so => /usr/lib/libtcl8.4.so (0x40030000)
libc.so.6 => /lib/tls/libc.so.6 (0x400d8000)
libdl.so.2 => /lib/libdl.so.2 (0x401f1000)
libm.so.6 => /lib/tls/libm.so.6 (0x401f5000)
/lib/ld-linux.so.2 (0x40000000)
我们发现,代码中重写了Tcl_AppInit函数,在tcl手册中该函数被形容为一个``hook'' procedure,很奇妙的东西。
因为最后会创建一个类似tclsh的执行文件,所以,tcl开发库必须链接上。
二.c/c++中调用tcl
之所以会有这样的做法,主要是想利用tcl的客户化能力。
例如把流程写在tcl脚本中,c/c++代码中仅仅执行Tcl_EvalFile就可以了,对于不同的服务,我们可以借助某种手段,绑定一个流程脚本,这样,当增加新的服务时或者服务流程需要变更时,不必修改源码,只要增加或修改配置文件/脚本文件即可,这也就实现了我们所说的客户化。
我们只讨论几个常用的函数:
Tcl_CreateInterp
- 创建一个tcl解释器
Tcl_Eval
- 执行一个tcl命令
Tcl_VarEval
- 类似Tcl_Eval,只不过这个命令是被参数串起来的
Tcl_EvalFile
- 执行一个tcl脚本
Tcl_SetVar
- 设置tcl变量
Tcl_GetVar
- 获取tcl变量的值
下面是一个用法测试:
【test.c】:
#include <tcl.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
int main()
{
Tcl_Interp* interp;
char
*p;
int tclres;
//create interpreter
interp = Tcl_CreateInterp();
/*
* Test Tcl_SetVar
*/
char *varname = "SERVICE_NAME";
char *varval = "MyServer";
p = (char *)Tcl_SetVar(interp, varname, varval, TCL_GLOBAL_ONLY);
assert ( p!=NULL && !strncmp(p, "MyServer", 8) );
/*
* Test Tcl_GetVar
*/
p = (char *)Tcl_GetVar(interp, "SERVICE_NAME", TCL_GLOBAL_ONLY);
assert ( p!=NULL && !strncmp(p, "MyServer", 8) );
/*
* Test Tcl_Eval
*/
tclres = Tcl_eval_r(interp, "set CFG_val world");
assert ( tclres == TCL_OK) ;
p = (char *)Tcl_GetVar(interp, "CFG_val", TCL_GLOBAL_ONLY);
assert ( p!=NULL && !strncmp(p, "world", 5) );
/*
* Test Tcl_VarEval
*/
char *home = "./";
tclres = Tcl_Vareval_r(interp, "source ", home, "test.cfg", NULL) ;
assert ( tclres == TCL_OK) ;
p = (char *)Tcl_GetVar(interp, "CFG_val", TCL_GLOBAL_ONLY);
assert ( p!=NULL && !strncmp(p, "hello", 5) );
/*
* Test Tcl_EvalFile
*/
char *path = "./test.cfg";
tclres = Tcl_EvalFile(interp, path);
assert ( tclres == TCL_OK) ;
p = (char *)Tcl_GetVar(interp, "CFG_val", TCL_GLOBAL_ONLY);
assert ( p!=NULL && !strncmp(p, "hello", 5) );
//END
printf("OK\n");
}
【Makefile】:
t = test
TCL_LIBS = -L/usr/lib -ltcl8.4
all: $t
clean:
rm -f $t core
test: test.c
gcc -I. ${TCL_LIBS} -o $@ test.c
linux:~/test/tcl/test # ./test
OK
三.后记
在实践中,以上二者往往是联系在一起的,例如c/c++中调用的tcl脚本,也可以执行扩展命令(该命令完全可能是用c/c++代码实现的)。没有必要刻意去区分它们,正确的时候做正确的事,这就是原则。
四.参考
http://wiki.tcl.tk/SoftwarePorts?tcl
http://www.swig.org/papers/Tcl98/TclChap.html
http://www.tclchina.com/article/chinese/tclandswig.htm
一.tcl中调用c/c++
1.如果当前tcl版本支持load命令
语法:load libpackage.so
意义:在tcl中,当一个动态库libpackage.so被装载时,tcl会调用其中名为package_Init的函数,记住,名字一定不能错,包括大小写。这样,你就获得了一个入口,可以进入c/c++啦,你可以干任何事,当然,最重要的还是Tcl_CreateCommand了。
函数原型:Tcl_Command Tcl_CreateCommand(interp, cmdName, proc, clientData, deleteProc)
意义:创建一个新的tcl命令cmdName,对应的操作函数指针为proc。
这里以一个阶乘算法为例:目的是提供一个名为myfract的tcl命令,只接收一个参数,例如:
myfract 10
表示计算10的阶乘。
【fract.c】:
#include "tcl.h"
int Tcl_myfract(ClientData notUsed, Tcl_Interp *interp, int argc, char **argv)
{
}
int Fract_Init(Tcl_Interp *Interp) {
}
【Makefile】:
t = libfract.so
all: $t
clean:
libfract.so: fract.c
【test.tcl】:
#!/usr/bin/tclsh
load ./libfract.so
set tcl_flag
if {$tcl_flag != 0} {
} else {
}
运行:
linux:~/test/tcl # ./test.tcl
2.000000e+00
【小结】:这里有两点需要注意,第一,Fract_Init是由TCL到c/c++的入口,而Tcl_CreateCommand则是由c/c++到TCL的入口;第二,编译动态库时不必链接tcl的开发库,因为这里仅仅需要引用。
还有swig可以使用
【fract.i】:
%module fract
extern double myfract(int);
swig的语法这里就不详述了,具体可参见 www.swig.org。
第一行表示模块名称叫做fract,对应前面的c/c++代码,就是说要创建的动态库叫做libfract.so;
第二行表示将要导出的函数,这个函数是需要你自己定义的。
【fract.c】:
double myfract(int n)
{
}
【Makefile】:
t = fract_wrap.c libfract.so
all: $t
clean:
fract_wrap.c: fract.i
libfract.so:fract_wrap.c
运行:
linux:~/test/tcl/swig # ./test.tcl
2.0
2.如果当前tcl版本不支持load命令
【main.c】:
#include <tcl.h>
#include <stdio.h>
int Tcl_AppInit(Tcl_Interp *interp);
int main(int argc, char *argv[]) {
}
int Tcl_AppInit(Tcl_Interp *interp) {
}
【Makefile】:
t = main
TCL_LIBS = -L/usr/lib -ltcl8.4
all: $t
clean:
main:main.c fract.c
运行:
linux:~/test/tcl # ./main
% myfract 8
4.032000e+04
% exit
linux:~/test/tcl # ldd main
我们发现,代码中重写了Tcl_AppInit函数,在tcl手册中该函数被形容为一个``hook'' procedure,很奇妙的东西。
因为最后会创建一个类似tclsh的执行文件,所以,tcl开发库必须链接上。
二.c/c++中调用tcl
Tcl_CreateInterp
Tcl_Eval
Tcl_VarEval
Tcl_EvalFile
Tcl_SetVar
Tcl_GetVar
下面是一个用法测试:
【test.c】:
#include <tcl.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
int main()
{
【Makefile】:
t = test
TCL_LIBS = -L/usr/lib -ltcl8.4
all: $t
clean:
test: test.c
linux:~/test/tcl/test # ./test
OK
三.后记
四.参考
http://wiki.tcl.tk/SoftwarePorts?tcl
http://www.swig.org/papers/Tcl98/TclChap.html
http://www.tclchina.com/article/chinese/tclandswig.htm