您的位置:首页 > 编程语言 > C语言/C++

NS2笔记 之 OTcl与C++

2011-11-29 19:35 363 查看
最近一直在理解NS2所谓的分裂对象模型,想知道ns是如何通过Otcl来创建C++对象的。下面把学习的一些心得总结一下。

Otcl其实就是面向对象的tcl,这和C++和C的关系是一样的。

Otcl和Tcl一样是可嵌入的,它提供了C的API接口,我们可以通过这些接口来访问Otcl对象。主要的接口描述见~/otcl/otcl.h文件

Otcl的基类是Object,所有的类都是从Object派生出来的。该类包含了对象的初始化和销毁过程,添加类成员函数和成员变量及其查询的一些方法。归纳如下:

Class创建一个对象
Destroy销毁一个对象
proc定义Tcl对象方法
set定义Tcl对象变量
instvar绑定实例变量
有关Otcl的详细语法可见:

http://bmrc.berkeley.edu/research/cmt/cmtdoc/otcl/

下面开始来看看是怎么通过OTcl来操纵C++对象的:

一、C++对象的创建

NS2采用的分裂对象模型,即每个C++的类都有一个Otcl类与之相对应。NS2中所有类的基类是TclObject,而Otcl中的基类则为SplitObject。下面首先看一下SplitObject这个类的定义(~/tclcl.*/Tcl-object.tcl):

Class SplitObject

SplitObject set id 0

SplitObject instproc init args {

$self next if [catch "$self create-shadow"text" > $args"] {error"__FAILED_SHADOW_OBJECT_" "" }

}

SplitObject instproc destroy {}{$self delete-shadow $self next }


由于每一个Otcl对象的创建都要调用其init过程,因此所有继承于SplitObject的Otcl类的初始化最终都会调用到SplitObject的init()这个instproc。

从上面代码中可以看到,init()实际上是调用了一个叫create-shadow的函数,这个函数就是用来创建C++对象的,这在后面会分析到。

同时,SplitObject也重载了destroy()函数,调用了一个叫delete-shadow的方法,顾名思义,也就是销毁相应的C++对象。

注意:Otcl并不会像C++那样会自动调用父类的构造函数,而必须显示的进行调用,也就是上面的: $self
next

在Otcl脚本中是通过new命令来创建一个Otcl对象,通过delete来销毁一个对象的。这两个命令在~/tclcl.*/Tcl-object.tcl中有定义:

#创建一个object

#调用该类的create函数

proc new {className args }{

set o [SplitObject getid]

if [catch "$className create $o $args" msg] {

if [string match"__FAILED_SHADOW_OBJECT_" $msg] {

# # The shadow object failed to be allocated. # delete $o return ""

}

global errorInfo error "class $className: constructor failed: $msg" $errorInfo

}

return $o

}

#销毁一个object proc delete o {$o delete_tkvar $o destroy }


可以看到,new方法实际上是调用了类中的一个create函数来进行初始化,那么这个create函数究竟是怎么一回事呢?再回过头来仔细看看Otcl的语法:"The create instproc
provides a mechanism for classes to create other classes and objects"

原来create就是用来创建一个Otcl解释对象的,在对象创建时会调用其init()方法,最终就调用create-shadow来创建一个与之相对应的C++shadow对象。

那么问题又来了,create-shadow又是怎么创建C++对象?还有它又怎么知道要创建哪个C++类对应的对象的呢?接下来就来看看Otcl类和C++类是怎么关联起来的。在Otcl基类SplitObject中提供了一个名为register的方法:

// This routine invoked by TclClass::bind.

//注册类名

SplitObject proc register className {

set classes [split $className /]

set parent SplitObject

set path ""

set sep ""

foreach cl $classes {

set path $path$sep$cl

if ![$self is-class $path] {

Class $path -superclass $parent

}

set sep /

set parent $path

}

}


通过上面的代码可以看出,register的功能其实就是定义一个指定classname的Otcl类。

在这还得简单介绍一下NS2中的类命名规则。NS2是使用字符’/’来作为分割符以表示类之间的继承关系的。具体据个例子,如名为”Agent/TCP/Reno”的Otcl类,它就是继承于“Agent/TCP”类的;同理“Agent/TCP”则是继承于“Agent”类,而Agent类则最后继承于基类SplitObject。知道了类的命名规则,上面的代码就很好理解了,它也就是根据提供的classname利用分隔符进行分割后创建相应的派生类。其中-superclass就表明了类之间的继承关系。

