3.4 TclObject 类
当建立解释器的对象时,TclObject类会提供建立编译影子对象的方法。TclObject类属于C++类,与OTcl域中的SplitObject类相对应。这两个类是各自体系内除独立类以外的其他所有类的基类。当OTcl域内的一个对象开始初始化时,会调用基类SplitObject的构造函数来完成初始化。其中一项就是影子对象的初始化。
3.4.1 TclObject 类的引用
OTcl类和C++类接入他们各自对象的方法并不相同。作为编译器,C++直接访问分配给其对象的内存空间。作为解释器,OTcl并不直接访问内存空间,而是用一字符串作为对象的引用(句柄)。NS2规定,SplitObject的这个字符串的命名形式为_
例子3.8 假设变量c_obj和otcl_obj分别是C++和OTcl类的一个对象。表3.2列出了这两个C++和OTcl对象的引用值。
我们可以用下列代码来查看存在OTcl对象内的值:
set ns [new Simulator]
set tcp [new Agent/Tcp]
puts “The value of tcp is $tcp”
结果如下: "The value of tcp is _o10”
3.4.1 生成和销毁TclObject的影子对象
大多数情况下,TclObject的影子函数在OTcl域内生成和销毁,通常在Tcl仿真脚本当中实现。OTcl用命令creat和destroy来生成和销毁一个独立的OTcl对象。但是,这两个命令在NS2中很少用到。因为他们并不能用来生成影子编译函数。在NS2中,全局函数new{…}和delete{}则经常被用来生成和销毁对象,包括影子编译对象。
生成TclObject对象
全局函数new{…}可以用来生成一个新的TclObject,语法为 new
Program 3.5列出了new{}函数实现的细节
new{classNameargs}函数有两个输入参数。第一个参数
new{classNameargs}函数的内部机制如下:首先第2行为对象检索了一个引用句柄,并存之于变量“O”。SplitObject类的实例程序getid()根据3.4.2小节中的的定义和命名规范生成引用句柄。而后,第3行生成了OTcl对象为className的对象,使其与存储在变量“O”的句柄对应。最后,如果对象被成功建立,11行返回引用句柄”O”给调用者,否者,屏幕上会出现错误信息(第9行)。
第3行的OTcl命令Create调用了实例程序alloc{…}来给className类的对象分配内存空间,然后init{…}函数初始化对象。在大多数情况下,init{…}会调用一个OTcl类的构造函数。每个类都会在构造函数中重载init{…}函数,定义自己的初始化步骤。
例子 3.9 产生一个OTcl Agent/TCP 对象,我们可以在仿真脚本中执行newAgent/TCP来实现。在解释体系中,Agent/TCP继承自Agent类,而Agent类继承自SplitObject.在编译体系,这三类则分别为TcpAgent类,Agent类和TclObject类。
图3.3画出了Agent/TCP的对象(o)的生成过程。第一步是调用SplitObject类的实例程序getid{…}来获取引用句柄。其次调用上层的init{…}来初始化。在最顶层,SplitObject调用creat-shadow函数来生成影子编译对象(图3.3右侧)。
creat-shadow返回之后,Agent/TCP的初始化函数完成剩余的初始化工作,程序继续进行下去,直至到达Agent/TCP.然后,函数返回creat函数和new函数,返回已生成的对象“o”至调用者。
注意,以上函数用于生成一个连接编译体系的解释型TclObject。独立的C++或者OTcl对象并不需要任何的影子对象,因此不需要上面的过程。他们可以以正常的构造函数完成。
销毁TclObject
OTcl使用实例程序delete{…}来销毁一个解释型对象和其影子函数(通过调用delete-shadow)。实例程序Simulator::use-scheduler{…}调用delete函数删除现有的scheduler(如果需要,第3行),并使用全局函数new{…}生成Scheduler/$type.我们将会在第4章详细讨论实例程序Simulator::use-scheduler{…}的细节。
3.4.3 在编译体系和解释体系内绑定变量
通常,无论是解释对象还是影子对象都有它们自己的类变量,不允许互相直接访问自己的变量。因此,NS2提供了一项机制来绑定两个体系的变量。在绑定之后,任何一方已绑定对象的改变都会自动的改变另一个体系的绑定对象。
绑定变量
NS2在影子对象函数的构造函数中将解释类变量绑定于编译对象。TclObject类在构造函数中调用以下特定函数来绑定变量。
其中,iname和cname分别代表解释体系和编译体系的变量。本质上来说,第一个参数和第二参数分别为解释器的变量句柄和编译变量的地址。
例子3.10.两个体系的Test类绑定。假设icount_,idelay_,ispeed_,ivirtuar_,iis_running_为OTcl类的变量,其类型分别为整数,实数,带宽,时间和布尔。下面这些代码为C++的Test类的构造函数:
所有的类变量都在编译体系下的构造函数中绑定。通常,我们约定,两个体系内需要绑定的变量名相同,在本例中只是为了说明起见。
设置默认值
NS2在~ns/tcl/lib/ns-default.tcl中设定绑定变量的初始值。语法如下:
为了设定变量的默认值,NS2调用了SplitObject的实例程序init-instvar{…}。该函数从~ns/tcl/lib/ns-default.tcl中读取变量的默认值并赋值给绑定变量。如果我们绑定了某个变量却没有设置默认值,实例函数SplitObject::warn-instvar{…}将会在屏幕上产生一个警告信息,但如果默认值设置的是一个无效值(例如,没有绑定或不存在),NS2不会打印警告信息。
3.4.4 OTcl command
3.2.2小节指出了一种从编译体系内访问解释体系的方法。本节讨论的恰好相反:如何从解释体系内访问编译体系,该方法称为“命令command”
回顾一下实例程序的调用机制
在我们具体讲解之前,让我们先回顾一下OTcl实例程序的调用机制。我们一般按下列步调用一个实例程序。
$obj
其中
i) 在对象类中查找对应的实例程序名,如果找到,执行对应的实例函数并返回,如果没有找到,执行下一步。
ii)查找实例函数unknow{…}.如果找到了,执行unknow{…}函数并返回,如果没有找到,执行下一步。实例程序unknown{…}是一个默认的实例程序,如果没有找到对应的实例程序,unknow{…}函数就会被调用。
iii)对对象的基类 重复步骤i)和ii)
iv) 如果调用至最顶层的基类,仍没有找到输入的实例程序和unknow{…}函数,报告错误并退出程序。
OTcl 命令调用
对OTcl命令的调用语法类似于实例程序: $obj
由于语法类似于调用实例程序,OTcl函数执行命令的方法类似于实例程序。下面,我们将讲述OTclAgent/TCP对象命令的调用机制(见程序3.9)。图3.4画出了命令调用机制的内部机制:
i) 执行 “$tcp
ii)在OTcl的Agent/TCP类中查找名为
iii)在OTcl的Agent/TCP类中查找unknown{…}函数,如果找到,调用unknown函数完成程序,否则,进行下一步。
iv)重复步骤ii)和iii),直至上溯到SplitObject类为止,若在继承树上的所有类中都没有找到unknown函数,NS2将会执行下列语句SplitObjectunknown。SplitObject类的unknown函数在~tclcl/tcl-object.tcl中定义。这里,将会执行"$self cmd$args”语句,其中args是unknown函数的输入参数,根据以上的调用,这语句变为 SplitObject cmd
v) cmd 实例程序将整个语句(i,e.,”cmd
vi)command(argc,argv)函数检查argv中的参数个数和argv[1]中的命令名。如果对应,则会激发需要的程序(程序3.9中的第6-7行),并返回TCL-OK。如果不对应,程序将会跳到最后一行。
vii) 程序3.9中的第12行调用了基类的command(argc,argv)函数。
viii)重复步骤vi)和vii),直至上溯至最顶层的类TclObject。若一直到TclObject类都没有相对应的命令,TclObject类的command函数将会报错,返回TCL_ERROR。
xi)返回下边的类体系中。当到达C++类的TcpAgent类,带着返回值返回OTcl类(分别为cmd函数和unknown函数),完成命令调用。
OTcl命令的另一种调用方法
在上一部分中,我们用这个语法调用OTcl命令:
$tcp
该命令起始于图3.4中的位置(1).但是,还有另外一种的调用方式,该方式图3.4中的位置(2),语法如下:
$tcp cmd
第二种调用方式避免了实例程序和OTcl命令相同时候的麻烦。
OTcl命令的返回机制
执行了C++的程序之后,NS2会返回适当的返回值,在nsallinone-2.30/tcl8.4.13/generic/tcl.h,NS2定义了以下5种返回值(0~5),如程序3.10.通知了解释器命令调用结果。
TCL_OK: 命令执行完全正确。
TCL_ERROR: 命令没有完成,解释器将会解释错误原因。
TCL_RETURN:从C++返回以后,解释器从当前的实例程序中退出,不运行剩下的实例程序。
TCL_BREAK:从C++返回以后,解释器终止当前循环,这类似于C++中的关键字break。
TCL_CONTINUE:从C++返回以后,解释器继续运行下面的程序。类似于C++中的关键字continue。
在以上5种类型中,TCL_OK和TCL_ERROR是最常用的两个。如果C++返回TCL_OK,解释器将会读取从C++域返回的值。回顾3.2.3节,解释器不读取返回值,而是阅读返回值指定的语句。TCL_OK命令只告诉OTcl,储存在tcl.result(…)的值是有效的。