学习NS2重要要把握几个重要的类。现在简单分析下。
1.Tcl类
这个类封装是OTcl解释器的真正实例,可以当成解释器理解。 其中定义了解释器访问及通信的方法。这个类是在~tclcl/tclcl.h和~tclcl/tcl.cc中定义的,提供了以下的操作方法:
1.1 获得 Tcl 实例的一个指针
在类定义有 static Tcl instance_;获取的方法是通过一个静态的内联函数static inline Tcl& instance() { return (instance_); }实现的。
1.2 通过解释器调用 OTcl 过程
通过解释器实例tcl来调用OTcl命令,他们在调用参数方面有本质的区别。每个函数都传递一个字串(string)给解
释器,然后解释器通过一个全局文本来识别这个字符串。如果解释器返回TCL_OK,则这些函数将会返一个相应的OTcl
过程。反过来,如果解释器返回TCL_ERROR,则这些函数将调用tkerror{}。用户可以重载(overload)个过程,以便有选
择地忽略某些类型的错误。
1.3 取出或将结果返回解释器
//tcl的解释器
Tcl_Interp* tcl_; Tcl_Interp是在~include/tcl.h中的定义的,
typedef struct Tcl_Interp {
char *result; /*指向命令的返回值*/
void (*freeProc) _ANSI_ARGS_((char *blockPtr));
int errorLine; /* 当返回TCL_ERROR时,该值指示出出现错误的行数(从1开始) */
} Tcl_Interp;
结果就保存在tcl_->result中。
取出结果:tcl.result(void)必须用于取回结果。注意这里的结果是一个字符串,它必须被转化成一个适合结果类型的内部格式。
结果传给解释器,即设置tcl_->result的值,a)tcl.result(const char* s),b)tcl.resultf(const char* fmt, . . . )
1.4 报告错误状态并以统一的方法退出
编译代码中提供了一种统一的报告错误的方法。
tcl.error(const char* s)执行以下功能: 将s写入stdout;将tcl_->result写入stdout;退出,并将错误代码(error code)置1。
tcl.resultf("cmd = %s", cmd);
tcl.error("invalid command specified");
1.5 存储并查找“TclObjects“
ns将每个TclObject在编译层次的一个指针(reference)存在一个hash表中;这样就能快速地访问该对象了。该hash
表在解释器的内部。Ns用户以TclObject的名字为关键字(key)在hash表中进行插入、查找或者删除TclObject的操作。
tcl.enter(TclObject* o)将在hash表插入一个指向TclObject o的指针(pointer)。它被TclClass::create_shadow()
用来在对象建立时,将其插入表中。
tcl.lookup(char* s)将取回名为s的TclObject。可以这样被使用:TclObject::lookup()。
tcl.remove(TclObject* o)将删除hash表中TclObject o的指针。可以用TclClass::delete_shadow()来移出hash表中
存在的入口,此时该对象已经被删除。
2.TclObject类
TclObject类是解释和编译层次大多数其它类的基类(base class)。TclObject类中的每个对象都由用户从解释器中创
建。编译层次中同时有一个与之对应的影子对象(shadow object)被创建。这两个对象相互紧密联系。下一小节描述的
TclClass类,包含了执行这种投射(shadowing)的机制。
TclObject类包含了早期的NsObject类的函数。因此,它储存了变量绑定(bindings)接口,这些变量绑定绑定了解释对象中的实例变量(instance variables)和相应的编译对象中的C++成员变量(member variables)。这种绑定比ns版本1要强,因为OTcl变量的任何变化都是被跟踪(trapped)的,而且每次当前的C++和OTcl的值被解释器访问后都要保持一致。这种一致性是由InstVar类来完成的。同样,和ns版本1不同的是,tclObject类的对象不再存储在一个全局链表(link list)中。而是存储在Tcl类的一个hash表中。
2.1 创建(creating)和撤销(Destroying)TclObjects
当用户创建一个新的TclObject时,通常调用new{}和delete{}过程 (procedures) 这些过程定义在~tclcl/tcl-object.tcl中。 它们可以用于创建和撤销所有类的对象。
创建TclObjects 用户可以调用new{}来创建一个解释类的TclObject。这时解释器将执行这个对象的构造函数
(constructor)init{},同时给它传递用户提供的任何参数。ns自动创建相应的编译对象。 shadow对象是通过基类TclObject
的构造函数被创建的。因此,新的TclObject的构造函数必须首先调用父类的构造函数。new{}方法返回一个对象的handle,
用户可以通过这个handle对对象进行进一步的操作。
如下面是Agent/SRM/Adaptive的构造函数:
Agent/SRM/Adaptive instproc init args {
eval $self next $args
$self array set closest_ "requestor 0 repairor 0"
$self set eps_ [$class set eps_]
}
创建一个gent/SRM/Adaptive对象时执行的步骤:
2.1.1 从TclObject的名字空间(name space)获取一个惟一的新的对象的handle。这个handle将返回给用户。ns中大多数handle都是以_o<NNN>的形式出现的,这里的<NNN>是一个整数。这个handle由getid{}创建。它可以从C++中的name(){}的方法获得。
在TclObject类中定义:
#if 0
/* allocate in-line rather than with new to avoid pointer and malloc overhead. */
#define TCLCL_NAME_LEN 12
char name_[TCLCL_NAME_LEN];
#else /* ! 0 */
char *name_;
#endif /* 0 */
inline const char* name() { return (name_); }
void name(const char*);
2.2.2 执行新对象的构造函数。任何的特定的用户输入参数都将作为构造函数的参数传入。这个构造函数必调用其父类的构造函数。
在上面的例子里,Agent/SRM/Adaptive在第一行就调用了其父类。
需要注意的是,每个构造函数,依次调用其父类的构造函数。 那么ns中的最后一个构造函数就是TclObject的构造函数。
这个构造函数用来创建对应的shadow对象,并执行其它的初始化工作与绑定。因此最好在构造函数先调用父类的构造函数,然后再初始化。这样可以使shadow对象先被建立,从而有变量可以绑定。
2.2.3 TclObject的构造函数为Agent/SRM/Adaptive类调用create-shadow{}实例过程。
2.2.4 当shadow对象建立以后,ns为编译对象调用所有的构造函数,它们每个都有可能为类中的对象建立相应的变量绑定,同时执行其它的必要的初始化工作。因此我们最好把调用父类的构造函数的语句放在类初始化语句之前。
2.2.5 在shadow对象成功创建后。这样就通过create_shadow(void)将创建的TclObject对象添加到tcl类的hash表中,如上文。使cmd{}成为一个新创建的解释对象的实例化过程。这个实例化过程会调用编译对象中的command()方法。在接下来的一节里,我们将介绍command方法是如何定义并被调用的。