NS2中OTcl类和C++类的连接(三)

3.4 TclObject 类

当建立解释器的对象时,TclObject类会提供建立编译影子对象的方法。TclObject类属于C++类,与OTcl域中的SplitObject类相对应。这两个类是各自体系内除独立类以外的其他所有类的基类。当OTcl域内的一个对象开始初始化时,会调用基类SplitObject的构造函数来完成初始化。其中一项就是影子对象的初始化。

3.4.1 TclObject 类的引用

OTcl类和C++类接入他们各自对象的方法并不相同。作为编译器,C++直接访问分配给其对象的内存空间。作为解释器,OTcl并不直接访问内存空间,而是用一字符串作为对象的引用(句柄)。NS2规定,SplitObject的这个字符串的命名形式为_<NNN>,其中NNN是由各自的SplitObject产生的唯一序号。例如,_o10.

例子3.8 假设变量c_obj和otcl_obj分别是C++和OTcl类的一个对象。表3.2列出了这两个C++和OTcl对象的引用值。

image

我们可以用下列代码来查看存在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<classname>[<args>]

Program 3.5列出了new{}函数实现的细节

image

new{classNameargs}函数有两个输入参数。第一个参数<className>(必须有)是OTcl的类名。第二个参数<args>(可选)是传给OTcl构造函数的输入参数。函数new{classNameargs}生成了一个对象,className即为该对象的OTcl类和相应的影子对象的类名。如果构造过程成功,函数会返回引用句柄(11行),否则,会将错误信息打印至屏幕。(9行)

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类。

image

图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{…}的细节。

image

3.4.3 在编译体系和解释体系内绑定变量

通常,无论是解释对象还是影子对象都有它们自己的类变量,不允许互相直接访问自己的变量。因此,NS2提供了一项机制来绑定两个体系的变量。在绑定之后,任何一方已绑定对象的改变都会自动的改变另一个体系的绑定对象。

绑定变量

NS2在影子对象函数的构造函数中将解释类变量绑定于编译对象。TclObject类在构造函数中调用以下特定函数来绑定变量。

image

其中,iname和cname分别代表解释体系和编译体系的变量。本质上来说,第一个参数和第二参数分别为解释器的变量句柄和编译变量的地址。

例子3.10.两个体系的Test类绑定。假设icount_,idelay_,ispeed_,ivirtuar_,iis_running_为OTcl类的变量,其类型分别为整数,实数,带宽,时间和布尔。下面这些代码为C++的Test类的构造函数:

image

所有的类变量都在编译体系下的构造函数中绑定。通常,我们约定,两个体系内需要绑定的变量名相同,在本例中只是为了说明起见。

设置默认值

NS2在~ns/tcl/lib/ns-default.tcl中设定绑定变量的初始值。语法如下:<className>set <instvar><def_value>.意为将<className>类的instvar变量<instvar>的值设为<def_value>。

为了设定变量的默认值,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 <instproc>[<args>]

其中<instproc>为必须,<args>为可选。实例程序调用内部机制如下:

i) 在对象类中查找对应的实例程序名,如果找到,执行对应的实例函数并返回,如果没有找到,执行下一步。

ii)查找实例函数unknow{…}.如果找到了,执行unknow{…}函数并返回,如果没有找到,执行下一步。实例程序unknown{…}是一个默认的实例程序,如果没有找到对应的实例程序,unknow{…}函数就会被调用。

iii)对对象的基类 重复步骤i)和ii)

iv) 如果调用至最顶层的基类,仍没有找到输入的实例程序和unknow{…}函数,报告错误并退出程序。

OTcl 命令调用

对OTcl命令的调用语法类似于实例程序: $obj<cmd_name>[<args>]

由于语法类似于调用实例程序,OTcl函数执行命令的方法类似于实例程序。下面,我们将讲述OTclAgent/TCP对象命令的调用机制(见程序3.9)。图3.4画出了命令调用机制的内部机制:

image

i) 执行 “$tcp <cmd_name><args>”语句

ii)在OTcl的Agent/TCP类中查找名为<cmd_name>的实例程序,如果找到,调用该实例函数并完成剩下的程序。否则,进行下一步。

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<cmd_args>,其中<cmd_args>为<cmd_name><args>.

v) cmd 实例程序将整个语句(i,e.,”cmd<cmd_args>”)做为输入参数矢量(argv)给影子对象(此为TcpAgent)的“command(argc,argv)”函数。如程序3.9所示,该函数总是携带两个参数。第二个输入参数argv为字符数组,包含着由cmd传送过来的输入参数。第一个参数argc,是所有输入参数的个数(例如,argv中的非空元素的个数)。argv的第一个和第二个输入参数分别为cmd和命令名。剩余的元素包含着原始调用的输入参数。见表3.3

image

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 <cmd_name><args>

该命令起始于图3.4中的位置(1).但是,还有另外一种的调用方式,该方式图3.4中的位置(2),语法如下:

$tcp cmd <cmd_name><args>

第二种调用方式避免了实例程序和OTcl命令相同时候的麻烦。

OTcl命令的返回机制

执行了C++的程序之后,NS2会返回适当的返回值,在nsallinone-2.30/tcl8.4.13/generic/tcl.h,NS2定义了以下5种返回值(0~5),如程序3.10.通知了解释器命令调用结果。

image

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(…)的值是有效的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值