[译] NS2中OTcl 和C++ 的连接

转自:http://blog.sina.com.cn/s/blog_6405cfd20100n6jn.html


本文翻译自《Introduction to Network Simulator NS2》中第三章,Linkage Between OTcl and C++ in NS2,只翻译了其中重要的段落,略过了其他太细节的解释,但不影响阅读和理解。

NS2是一个面向对象的网络模拟器,由OTcl语言和C++语言编就。其中前者负责前台工作(例如用户接口),后者负责后台运算即执行仿真运算。如图1所示,两中语言

image                                                                                         图1

的类架构可以是独立的,也可以由TclCL (称为OTcl/C++接口)类连接起来。左右连个框图中都有两种类型的C++或OTcl类。第一种包括在OTcl和C++之间有联系的类(如左框图的右下角和右框图的左下角)。文献中一般把这些OTcl类和C++类分别称为解释体系(Interpreted hierarchy)和编译体系(Compiled hierarchy)。第二种包括没有相互联系的OTcl类和C++类。这第二种类既不属于解释体系,也不属于编译体系。在这章中我们主要讨论OTcl语言和C++语言如何组成NS2.

TclCl由C++语言写成,包括以下六种主要的类。

1 Tcl 类:提供从编译体系访问解释体系的方法(Method).

2 InstVar 类:负责把两种体系中的成员函数对应的绑定。

3 TclObject 类:是编译体系中所有C++模拟对象的基类。

4 TclClass 类:将解释体系中的类名一一对应于编译体系中的对应类名。

5 TclCommand 类:提供从解释体系访问编译体系的全局方法。

6 EmbeddedTcl 类:把OTcl 脚本转化为C++代码。

3.1 NS2中为什么由两种语言编写?

为什么是两种语言呢?简单的说,NS2使用OTcl语言建立和配置网络,利用C++语言执行仿真。所有的C++代码需要编译并被链接成可执行文件。由于NS2的代码量相当庞大,编译时间必然不短。另一方面,OTcl属于解释器而非编译器,OTcl文件中的任何改变都不需要编译。然而,由于OTcl并没有把所有的代码都转化为机器语言,每行代码都需要更多的执行时间。总之,C++运行速度快但编译修改时间长,适合运行大规模仿真。OTcl运行速度慢但修改时间短,适合运行小规模但参数任意可变的仿真。有鉴如此,NS2吸收了两种语言的优点。

NS2手册提供了如下准则来选择代码语言:

Using OTcl

     ---配置,调整,或一次性仿真。抑或执行现有的NS2模块。

         这中选择十分适合大多数的初学者。因为它不需要对NS2复杂地内部机制的深入理解。但是,现有的NS2模块十分有限,这种选择可能无法满足很多读者。

Using C++

    ---当读者要处理数据包或者修改现有的NS2模块。

        这种选择可能使大多数的NS2初学者感到沮丧。本书的主要的目标是帮助读者理解NS2的内部结构,使读者修改NS2模块时得心应手。

理论上说,我们有三种方式来开发C++程序。第一种称为“Basic C++”,是三种方法中最简单的一种,只包括基本的C++结构。这种方式存在灵活性问题,因为系统中任何参数的改变都需要重新编译。着眼于灵活性问题,第二种方法称为“C++ coding with input arguments”带有输入参数的C++程序。当系统参数发生变化,我们只需简单的改变输入参数而不需要重新编译。但是,第二种方式的重要问题是:当输入参数很多时,调用命令会相当长且复杂。第三种方式称为“C++ coding with configuration files“ -把所有的系统参数写入配置文件。这种方式解决了灵活性问题和复杂的调用问题。需要改变系统参数时,只需简单的改变配置文件。事实上,NS2正式基于这种方式开发的。

3.2 Tcl类

Tcl 类是作为OTcl域用户接口的一个C++类,它提供以下操作的方法:

1 获取Tcl句柄。(instance 函数)

2 在c++域內调用OTcl程序。(eval(),evalc(),evalf()等函数)

3 从/向解释器接收或传递结果。(result(),resultf()函数)

4 用统一的格式报告错误和终止程序 。(error()函数)

5 检索TclObjects对象的引用(enter(),delete(),lookup()函数)

3.2.1 获得Tcl 类实例的引用

在c++中,我们通过类的对象来调用该类的成员函数。为了调用Tcl类的函数,我们需要一个Tcl类的对象。Tcl类提供instance()函数来获取静态的Tcl变量:

