NS2支持C++变量和Tcl变量的双向绑定,这样,能通过它们访问同一个数据,而对其中一个变量做出了修改,与之绑定的变量也被修改.
NS2支持5种不同的类型:实数(reals)、整数(integers)、时间(timevalued variables)、带宽(bandwidthvalued variables)、布尔(booleans).
如下说明了5中类型的变量的绑定方法:
函数中,参数1指定了Tcl变量的名称,参数2指定了对应的C++变量的地址。
下面,我们跟踪bind函数,来大致了解其工作原理。
在tcl/Tcl.cc文件中,有这样的一段代码,实际上是定义了9个函数
#defineTOB(FUNCTION, C_TYPE, INSTVAR_TYPE, OTHER_STUFF) \
voidTclObject::FUNCTION(const char* var, C_TYPE* val) \
{\
create_instvar(var); \
OTHER_STUFF; \
init(new INSTVAR_TYPE(var, val), var); \
}
TOB(bind, double,InstVarReal, ;)
TOB(bind_bw, double,InstVarBandwidth, ;)
TOB(bind_time,double, InstVarTime, ;)
TOB(bind, int,InstVarInt, ;)
TOB(bind, unsignedint, InstVarUInt, ;)
TOB(bind_bool, int,InstVarBool, ;)
TOB(bind,TclObject*, InstVarTclObject, ;)
TOB(bind, TracedInt,InstVarTracedInt, val->name(var); val->owner(this);)
TOB(bind,TracedDouble, InstVarTracedReal, val->name(var); val->owner(this);)
其中InstVar***都是class InstVar的派生类,例如InstVarReal
class InstVarReal :public InstVar {
public:
InstVarReal(const char* name, double* val)
:InstVar(name), val_(val) {}
constchar* snget(char *wrk, int wrklen) {
if(-1 == snprintf(wrk, wrklen, "%.17g", *val_))
abort();
return(wrk);
}
voidset(const char* s) {
*val_= atof(s);
}
protected:
double*val_;
};
继续看bind函数调用的create_instvar(),定义如下
void
TclObject::create_instvar(constchar* var)
{
/*
* XXX can't use tcl.evalf() because it usesTcl_GlobalEval
* and we need to run in the context of themethod.
*/
charwrk[256];
sprintf(wrk,"$self instvar %s", var);
Tcl_Eval(Tcl::instance().interp(),wrk);
}
注意TclObject类是所有服从分裂对象模型的C++类的基类。可见,该函数执行了Tcl命令:$self instvar name
需要说明的是,instvar{}最初是在otcl_Init()函数(otcl/otcl.c)中被添加的,如下
OTclAddIMethod(theobj,"instvar", (Tcl_CmdProc *) OTclOInstVarMethod, 0, 0);
PS:在TclClass::create_shadow()函数(在创建OTcl对象时被调用)中,也添加了instvar{}
OTclAddPMethod(OTclGetObject(interp,argv[0]), "instvar",
(Tcl_CmdProc *) dispatch_instvar,(ClientData)o, 0);
而且,dispatch_instvar()部分代码跟OTclOInstVarMethod()一样的.但是,该添加操作是在构造TclObject对象之后发生的(create_shadow()调用create()构造了该对象,OTclAddPMethod()在create()之后被调用),而对bind()的调用发生在构造函数中,所以第一次构造TclObject对象时,用的是otcl_Init()添加的OTclOInstVarMethod()。(此处还需验证)
继续看bind()函数中的init(new INSTVAR_TYPE(var, val), var);
首先new了一个新的InstVar***对象,然后调用TclObject::init()
voidTclObject::init(InstVar* v, const char* var)
{
insert(v);
v->init(var);
}
每个TclObject对象维护了一个instvar_链表,记录绑定的变量的信息.
再来看InstVar::init()
voidInstVar::init(const char* var)
{
charwrk[256];
sprintf(wrk,"$self init-instvar %s", var);
if(Tcl_Eval(Tcl::instance().interp(), wrk) != TCL_OK) {
/*XXXcan only happy if TclObject::init-instvar broken */
Tcl::instance().evalf("putsstderr \"init-instvar: $errorInfo\"");
exit(1);
}
}
函数运行Tcl命令 $selfinit-instvar var来初始化变量
init-instvar{}定义在tclcl/embedded-tclobj.cc中
SplitObjectinstproc init-instvar var {
set cl [$self info class]
while { \"$cl\" != \"\" &&\"$cl\" != \"SplitObject\" } {
foreach c $cl {
if ![catch \"$c set $var\" val] {
$self set $var $val
return
}
}
set parents \"\"
foreach c $cl {
if { $cl != \"SplitObject\" && $cl !=\"Object\" } {
set parents \"$parents [$c info superclass]\"
}
}
set cl $parents
}
$self warn-instvar [$self info class]::$var
}
该过检查对象和其所有的父类,找到第一个定义var变量的类,使用该类的变量的值初始化var,大部分的这样的变量定义在tcl/lib/ns-default.tcl中,例如
需要特别注意的是,没修改一次默认值,都需要重新编译NS2,方能使之生效.
在ns2源码目录下的Makefile文件中,
NS_TCL_LIB = \
tcl/lib/ns-compat.tcl \
tcl/lib/ns-default.tcl \
tcl/lib/ns-errmodel.tcl \
tcl/lib/ns-lib.tcl \
......
$(NS_TCL_LIB_STL)
$(GEN_DIR)ns_tcl.cc: $(NS_TCL_LIB)
$(TCLSH) bin/tcl-expand.tcl tcl/lib/ns-lib.tcl $(NS_TCL_LIB_STL) | $(TCL2C) et_ns_lib > $@
(原以为,Tcl脚本会被ns程序解释的,原来是被链接进去了)
如果没有指定默认值,在Tcl中new的时候,会打印警告信息,但是不会影响程序执行(还没有发现什么问题)
PS:set{}也是定义在tclcl/embedded-tclobj.cc中,
注意看$self instvar -parse-part1 $var,再次调用了instvar。奇怪的是,OTclOInstVarMethod()没有对-parse-part1选项的处理操作,而在dispatch_instvar()则有相应的处理。(已经用eclipse搜索了pare-part1,只在dispatch_instvar()找到)