NS2 OTcl/C++分裂模型接口TclCL

OTcl/C++接口TclCL使用C++语言编写,包含六个主要的C++类

(1)   ClassTcl:定义从C++类访问OTcl类的方法

  • 在C++中调用OTcl类的方法,首先需要获得Tcl实例:Tcl& tcl=Tcl::instance()。通过调用tcl实例的eval(char* str),evalc(const char* str)以及evalf(const char* fmt, …)方法间接调用OTcl类方法
  • €向解释器传递或获取结果。向解释器传递值:调用tcl实例的result(const char* str)和resultf(const char* str, …),并返回TCL_OK或者TCL_ERROR。从解释器取值:调用tcl实例的char* result(void)获取值,在取值之前一般会调用tcl实例的eval等方法产生所需要的值。这两种方法的实质是调用Tcl类的私有Tcl_Interp类成员变量tcl_的result(…)方法实现两种语言之间的值传递。
  • €报告错误并退出程序。调用error(const char* str)仅仅简单地将存储在str中的错误信息在屏幕上打印出来并退出程序。而返回TCL_ERROR是使NS2诱捕错误,是用户能够使用这些诱捕的错误从错误中恢复,或定位错误,或从错误堆栈中打印所有错误的信息。
  • €获得OTcl对象对应的影子C++对象的TclObject引用。NS2使用hash表实现OTcl对象和C++对象之间联系。Class Tcl提供方法从hash表中获取、删除条目。Tcl::enter(TclObject*o)将对象”*o”插入hash表并将*o与存储在保护变量name_中的OTcl名字字符串进行关联。当有新的对象产生时,该方法被TclClass:creat_shadow(…)调用。Tcl::delete(TclObject*o)将对象”*.o”从hash表中删除。当有对象销毁使,该方法被TclClass:delete_shadow(…)调用。Tcl::lookup(char*str)返回名字为”str”的TclObject对象。

(2)   ClassInstVar:用于绑定C++类和OTcl类的成员变量

NS2支持5种数据类型变量的绑定:real,integer,bandwidth,time和boolean。这5中数据类型既不是C++的数据类型也不是OTcl(只有字符串类型)的数据类型。这些数据类型使用C++类实现,是InstVar的派生类:InstVarReal,InstVarInt,InstVarBandwidth,InstVarTime和InstVarBool。这些数据类型中InstVarReal,InstVarBandwidth和InstVarTime使用C++的double数据类型, InstVarInt和InstVarBool分别使用C++的int和bool数据类型。数据类型绑定对照表如下:

OTcl data type

C++ binding class

Real

InstVarReal

Integer

InstVarInt

Bandwidth

InstVarBandwidth

Time

InstVarTime

Boolean

InstVarBool

Bandwidth量纲:k或K为千,m或M为兆,B为字节,b为比特

Time的量纲:m或ms为毫秒,n或ns为纳秒,p或ps为皮秒

Boolean:只要值的第一个字符大于0或为t或为T时则表示Ture,其他为False。NS2在判断Boolean类型的值时只考虑字符串的第一个字符而忽略其他字符。

NS2对这些数据类型的合法后缀进行数字转换,如Bandwidth变量值的后缀m转换成103,而对于Boolean类型变量的值的第一个字符,如果是整型则不予转换,如果是字母则根据是否为T,转换成1或0整型数据。

(3)   ClassTclObject:是所有C++仿真类的基类