那么又是谁来调用这个register方法进行Otcl类注册的呢?这时候就得把TclClass给介绍出来了,终于可以从Otcl脚本转向熟悉的C++了~~。TclClass是个纯虚类,它封装了Otcl类的注册机制。废话少说,先看看这个类的定义(~/tclcl.*/tclcl.h)

class TclClass

{

public:

static void init();

virtual ~TclClass();

protected:

TclClass(const char* classname);

virtual TclObject* create(int argc, constchar*const*argv) = 0;

private:

static int create_shadow(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[]);

static intdelete_shadow(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[]);

static int dispatch_cmd(ClientData clientData, Tcl_Interp *interp, intargc, CONST84 char *argv[]);

static int dispatch_init(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[]);

static int dispatch_instvar(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[]);

static TclClass* all_;

TclClass* next_;

protected:

	virtual void otcl_mappings() {}

virtual voidbind();

virtual int method(int argc, const char*const* argv);

void add_method(const char* name);

static int dispatch_method(ClientData, Tcl_Interp*, int ac, CONST84 char** av);

OTclClass* class_;

const char* classname_;

};


首先来分析一下它的构造函数TclClass::TclClass(const char*
classname);

TclClass::TclClass(const char* classname) : class_(0), classname_(classname)

{

	if (Tcl::instance().interp()!=NULL) {// the interpreter already exists! 

bind();

}else{// the interpreter doesn't yet exist      	       //add this class to a linked list that is traversed when the interpreter is created

next_ = all_;

all_ = this;

}

}


嗯,看起来是非常的简单。。。在判断Otcl解释器存在之后就调用了一个名为bind的成员函数,现在再来看看这个bind函数的具体实现:

void TclClass::bind()

{

Tcl& tcl = Tcl::instance(); //Register classname in OTCL

tcl.evalf("SplitObject register %s", classname_);

class_ = OTclGetClass(tcl.interp(), (char*)classname_); //Add 2 method for this class //create-shadow & delete-shadow

OTclAddIMethod(class_, "create-shadow", create_shadow, (ClientData)this, 0);

OTclAddIMethod(class_, "delete-shadow", delete_shadow, (ClientData)this, 0);

otcl_mappings();

}


这下就豁然开朗了,原来是bind函数调用SplitObject的register方法注册了一个名为classname_Otcl类,然后再在该类中添加了两个方法:create-shadow和delete-shadow,这就是我们上面讲到的在创建和销毁Otcl对象是调用的两个方法,它们分别和TclClass中的两个成员函数:create_shadow和delete_shadow绑定了起来。那么现在就来看看create_shadow的部分实现(~/tclcl.*/):

int TclClass::create_shadow(ClientData clientData, Tcl_Interp *interp, int argc, CONST84 char *argv[])

{

TclClass* p = (TclClass*)clientData;

TclObject* o = p->create(argc, argv);

/*以下代码省略…*/

}


从开头的代码可以看出,它其实是调用了纯虚函数virtual TclObject*
create(int argc, const char*const*argv)
= 0; 那么这个create函数究竟又是干什么的呢?接下来以TclClass的一个派生类AgentClass为例来说明,其定义如下(~/ns2.*/common/Agent.cc):

static class AgentClass : public TclClass

{

	public:AgentClass() : TclClass("Agent") {}

	TclObject* create(int, const char*const*) {return (new Agent(PT_NTYPE)); }

}class_agent;


首先我们注意到了关键字static,说明AgentClass是一个静态类,也就是说当NS2刚开始初始化的时候,便会调用该类的构造函数,而构造函数又做了什么工作呢?上面我们已经分析了,其实就是在Otcl中注册了一个名为Agent的Otcl解释类。当Otcl脚本调用new方法实例化这个Agent类的时候,最终会调用到AgentClass的create函数,该create函数的实现很简单,就是实例化了一个C++的Agent对象,并返回该对象的指针。

至此Otcl对象终于和C++对象关联起来了。

原文链接:/article/9917116.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: