您的位置:首页 > 其它

NS2 Tclcl机制分析, 编译层/解释层交互过程 ------ NS2学习日记 (2)

2014-12-16 16:16 736 查看
NS2中使用了tclcl模块实现编译层与解释层的交互,下面将依据TclClass/TclObject以及SatNode模块简要分析交互的过程 。

如果有疏漏的地方希望能够帮忙指正。

详细的用户手册可以参见文档《NS Manual》/《ns-chinese-manual》。

Tclcl 模块: 主要包含了Tcl, TclObject, TclClass, InstVar, TclCommand, EmbeddedTcl等c++类:



Tcl 类: 是解释器的实例, 并提供了c++代码访问解释器的接口。
TclObject 类 : 所有编译层类的基类, 我们所编写的新模块必须继承该类或它的子类。
TclClass 类 : 向解释器注册解释层类, 并将对应的解释层类与编译层类进行绑定(TclClass::create方法)。
InstVar 类 : 编译层类与解释层类中成员变量进行绑定。
TclCommand 类 : 注册解释器全局命令。
EmbededTcl 类 : 动态实现装载脚本。

下面主要进行TclObject, TclClass 的分析, 模块SatNode为例, 进行如下分析 :

1. 如何向NS添加一个模块, 在添加模块的过程中是怎样的执行过程 ?

2. 在ns中使用新的类时是怎样的执行过程, 如何实现 ?

3. 创建解释对象时, 如何实现变量的绑定 ?

1. 如何向NS添加一个模块, 在添加模块的过程中是怎样的执行过程 ?

首先需要创建两个类, 与该模块对应的TclObject类以及TclClass类。

以SatNode为例, 我们需要创建

class SatNode : public Node {}
static class SatNodeClass : public TclClass {
public:
SatNodeClass() : TclClass("Node/SatNode") {}
TclObject* create(int , const char*const* ) {
return (new SatNode);
}
} class_satnode;
其中Node类为TclObject的子类。

SatNodeClass继承了TclClass类, 该静态类创建了一个全局实例对象, 所以在ns运行过程中会实例化对象class_satnode, 从而执行SatNodeClass的初始化函数。

SatNodeClass的初始化函数调用了父类TclClass的初始化函数, TclClass("Node/SatNode"), TclClass的初始化函数如下:

TclClass::TclClass(const char* classname) : class_(0), classname_(classname)
{
if (Tcl::instance().interp()!=NULL) {
bind();
}
...
}
初始化函数将执行TclClass::bind函数:

void TclClass::bind()
{
Tcl& tcl = Tcl::instance();
tcl.evalf("SplitObject register %s", classname_);
class_ = OTclGetClass(tcl.interp(), (char*)classname_);
OTclAddIMethod(class_, "create-shadow",
(Tcl_CmdProc *) create_shadow, (ClientData)this, 0);
OTclAddIMethod(class_, "delete-shadow",
(Tcl_CmdProc *) delete_shadow, (ClientData)this, 0);
otcl_mappings();
}

bind函数中tcl.evalf("SplitObject register %s", classname_)语句为调用解释器api(evalf)执行tcl语句, 功能是向解释器内注册新类, 类名为classname也就是"Node/SatNode", 在注册过程中将进行一定的层次分析, 可以参见SplitObject proc register className{}(在tclcl/tcl-object.tcl内)。

接着向解释器内该类添加方法, create-shadow/deleteshadow函数, 这将在解释类的实例创建时调用。

同时SatNodeClass覆盖了TclClass的虚函数, 并且创建一个新的编译实例(new SatNode)作为返回参数, 这也会在解释层实例创建时调用。

至此, 已经向解释器注册了新的模块SatNode。(模块的具体实现在下面介绍, 新模块的编译链接可以参考网上的文档)

2. 在ns中使用新的类时是怎样的执行过程, 如何实现 ?

在解释器中, 我们可以使用 new Node/SatNode创建一个解释类的实例, 参见Simulator instproc newsatnode {} (ns/tcl/lib/ns-lib.tcl)。

这将调用函数 proc new { className args } (/tclcl/tcl-object.tcl)