当解释对象创建时,TclObject指导编译影子对象的创建。C++类TclObject对象映射到OTcl类SplitObject对象(即它们互为影子对象),这两个类是各自类层次结构的基类。当在OTcl域中实例化一个对象,SplitObject类的构造方法将被调用,其中一个实例化过程是影子对象的创建。

  • €TclObject引用。OTcl和C++访问各自对象的方法不同。作为编译器,C++直接访问特定对象所分配的内存空间(即引用是内存地址,如0xd6f9c0),而作为解释器,OTcl并不是直接访问内存空间,而是使用一个字符串句柄(如”_o10”)作为对象的引用。根据规则,SplitObject对象的命名串的格式是_<NNN>,<NNN>对于每一个SplitObject对象都是唯一的。
  • €影子TclObject对象的创建和销毁。在大部分情况下,对象的创建和销毁是在OTcl域(或者是Tcl仿真脚本)中进行的。OTcl使用creat和destroy命令可以创建和销毁独立的OTcl对象,然而这些命令很少在NS2中,因为它们不会创建OTcl对象的编译影子对象。在NS2中使用全局过程new{…}和delete{…}创建和销毁OTcl对象及其编译影子对象。new<className> [<args>],其中className是OTcl类名,这个参数必须有。可选参数用于该类的构造函数,返回的是一个OTcl对象的字符串句柄。new过程调用SplitObject的getid方法为创建对象根据命名法则产生字符串句柄,调用className类的create方法将字符串句柄与className对象进行关联并调用alloc{…}为className对象分配内存空间和init{…}对className对象进行初始化。在大部分情况下,init{…}是OTcl对象的构造器,每一个类重写init{…}定义自己的初始化过程,一般在init{…}中会使用eval $self next调用父类的同名函数,即init{…}。这个过程一直到SplitObject类的init{…},其会调用creat-shadow命令创建编译影子对象。delete{…}直接销毁OTcl对象,并通过调用instproc delete-shadow销毁对象的编译影子对象。
  • €编译和解释类层次中的变量绑定。在编译影子对象创建的过程中,NS2将解释类的变量绑定到编译影子类的相应变量上。TclObject类在构造函数(C++类的构造函数)中调用bind(“iname”, &cname),bind_bw(“iname”, &cname),bind_time(“iname”, &cname)和bind_bool(“iname”, &cname)中的某个函数进行两个层次结构中相应类变量的绑定,第一个参数是解释类变量的字符串句柄,第二个参数是编译类变量的地址。NS2会将ns-default.tcl中指定的缺省值赋给两个类层次结构中对象所绑定的变量。NS2通过调用SplitObject的instproc init-instvar{…}从文件ns-default.tcl中读取缺省值,并赋值给绑定对象。如果我们绑定的变量没有赋缺省值,或者说ns-default.tcl中没有指定绑定变量的缺省值,SplitObject的instproc init-instvar{…}会调用instprocSplitObject::warn-instvar{…}在屏幕上显示警告信息,而将缺省值赋值给一个不合法的变量(没有绑定或者不存在)不会出现警告信息。
  • 从解释层次对象访问编译层次对象的方法:command,这与Class Tcl的功能相反。OTcl的instproc调用机制:$obj <instproc>[<args>]首先会在当前对象$obj中寻找名字为<instproc>的过程,没有找到则接着找有没有instproc unknown{…}过程,如果都没有则在该对象的父类中接着执行上述过程,直到顶层SplitObject类中。如果在顶层类中还没有找到这两个过程,程序报错并退出。command方法的一种调用过程和instproc的调用过程一样:$obj <cmd_name>[<args>],回溯到SplitObject的unknown方法时,会调用SplitObject cmd <cmd_args>,其中<cmd_args>是”<cmd_name> <args>”,instproc cmd会将”cmd <cmd_args>”,即”cmd cmd_name args”作为参数传递给$obj的编译影子对象command(argc, argv)的第二个参数,接着该编译影子对象匹配argv[1]中的值(argc至少等于3),如果匹配上,执行相应操作并返回TCL_OK,没有匹配上则在父类的command(argc, argv)中继续匹配,直到顶层类TclObject的command(argc, argv)中,如果还没有匹配上则报错(no such method, requiresadditional args)返回TCL_ERROR,接着回溯到OTcl域中的相应对象中,返回的值根据是否成功匹配上得到TCL_OK或TCL_ERROR。command方法调用的一种替换方法:$obj cmd <cmd_name><args>这种调用方法省去了在OTcl域中向父类回溯的过程,直接调用SplitObject cmd <cmd_name><args> (等同于SplitObject cmd<cmd_args>),这种方法避免了OTcl的cmd_name名称和instproc名称相同带来的问题,如果相同cmd_name可能无法调用编译影子对象的command(argc, argv)方法。而是用cmd则不会导致混淆,因为只有SplitObject类中才定义了cmd方法。OTcl command返回机制,返回值及含义如下:

#define TCL_OK        0

Command成功完成

#define TCL_ERROR    1

Command完成失败

#define TCL_RETURN   2

解释器退出当前instproc,并不会执行其他部分

#define TCL_BREAK    3

解释器退出当前循环

#define TCL_CONTINUE 4

解释器退出当前循环中的一次迭代

当command命令执行结束后,C++如果返回TCL_OK,解释器读取表达式中的值,如tcl.result(…)中存储的值,而不是读取return中的TCL_OK值。当返回TCL_ERROR时,解释调用过程tkerror,在屏幕上显示错误信息。

(4)   ClassTclClass:将OTcl类名映射到C++类名

当一个TclObject对象创建时,NS2自动创建编译影子对象,这个工作由TclClass完成。TclClass将OTcl类映射到一个C++静态映射变量,并提供创建编译影子对象的方法。不像其他的C++类,TclClass的派生类声明,实现以及实例化都在同一个位置完成。如下将OTcl类Agent/TCP映射到C++静态变量class_tcp