Tcl& tcl=Tcl::instance();

这里,instance()函数返回Tcl的静态变量instance_。由于它是静态的,所以在仿真中将会只有一个Tcl 对象instance_。因此,任何时候用以上语句检索一个Tcl对象时都会返回同一个Tcl对象。获取了Tcl对象之后,我们就可以调用Tcl类中的成员函数了。

3.2.2 调用Tcl 程序

在编写C++程序的时候,我们可能需要调用某个OTcl实例程序(Instproc)。例如,我们可能要通过解释体系中Simulator的instproc :now()函数来获取当前仿真时间。Tcl 类提供以下函数来调用OTcl程序。例如,下面的C++程序告诉OTcl在屏幕上打印“Overall Packet Delay is 10.0 seconds“。

i) Tcl::eval_r(char * str):通过解释器执行存储在变量str中的命令语句。例如:

Tcl&= Tcl::instance();

char s[128];

strcpy(s,”puts [Overall Packet Delay is 10.0 seconds]”);

tcl.evalc(s);

ii) Tcl::evalc(const char* str):执行命令语句"str”.例如

Tcl& tcl =Tcl::instance();

tcl.evalc(“puts [Overall Packet Delay is 10.0 seconds]”);

和前个函数不同。前者把指向字符变量的指针作为输入参数(char*),此函数把字符作为输入变量(const char*)。

Tcl::eval_r():函数执行已经被存储进内部变量bp_的命令。例如

Tcl& tcl=Tcl::instance();

char s[128];

sprintf(tcl.buffer(),”puts [Overall Packet Delay is 10.0 seconds]”);

tcl.eval_r();

其中,tcl::buffer() 把内容交给内部变量bp。

iii) Tcl::evalf(const char* fmt,…);像C++中的printf一样使用fmt。例如

Tcl& tcl=Tcl::instance();

float delay =10.0;

tcl.evalf(“puts [Overall Packet Delay is %2.1f seconds]”,delay);

3.2.3 从/向解释器接收或者传递结果

我们有时也需要向/从解释器传递或获取数值。例如在上面的例子中,我们需要把包时延传递给解释器而不仅打印出来。Tcl类提供了3个函数来回传数值。

i) Tcl::result(const char* fmt):把字符作为结果传递给解释器。例如:

   Tcl& tcl=Tcl::instance();

   tcl.result(“10”);

   return TCL_OK;

ii) Tcl::resultf(const char* result,…); 用类似于C++中的printf(…)格式来把数值传递给解释器。

    例子3.2 :使用chain 类中的retrundelay命令返回C++变量dealy。具体执行如下:

              Tcl& tcl=Tcl::instance();

              tcl.resultf(“%1.1f”,delay);

              return TCL_OK;

    在OTcl域,下面的代码将C++的chain类中的变量delay存储在d中。

              set chain [new Chain]

              set d [$Chain returnDelay]

iii) Tcl::result(void);从解释器中检索结果(当C++程序调用一个OTcl命令时,解释器将执行结果保存在私有成员变量tcl_->result中 ,用户必须用tcl.result(void)来uoqu执行结果,需要注意的是,结果是字符串,必须被转化为适当的类型)。例如,下面的语句把OTcl变量d存储在C++变量delay中。

      Tcl& tcl=Tcl::instance();

      tcl.evalc(“$d”);

      char* delay=tcl.result();

3.2.4  报告错误和终止程序

Tcl提供error()函数用统一的格式来终止程序。这个函数仅仅把储存在“str”和tcl->result(…)中的错误信息打印到屏幕上。

Tcl::error(const char* str)

Tcl::error(str)和ruturn TCL_ERROR的区别如下:前者仅仅打印错误信息和退出程序。后者出现时,NS2会追踪错误(这些错误可能不止一处)。最后,用户可以使用追踪到的这些错误来恢复程序、定位错误点或者打印出错误信息。

3.2.5 检索TclObjects的引用

我们记得,解释体系的对象总是有一个对应的编译对象(称为影子对象)。在某些情况下,我们可能需要获得某个解释对象的影子对象。NS2把两种类型对象的映射关系储存在一个哈希表中。Tcl类提供下面几个函数来输入,删除,或者检索哈希表中的实体。

1 cl::enter(TclObject* o);把对象*o插入哈希表中,并把*o对应的OTcl名字保存在保护变量name_中。此函数在TclClass:creat_shadow(…)被调用。

2 Tcl::delete(TclObject *o);把哈希表中对应*o的实体删除。此函数在TclClass:delete_shadow(…)被调用。

3 Tcl::lookup(char *str);返回名字为“str”的实体。

image

现在我们来讨论程序3.4中的代码。这里argv[2]是由OTcl传入的输入参数。第8行使用TclObject::lookup(argv[2])函数来获取对应于argv[2]的影子函数。得到的对象被转化为NsObject类,存储于变量*target_中。注意,command命令的细节将在3.44中讲述。


3.3 InstVar类

Instvar 类的作用类似于胶水,它负责将C++类的成员变量和OTcl类的实例程序绑定在一起。当一个C++变量和OTcl实例程序绑定在一起时,任何一方的改变都会引起对方的改变(更新)。NS2支持5种数据类型的绑定:实数,整数,带宽,时间,布尔。这5中类型既不是C++数据类型,也不是OTcl数据类型(Tcl以字符的形式存储一切,因此OTcl变量没有数据类型)。 定义类型这些主要是因为它们有利于NS2赋值。

[译] NS2中OTcl 和C++ 的连接_第1张图片

如表3.1,这5种数据类型被定义为5个C++类,都继承于InstVar类。其中实数,带宽,时间三个数据类型都使用double型,而整数和布尔分别使用int和bool C++型。

3.3.1 实数和整数变量

这两种NS2数据类型分别被指定为实数型和整型。另外,我们可以使用e表示×10(表示10的x次方).

例子 3.4 realvar 和intvar是OTcl 对象obj的实数和整数数据类型 。它们可以以下面几种方式赋值

$obj set realvar 1.2e3

$obj set realvar 1200

$obj set intvar 1200

3.3.2 带宽

带宽被指定为实数型数据。带宽的默认单位是比特每秒(bps)。另外,我们可以在带宽设定时加上以下后缀。

k或K表示‘千’ ×10的3次方

m或M表示‘兆’ ×10的6次方

B表示带宽的单位从比特每秒(bps)改变为字节每秒(Bps)

NS2只考虑有效后缀的第一个字母。因此,对NS2来说‘M’和‘Mps’意义相同。

例如 :

$obj set bwvar 8000000

$obj set bwvar 8m

$obj set bwvar 8Mbps

$obj set bwvar 800K

$obj set bwvar 1MB

3.3.3 Time

时间也别指定为实数型。时间的默认单位是秒。另外,我们也可以增加后缀来改变时间单位。

m 表示 ‘毫’ ×10的-3次方

n 表示  ‘纳’ ×10的-6次方

p 表示  ‘微’ ×10的-8次方

例如:

$obj set timevar 2m

$obj set timevar 2e-3

$obj set timevar 2e6n

$obj set timevar 2e9ps

3.3.4 布尔

布尔被指定为‘真’(正数)或‘假’(零)。如果一个布尔变量的第一个字母是‘t’或者‘T’,或数字比0大则布尔值为真,否则为假。

例子3.7 ;                    

#set boolvar to be TRUE

$obj set boolvar 1

$obj set boolvar T

$obj set boolvar true

$obj set boolvar true

$obj set boolvar tasty

$obj set boolvar 20

$obj set boolvar 3.37

#set boolvar to be FALSE

$obj set boolvar 0

$obj set boolvar f

$obj set boolvar false

$obj set boolvar something

$obj set boolvar –5.29

请谨记:NS2只对第一个布尔类型的第一个字母做辨别,因此true和tasty没有区别。

给OTcl变量赋值之后,NS2把字符型数值转换为相应的C++类型。除了布尔型,NS2把字符型数值转化为int型或者double型。在这一过程中,有效后缀也会进行相应的转化。对于布尔型,NS2只考虑第一个字母。若一个字符是整数,NS2不做任何处理,若第一个字母是T或者t,NS2会把这个字符转化成整数1,反之为0.转化完成后,NS2会把整数转化成布尔型,并更型所绑定的编译体系的变量。


3.4 TclObject 类

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

3.4.1 TclObject 类的引用

OTcl类和C++类接入他们各自对象的方法并不相同。作为编译器,C++直接访问分配给其对象的内存空间。作为解释器,OTcl并不直接访问内存空间,而是用一字符串作为对象的引用(句柄)。NS2规定,SplitObject的这个字符串的命名形式为_,其中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[]

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

image

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

new{className args}函数的内部机制如下:首先第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 对象,我们可以在仿真脚本中执行new Agent/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中设定绑定变量的初始值。语法如下: set .意为将类的instvar变量的值设为

