上次完成了在QT中使用C++调用Tcl解释器的任务,这次将会为Tcl解释器增加一些自定义的命令,用来提高用户的操控性。这次的目标是自定义一个length的命令,用来计算平面点之间的坐标,输入形式按照: length x1 y1 x2 y2的形式输入。Tcl解释器截取length命令后调用C++程序完成点(x1, y1)和(x2, y2)距离的计算。因此我们先写一个简单的Tcl测试程序:
1 2 3 |
for {set i 0} {$i < 3} {incr i 1} { length $i [expr 2*$i] 3 4 } |
这样计算的是一系列点与点(3, 4)之间的距离。上面的代码直接使用Tcl解释器是不能运行的,因为解释器会提示length为未知关键字,因此需要我们为Tcl解释器加一个“壳子”,包装的代码如下所示:
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 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
#include <QCoreApplication> #include <iostream> #include <QDebug> #include "tcl.h" using namespace std; int length(ClientData clientData, Tcl_Interp *interp, int argc, Tcl_Obj* const *argv); int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); Tcl_Interp *interp = Tcl_CreateInterp(); Tcl_CreateObjCommand(interp, "length", &length, (ClientData)NULL, (Tcl_CmdDeleteProc*)NULL); int Res; const char *detail; Res = Tcl_EvalFile(interp, "E:/QT/tcltest/tcltest/test.tcl"); detail = Tcl_GetStringResult(interp); if (Res != TCL_OK) { cout<<"Failed!"<<endl; cout<<detail<<endl; } else { cout<<"Success!"<<endl; } Tcl_DeleteInterp(interp); return a.exec(); } int length(ClientData clientData, Tcl_Interp *interp, int argc, Tcl_Obj *const *argv) { double x_1 = QString(Tcl_GetString(argv[1])).toDouble(); double y_1 = QString(Tcl_GetString(argv[2])).toDouble(); double x_2 = QString(Tcl_GetString(argv[3])).toDouble(); double y_2 = QString(Tcl_GetString(argv[4])).toDouble(); cout<<"Point_1 Coord: "<<x_1<<" "<<y_1<<endl; cout<<"Point_2 Coord: "<<x_2<<" "<<y_2<<endl; double res = sqrt(pow((x_1-x_2),2)+pow((y_1-y_2),2)); cout<<"Length: "<< res <<endl; cout<<"=============================="<<endl; return TCL_OK; } |
关于Tcl解释器的配置,可以看我之前写的C++(QT)调用TCL解释器。这里主要说几个命令:
Tcl_CreateObjCommand命令用于向Tcl解释器增加一个自定义命令,在这个命令里面,需要指定一个增加命令的解释器,命令的名称,以及处理这个命令调用的函数的这里定义的是length命令,当Tcl解释器遇到length命令的时候,会在C++中调用相应的length函数执行命令,这样我们就可以“截取”我们自定义的命令。
下面根据官方API介绍一下Tcl_CreateObjCommand命令的参数:
1 |
Tcl_CreateObjCommand(interp, cmdName, proc, clientData, deleteProc) |
- Tcl_Interp *interp (in): 解释器指针;
- char *cmdName (in): 命令名称;
- Tcl_ObjCmdProc *proc (in): 执行cmdName的时候会调用proc函数;
- ClientData clientData (in): 传递给proc或者deleteProc的参数;
- Tcl_CmdDeleteProc *deleteProc (in): 在cmdName删除之前解释器会调用deleteProc函数执行一些用户自定义的操作;
创建的length函数格式如下:
1 |
int length(ClientData clientData, Tcl_Interp *interp, int argc, Tcl_Obj *const *argv) |
clientData与interp由Tcl_CreateObjCommand命令传递过来,argc表示命令的数量,argv则是一个Tcl_Obj的指针数组,这两个东西看上去复杂用起来比较简单。结合我们的length命令:
1 |
length $i [expr 2*$i] 3 4 |
argc为5表示总共五个参数,包括length本身,而argv[0]~argv[4]指代这四个参数的Tcl_Obj,使用Tcl_GetString()函数可以转换Tcl_Obj为char类型指针,具体可以看上面完整的程序代码。
因此我们运行上面的命令,可以得到下图所示的结果:
这样就成功的将自定义length命令中的两个点的坐标和长度计算出来了。这也是OpenSees内部处理OpenSees自定义命令的方法。像node, element, EqualDof等等的命令都是OpenSees通过这种方式创建的。当用户加载OpenSees的Tcl脚本时,OpenSees内部通过上述方式获取node的坐标,element的单元类型,然后将这些信息存放在内存中,组成矩阵计算结构响应。
PS: 这篇文章很早就写了一半,无奈教研室网络虽然速度飞快,但是和我服务器的连接很不稳定,50%+的掉包使得网站一直难以更新。。。