Tcl/C++混合编程,将Tcl嵌入C++程序
参考链接:
Tcl/Tk同Python一样,是一种高级语言。所不同的是,Python追求高大全,几乎我们能想得到的所有计算机领域都有Python的库,甚至如CUDA调用,符号计算,3D显示等都可以。Tcl却恰恰相反,它追求的是小巧精致,尽管没有那么华丽的库,但这也是它的优点。整个Tcl/tk库可以简单而无缝的嵌入到任何人自己开发的程序而不使程序臃肿变大很多,这使得程序功能的提升具有无限的潜力。在很多EDA工具中都嵌入了Tcl环境。这篇文章主要的内容,就是试图介绍一下如何将Tcl嵌入到自己编写的程序中。这里,默认读者已经熟悉Tcl/tk语言和简单的C语言编程。当然,我们不会将Tcl的全部API介绍一遍,这里只介绍一些最重要的函数。
测试环境:
1
2
# Ubuntu 20.04.6
# Tcl8.6
1. 配置环境
1
2
3
4
5
# 安装Tcl 解释器
sudo apt update
sudo apt install tcl
# 安装头文件和库
sudo apt install tcl-dev
通常,头文件会被安装在 /usr/include/tcl8.x/
目录下(例如 /usr/include/tcl8.6/
)。
2. 示意
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <tcl.h>
#include <stdio.h>
int Tcl_AppInit(Tcl_Interp *interp){
return (Tcl_Init(interp) == (TCL_ERROR))?TCL_ERROR: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;
}
编译:g++ -o tcl_test main.cpp -I /user/include -ltcl
可以看到我们的程序似乎是一个Tcl的shell,此时可以在终端执行tcl命令,例如exit
或者for {set i 0} {$i < 5} {incr i} {puts $i}
,前者退出shell,后者将0~4输出在终端。
现在分析一下这个程序。
- 首先必须导入tcl的头文件
- main函数中,我们调用了一个Tcl函数:Tcl_Main(argc, argv, Tcl_AppInit)。这个函数的第三个参数是一个TCL初始化函数的指针,这个函数必须由自己定义。
- 在Tcl_AppInit(Tcl_Interp *interp)中,interp是一个tcl解释器的指针,它必须由Tcl_Init来初始化。
- Tcl_Main之前的C语言与平时的编程没什么不同。而调用Tcl_Main之后,程序中开启了一个tcl的shell,在这个shell结束后,后面的命令并没有执行,而是直接退出了,也就是说Tcl_Main并不会return
我们这个简单的程序算是一个完整的TCL/C程序了。
3. 自定义命令
这里我们使用Tcl_CreateCommand来创建自定义命令,函数如下:
1
2
Tcl_Command
Tcl_CreateCommand(Tcl_Interp *interp, const char *cmdName, Tcl_CmdProc *proc, ClientData clientData, Tcl_CmdDeleteProc *deleteProc)
proc是实现名字为cmdName的新命令的函数,clientData在TK编程时很有用,可以设为0;deleteProc是调用Tcl_DeleteCommand时自动先调用的函数指
针,目前也设为0。
这里定义两个命令:test与test2。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int test(ClientData clientData, Tcl_Interp* interp, int argc, const char** argv) {
printf("test create command...\n");
return TCL_OK;
}
int test2(ClientData clientData, Tcl_Interp* interp, int argc, const char** argv) {
printf("test2 create command...\n");
return TCL_OK;
}
int Tcl_AppInit(Tcl_Interp* interp) {
if(Tcl_Init(interp) != TCL_OK)
return TCL_ERROR;
Tcl_CreateCommand(interp, "test", test, 0, 0);
Tcl_CreateCommand(interp, "test2", test2, 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;
}
添加command之后,就可以在上文示意的Tcl shell中执行test与test2,输出结果为:
1
2
3
4
5
6
% test
test create command...
% test2
test2 create command...
% test3
invalid command name "test3"
4. 关于source命令
Tcl的source命令可以解析一个.tcl脚本文件,例如上述代码,可以将test与test2写入cmd.tcl文件,如果要进行解析,有三种方式:
Tcl shell中执行 source ./cmd.tcl
直接在终端执行 ./tcl_test ./cmd.tcl
在Tcl_AppInit中使用Tcl_Eval函数执行命令
1 2 3 4 5 6 7 8 9 10 11 12 13
int Tcl_AppInit(Tcl_Interp* interp) { if(Tcl_Init(interp) != TCL_OK) return TCL_ERROR; Tcl_CreateCommand(interp, "test", test, 0, 0); Tcl_CreateCommand(interp, "test2", test2, 0, 0); // source tcl file std::string cmd = "source ./cmd.tcl"; int code = Tcl_Eval(interp, cmd.c_str()); return code; }
三种方式都会顺序执行test与test2命令。
最基础的使用方式就是这样,其他函数可以看文档。