proc new { className args } {
set o [SplitObject getid]
if [catch "$className create $o $args" msg] {
该函数又会调用 create 函数。

Class instproc create {obj args} {
set h [$self info heritage]
foreach i [concat $self $h] {
if {[$i info commands alloc] != {}} then {
set args [eval [list $i] alloc [list $obj] $args]
$obj class $self
eval [list $obj] init $args
return $obj
}
}
error {No reachable alloc}
}

在create函数中, 将根据解释类的层次, 对当前的解释类进行继承层次解析,并依次分配内存。,之后eval [list $obj] init $args调用当前解释类的init函数(注 : 一般所有的init函数第一条语句都是调用父类的init函数,而SplitObject是所有TclClass创建解释类的基类, 所以会首先调用SplitObject的init函数)。

SplitObject instproc init args {
$self next
if [catch "$self create-shadow $args"] {
error "__FAILED_SHADOW_OBJECT_" ""
}
}
init函数将调用该解释类的create-shadow函数, 该函数在SatNodeClass创建实例的过程中已经向注册器中注册。

int TclClass::create_shadow(ClientData clientData, Tcl_Interp *interp,
int argc, CONST84 char *argv[])
{
TclClass* p = (TclClass*)clientData;
TclObject* o = p->create(argc, argv);
Tcl& tcl = Tcl::instance();
if (o != 0) {
o->name(argv[0]);
tcl.enter(o);
if (o->init(argc - 2, argv + 2) == TCL_ERROR) {
tcl.remove(o);
delete o;
return (TCL_ERROR);
}
tcl.result(o->name());
OTclAddPMethod(OTclGetObject(interp, argv[0]), "cmd",
(Tcl_CmdProc *) dispatch_cmd, (ClientData)o, 0);
OTclAddPMethod(OTclGetObject(interp, argv[0]), "instvar",
(Tcl_CmdProc *) dispatch_instvar, (ClientData)o, 0);
o->delay_bind_init_all();
return (TCL_OK);
} else {
tcl.resultf("new failed while creating object of class %s",
p->classname_);
return (TCL_ERROR);
}
}
该函数内首先调用编译类(TclClass)的create函数, 创建一个对应TclObject的实例对象, create函数一般创建一个对应TclObject的实例作为返回参数。(注意构造函数调用过程中将会依次调用父类的构造函数, 同时一般会在构造函数内进行解释实例以及编译实例的变量绑定工作, 在下一节分析)

接下来, 将根据解释器内创建类实例时提供的参数进行TclObject的初始化, o->init(argc - 2, argv + 2), o是create函数创建的解释类实例, 调用init函数进行变量的绑定。注意该函数是一个虚函数, 可以在实现的类中进行自己的定义。

而接下来OTclAddPMethod(OTclGetObject(interp, argv[0]), "cmd", (Tcl_CmdProc *) dispatch_cmd, (ClientData)o, 0)等语句则向解释器内该解释类实例添加了两个命令cmd和instvar。(关于cmd的内容可以前面介绍的参考文档中

至此, 一个解释类实例的创建过程已经完成。

3. 创建解释对象时, 如何实现变量的绑定 ?

在上一节中讲到在调用编译类的构造函数时将会依次调用父类的构造函数, 同时在ns中, 一般在编译类的构造函数中进行instvar的绑定, 如在SatNode中 :

SatNode::SatNode() : ragent_(0), trace_(0), hm_(0)
{
bind_bool("dist_routing_", &dist_routing_);
}
调用bind_bool函数,进行成员变量“dist_routing_”的绑定, bind_bool实现如下。

#define TOB(FUNCTION, C_TYPE, INSTVAR_TYPE, OTHER_STUFF) \
void TclObject::FUNCTION(const char* var, C_TYPE* val) \
{ \
create_instvar(var); \
OTHER_STUFF; \
init(new INSTVAR_TYPE(var, val), var); \
}
TOB(bind_bool, int, InstVarBool, ;)
通过“#define” 功能实现了bind_bool的定义, 在函数中存在两个语句create_instvar(var)以及init(new InstVarBool(var, val), var), 其中create_instvar定义如下:

void TclObject::create_instvar(const char* var)
{
/*
* XXX can't use tcl.evalf() because it uses Tcl_GlobalEval
* and we need to run in the context of the method.
*/
char wrk[256];
sprintf(wrk, "$self instvar %s", var);
Tcl_Eval(Tcl::instance().interp(), wrk);
}
调用instvar命令, 进行解释器内全局变量的声明(?), 接着init(new InstVarBool(var, val), var)语句进行变量的绑定以及初始化的工作。

void TclObject::init(InstVar* v, const char* var)
{
insert(v);
v->init(var);
}

在TclObject的实例中, 所有的InstVar是存放在一个链表中的, 在init函数中进行了入表和初始化的操作。

当所有的构造函数完成, 也就实现了解释类成员变量和编译类成员变量的绑定。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