前面提到的第三本英文书籍,个人感觉最重要的一张,有大神进行了翻译,鼓掌。。。。
——————华丽的分割线——————————————————————————————————————————————————
本文翻译自《Introduction to Network Simulator NS2》中第三章,Linkage Between OTcl and C++ in NS2,只翻译了其中重要的段落,略过了其他太细节的解释,但不影响阅读和理解。
NS2是一个面向对象的网络模拟器,由OTcl语言和C++语言编就。其中前者负责前台工作(例如用户接口),后者负责后台运算即执行仿真运算。如图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
Using C++
理论上说,我们有三种方式来开发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):把字符作为结果传递给解释器。例如:
ii) Tcl::resultf(const char* result,…); 用类似于C++中的printf(…)格式来把数值传递给解释器。
iii) Tcl::result(void);从解释器中检索结果(当C++程序调用一个OTcl命令时,解释器将执行结果保存在私有成员变量tcl_->result中 ,用户必须用tcl.result(void)来uoqu执行结果,需要注意的是,结果是字符串,必须被转化为适当的类型)。例如,下面的语句把OTcl变量d存储在C++变量delay中。
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”的实体。
现在我们来讨论程序3.4中的代码。这里argv[2]是由OTcl传入的输入参数。第8行使用TclObject::lookup(argv[2])函数来获取对应于argv[2]的影子函数。得到的对象被转化为NsObject类,存储于变量*target_中。注意,command命令的细节将在3.44中讲述。
待续。