static class TcpClass : public TclClass{//映射类声明
public:
TcpClass() : TclClass(“Agent/TCP”){}//映射类的构造函数
TclObject* create(int, const char* const*){//创建影子对象
    return (new TcpAgent());
}
} class_tcp; //实例化C++静态映射变量

  • €TclObject创建。在OTcl域中使用new命令创建OTcl对象Agent/TCP时,在init{…}中会回溯到父类SplitObject的init{…}方法,在该方法中调用SplitObject的命令create-shadow,该命令会调用TclClass的create-shadow(…)方法,该方法接着调用TcpClass的create(…)方法执行new TcpAgent()并返回所创建对象的引用。TcpAgent在执行构造函数时,会依次调用父类Agent和TclObject的构造函数,在Agent对象构造函数中会将自己的变量和OTcl域中的影子对象成员变量进行绑定,返回到TcpAgent对象构造函数也会将TcpAgent的变量和对应的影子对象成员变量进行绑定,最终将创建的影子对象返回给instprocSplitObject::init{…}。
  • €Class TclClass命名规则。所有编译层次结构中的类(不用管他们之间的继承关系)的映射类都直接继承自TclClass。映射类的类名命名是在C++类名之后加”Class”,映射变量的命名是在C++类名之间加上”class_”。
  • €映射变量实例化过程。NS2启动便会实例化所有静态映射变量。TclClass类会将所有OTcl类名存放在自己的成员变量classname_中,并将所有映射变量存放在自己的链接列表(linked list)all_中。当所有映射变量存入all_之后,调用TclClass::bind(…),将all_中的所有映射变量注册进系统并创建解释类层次结构,同时bind(…)会将instproc create-shadow和instproc delete-shadow分别绑定到映射类(如TcpClass)的create_shadow(…)和delete_shadow(…)方法(这两个方法TcpClass继承自TclClass)。在这之后,NS2便能识别所有OTcl类名。

(5)   ClassTclCommand:提供从OTcl类访问C++类的全局接口

    TclCommand的功能和OTcl command的功能一样,不同之处在于,TclCommand并不关联到任何OTcl/C++类,并且在全局范围内可用,但是TclCommand破坏了面向对象的概念,不推荐使用这种类型的command。

  • €调用TclCommand。TclCommand可以像一个全局过程在任何地方调用。进入NS2环境后,可以直接在命令控制台中输入”ns-version”或”ns-random”这两个TclCommands。
  • €创建TclCommand。一条TclCommand命令定义在一个TclCommand的子类中。TclCommand命令名称作为参数传递给TclCommand类并同时在command(…)中实现该命令。当NS2启动后,将所有TclCommand命令名字和相应类的command(…)方法进行绑定。例如ns-random命令关联TclCommand子类RandomCommand(在~ns/common/misc.cc中),并将”ns-random”作为参数传递给TclCommand的构造函数,当在OTcl域中使用ns-random命令时,会调用RandCommand的command(int argc, constchar*const* argv)方法,将执行结果返回给解释器。在NS2启动时,会调用~tclcl/TclAppInit.cc中的函数init_misc(…),该函数会通过调用new TclCommand类实例化所有TclCommand命令。

(6)   ClassEmbeddedTcl:将OTcl脚本翻译成C++代码

虽然NS2采用两种语言架构,但是主要的操作是在C++代码中执行的。在编译阶段,NS2会使用EmbeddedTcl将所有OTcl脚本翻译成C++语言。翻译过程主要由两部分构成:

  1. NS2使用makefile中的语句,例如$(TCLSH) bin/tcl-expand.tcltcl/lib/ns-lib.tcl | $(TCL2C) et_ns_lib > gen/ns_tcl.cc在编译阶段将脚本翻译成EmbeddedTcl对象。这个语句有两个功能:展开ns-lib.tcl,将其中的source <filename>用filename文件内容替换;将展开后的文件翻译成EmbeddedTcl对象et_ns_lib,并将结果写入.cc文件ns_tcl.cc。
  2. 在NS2启动过程中,NS2将翻译后的EmbeddedTcl对象加载入NS2。

总结:TclObject的三大功能:提供创建和销毁shadow C++对象的方法;绑定OTcl类和C++类的成员变量;提供OTcl command方法,使能从OTcl域方法访问C++域方法。TclClass将一个OTcl类名映射到一个C++静态映射变量,虽然TclObject启动影子对象的创建过程,但实际影子对象的创建是由TclClass完成。InstVar定义NS2中能够进行绑定的数据类型。Tcl提供从C++域访问OTcl域的接口。OTcl command和TclCommand都提供了从OTcl域访问C++域的接口,但它们有些许不同。EmbeddedTcl将OTcl脚本翻译成C++代码。

你可能感兴趣的:(NS2 OTcl/C++分裂模型接口TclCL)