为了设定变量的默认值,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函数执行命令的方法类似于实例程序。下面,我们将讲述OTcl Agent/TCP对象命令的调用机制(见程序3.9)。图3.4画出了命令调用机制的内部机制:

image

i) 执行 “$tcp ”语句

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

iii)在OTcl的Agent/TCP类中查找unknown{…}函数,如果找到,调用unknown函数完成程序,否则,进行下一步。

iv)重复步骤ii)和iii),直至上溯到SplitObject类为止,若在继承树上的所有类中都没有找到unknown函数,NS2将会执行下列语句SplitObject unknown。SplitObject类的 unknown函数在~tclcl/tcl-object.tcl中定义。这里,将会执行"$self cmd $args”语句,其中args是unknown函数的输入参数,根据以上的调用,这语句变为 SplitObject cmd ,其中 .

v) cmd 实例程序将整个语句(i,e.,”cmd ”)做为输入参数矢量(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

该命令起始于图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.通知了解释器命令调用结果。

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


当生成一个TclObject时,NS2会自动的建立一个影子编译对象。在3.4.2节中,我们解释了TclObject的生成机制。我们注意到,TclClass类与影子对象的生成有关。我们现在就解释一下TclClass还有影子对象生成的细节。

3.5.1 TclClass类综述

TclClass负责在编译体系内建议影子对象。该类把OTcl类映射给C++的一个静态的映射变量,并提供在编译体系内生成影子对象的方法。程序3.11列出了TcpClass的细节:把解释体系内的Agent/TCP类映射给编译体系内的静态映射变量class_tcp。

image

不同于其他类,TclClass类的子类在同一个地方声明,执行和实例化。在程序3.11中,TclClass的一个子类只包含两个两个函数:构造函数和creat(…)函数。其中creat函数生成影子对象。为了为OTcl的Agent/TCP生成一个影子对象,我们需要在编译体系内执行下列步骤:

i)生成影子编译类

ii)从TclClass继承一个映射类(例如TcpClass)

iii)给这个映射类添加一个实例(例如class_tcp)

iv)定义这个映射子类的构造函数(3行~11行),添加OTcl类的类名(例如Agent/TCP)作为基类构造函数的输入参数,例如TclClass)

v)定义creat函数来构造影子编译对象。调用new函数来产生影子编译对象(例如,new TcpAgent)并返回以生成的对象(程序3.11的第5行)。

3.5.2 TclObject 的生成

我们现在来解释一下TclObject的整个生成过程。考虑图3.3,TclObject 的生成过程如下:

  1. 象在3.4.2小节中一样,生成一个OTcl对象
  2. 调用TclClass函数的实例程序create-shadow
  3. 在creat-shadow函数中调用TcpClass中的create(…)函数
  4. 在程序3.11中,第5行中的create(…)函数执行“new TcpAgent”并返回生成的对象
  5. 调用其父类的构造函数构造一个TcpAgent对象
  6. 构造Agent对象。其中包括绑定在解释体系内所有变量。
  7. 返回TcpAgent类,构造TcpAgent对象,绑定在解释体系所有变量。
  8. 返回生成的影子对象给SplitObject::init{…},程序继续进行。

3.5.3 TclClass的命名规范

现在我们来讲一讲继承自TclClass类的子类和其对应静态变量的命名规范。首先,每个子类都是直接从TclClass继承而来,而不管其类体系所属。例如,RenoTcpAgent继承自TcpAgent,但是它的映射类RenoTcpClass和TcpClass均继承自TclClass。

其次,它的命名规范十分类似于C++变量的命名规范。在大多数情况下,我们仅在其C++类名后面加上Class字样即成。而其静态变量则需要在前面加上class_。表3.4列出了几个例子。

image

3.5.4 映射变量的实例化

在NS2启动时,所有的静态映射变量都会实例化。在此,TclClass类把OTcl类名存储在变量classname_中,把所有的映射变量存储在链表all_中。在所有的映射变量被存储在链表中之后,调用TclClass::bind(…)。bind(…)在系统中注册all_中所有的映射变量,并创建相对应的解释体系。bind(…)函数同时也将create-shadow和delete-shadow函数绑定于映射类(如TcpClass)的create-shadow{…}和delete-shadow{…}。在这之后,NS2重新组织所有的OTcl类名。OTcl类对象的建立遵循3.4.2小节和3.5.2小节的步骤。


你可能感兴趣的:(NS,Linux)