转载网址:
http://hi.baidu.com/aoxinguy/item/e206041637d450721109b57f
new Simulator 是每个脚本中必须写的一句,而且,也只能写一句(原因见另一贴)
接下来,我们通过代码跟踪(C++和Otcl层次)来看一下,new Simulator到底会做些什么~
以 set ns [new Simulator] 为例
首先 new 属于otcl在tcl-object.tcl 文件中定义的一个全局过程,其定义如下:
proc new { className args } {
set o [SplitObject getid] // getid 只是将SplitObject的静态变量 id值加1, 初始化时为0, 需要注意的是:
getid 中的最后一句,return _o$id,此时返回的实际上就是待创建的OTCl object的唯一ID值,可以看成是引用
if [catch "$className create $o $args" msg] { //此时className是Simulator,o为全局唯一ID值,作为待创建的Simulator实例的参数传入。其作用实际是留在create-shadow中,用来赋值新创建的C++影像对象的name属性(C++对象的父类TclObject有一个属性char* name)
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
}
*****
$className create $o $args 这句话比较关键。 OTCL与C++绑定的巧妙之处就在于 create 是只有OTCL的祖先类 Class 才有的 成员变量 (OTCL语言定义的,写在脚本引擎中去了,查看tcl文件无法找到这个定义;就像很多关键字或者内建命令的定义是写在引擎中一样。tcl文件中的都是拓展定义)!
因此,无论是任何OTCL类,都需要用new 来创建,然后 new 又会调用祖先类的 create 过程
******************************************************************************************************************************
{create过程的定义 在 OTCL参考中是这样定义的:但这只是参考,NS2中可能会对其有所修改,通过tcldebug是无法查看到其中的代码的~~~(因为它是内置的?)
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}
}
}
*********************************************************************************************************************************
{附注: 其实,在OTCL中,OTCL层次上实例变量的创建工作非常简单(复杂的是如何创建对应的C++影像实例): 首先,set o [SplitObject getid] 直接获得实例变量的全局唯一ID值(可以理解为地址值~);接下来,所有OTCL层次的创建工作都在 "$className create $o $args" 这句代码中完成。如前所讲,因为create是OTCL语言的内部实现,因此,无法跟踪至其源码 (tcl调试中,从create会直接跳至init); 但,我大概猜测,依据传入的$className 以及$o等参数,OTCL语言本身完成的工作是:将$o 以及$className建立类与实例的关系。 这样,当我们输入 Class info instance命令时,便可以知道$o 指向的OTCL变量是$className类的一个实例了。 从这个意义上来讲,OTCL实例根本不存在创建失败的情况,顶多也就是它对应的C++影像类创建失败了} -----这段话,待证实
在create过程中,会调用init()过程,而这个过程,却是各个OTCL类各自负责具体实现的。
在SplitObject的各个子类的init过程中,总会在最后通过 $self next $args 来调用父类的init过程,依次一直上溯至祖先类SplitObject。因此,SplitObject的init过程是每个OTCL类通过 new 创建时,一定会执行的过程(从子类至父类)。因此,将OTCL与C++绑定机制的具体实现放至这个地方是最合适不过的了。
先引用一下 SplitObject类的init过程定义:
SplitObject instproc init args {
$self next
if [catch "$self create-shadow $args"] {
error "__FAILED_SHADOW_OBJECT_" ""
}
}
由上面可知,这个过程是new 一个 OTCL实例时,最后执行的一步。这里,我暂且不剖析这个过程的执行情况;因为 Simulator类的 init过程是先于上面这段代码执行的,因此,我们先跳至分析Simulator的init过程(定义在ns-lib.tcl中),代码引用如下:
Simulator instproc init args {
# Debojyoti added this for asim
$self instvar useasim_ //定义一些仿真类需要用到的变量,具体意义尚未完全清楚,待以后查明后补上
$self instvar slinks_
$self instvar nconn_
$self instvar sflows_
$self instvar nsflows_
set slinks_(0:0) 0
set nconn_ 0
set conn_ ""
# for short flows stuff
set sflows_ ""
set nsflows_ 0
set useasim_ 0
$self create_packetformat //创建包结构(这又是一个比较大的话题~涉及比较多,需另开贴)
$self use-scheduler Calendar //指定默认的调度器类型
#$self use-scheduler List
$self set nullAgent_ [new Agent/Null] //nullAgent_是要用在整个仿真场景中,任何需要将包进行丢弃处理的地方,实际上就相当于一个垃圾包回收站,只负责将包释放掉~
$self set-address-format def
if {[lindex $args 0] == "-multicast"} { //如果打开多播的话,还需要运行另外的代码进行配置
$self multicast $args
}
eval $self next $args //调用父类的init过程,也就是直接调用SplitObject的init过程,代码见上段
}
Simulator类的init过程中涉及的代码,均已在代码后作了相关注释,就不再多说明。接下来,我们回到先前讲的SplitObejct 的init过程,也就是所有OTCL实例创建时,都要执行的过程;代码再次引用如下:
SplitObject instproc init args {
$self next
if [catch "$self create-shadow $args"] {
error "__FAILED_SHADOW_OBJECT_" ""
}
}
注:1. 此处$args一般传入的是已创建的OTCL实例唯一ID( _o+ interger形式),其用处 前面已有说明
2. catch的作用是,无论 catch中的command是否成功执行,都不会中止;而只会根据结果返回1或0
这里,我们将讲到OTCL与C++绑定或者说是tclcl机制中最关键的一点:
首先,自问:$self create-shadow $args 何以会精确地调用到 Simulator C++类的构造函数呢?(当然,我们知道,通过tclclass机制,实际上直接调用的是SimulatorClass类的create函数,而create函数封装了Simulator C++类的构造函数而矣)
通过跟踪代码,我们知道, 当OTCL执行到 $self create-shadow $args以后($self 的值为 _o4, $args为空),直接跳入的是C++的TclClass类的create_shadow 函数,其代码是:
{至于为什么能够直接跳,我推测:由_o4属于Simulator类,NS框架自然有办法,构造好相应的ClientData 等参数后,调用create_shadow函数}
int TclClass::create_shadow(ClientData clientData, Tcl_Interp *interp,
int argc, CONST84 char *argv[])
{
TclClass* p = (TclClass*)clientData; // tclclass机制
TclObject* o = p->create(argc, argv); // Simulator C++类的构造函数就封装在 p->create(argc,argv)中
Tcl& tcl = Tcl::instance();
if (o != 0) {
o->name(argv[0]); //此处 argv[0]的值为 _o4 (),也就是$self的值
tcl.enter(o);
if (o->init(argc - 2, argv + 2) == TCL_ERROR) { //????待跟踪~ eclipse中跟踪
tcl.remove(o);
delete o;
return (TCL_ERROR);
}
//至此,OTCL对应的C++影像类创建完毕,且OTCL实例与C++影像的绑定关系,也通过o->name(argv[0]); tcl.enter(o) 这两句代码建立起来了。给出_o4,便可以获C++实例o的地址值~
tcl.result(o->name()); // ?
OTclAddPMethod(OTclGetObject(interp, argv[0]), "cmd", //为新创建的OTCL实例新添加cmd过程
(Tcl_CmdProc *) dispatch_cmd, (ClientData)o, 0);
OTclAddPMethod(OTclGetObject(interp, argv[0]), "instvar", //为新创建的OTCL实例新添加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函数定义时,都有两个参数,一个是int argc,另一个是const char*const* argv。但,creat函数的具体实现一般都是直接 new Class(); 根本用不到定义的这两个参数。
所以,如果tclclass中的create定义时也不带参数的话,上述代码中的 p->create(argc, argv); 其实完全可以p->create (); 搞不明白,为什么要多添加这两个参数?
当然,用处可能是暂时我们还没发现而矣~留着也无碍 `
new Simulator 是每个脚本中必须写的一句,而且,也只能写一句(原因见另一贴)
接下来,我们通过代码跟踪(C++和Otcl层次)来看一下,new Simulator到底会做些什么~
以 set ns [new Simulator] 为例
首先 new 属于otcl在tcl-object.tcl 文件中定义的一个全局过程,其定义如下:
proc new { className args } {
set o [SplitObject getid] // getid 只是将SplitObject的静态变量 id值加1, 初始化时为0, 需要注意的是:
getid 中的最后一句,return _o$id,此时返回的实际上就是待创建的OTCl object的唯一ID值,可以看成是引用
if [catch "$className create $o $args" msg] { //此时className是Simulator,o为全局唯一ID值,作为待创建的Simulator实例的参数传入。其作用实际是留在create-shadow中,用来赋值新创建的C++影像对象的name属性(C++对象的父类TclObject有一个属性char* name)
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
}
*****
$className create $o $args 这句话比较关键。 OTCL与C++绑定的巧妙之处就在于 create 是只有OTCL的祖先类 Class 才有的 成员变量 (OTCL语言定义的,写在脚本引擎中去了,查看tcl文件无法找到这个定义;就像很多关键字或者内建命令的定义是写在引擎中一样。tcl文件中的都是拓展定义)!
因此,无论是任何OTCL类,都需要用new 来创建,然后 new 又会调用祖先类的 create 过程
******************************************************************************************************************************
{create过程的定义 在 OTCL参考中是这样定义的:但这只是参考,NS2中可能会对其有所修改,通过tcldebug是无法查看到其中的代码的~~~(因为它是内置的?)
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}
}
}
*********************************************************************************************************************************
{附注: 其实,在OTCL中,OTCL层次上实例变量的创建工作非常简单(复杂的是如何创建对应的C++影像实例): 首先,set o [SplitObject getid] 直接获得实例变量的全局唯一ID值(可以理解为地址值~);接下来,所有OTCL层次的创建工作都在 "$className create $o $args" 这句代码中完成。如前所讲,因为create是OTCL语言的内部实现,因此,无法跟踪至其源码 (tcl调试中,从create会直接跳至init); 但,我大概猜测,依据传入的$className 以及$o等参数,OTCL语言本身完成的工作是:将$o 以及$className建立类与实例的关系。 这样,当我们输入 Class info instance命令时,便可以知道$o 指向的OTCL变量是$className类的一个实例了。 从这个意义上来讲,OTCL实例根本不存在创建失败的情况,顶多也就是它对应的C++影像类创建失败了} -----这段话,待证实
在create过程中,会调用init()过程,而这个过程,却是各个OTCL类各自负责具体实现的。
在SplitObject的各个子类的init过程中,总会在最后通过 $self next $args 来调用父类的init过程,依次一直上溯至祖先类SplitObject。因此,SplitObject的init过程是每个OTCL类通过 new 创建时,一定会执行的过程(从子类至父类)。因此,将OTCL与C++绑定机制的具体实现放至这个地方是最合适不过的了。
先引用一下 SplitObject类的init过程定义:
SplitObject instproc init args {
$self next
if [catch "$self create-shadow $args"] {
error "__FAILED_SHADOW_OBJECT_" ""
}
}
由上面可知,这个过程是new 一个 OTCL实例时,最后执行的一步。这里,我暂且不剖析这个过程的执行情况;因为 Simulator类的 init过程是先于上面这段代码执行的,因此,我们先跳至分析Simulator的init过程(定义在ns-lib.tcl中),代码引用如下:
Simulator instproc init args {
# Debojyoti added this for asim
$self instvar useasim_ //定义一些仿真类需要用到的变量,具体意义尚未完全清楚,待以后查明后补上
$self instvar slinks_
$self instvar nconn_
$self instvar sflows_
$self instvar nsflows_
set slinks_(0:0) 0
set nconn_ 0
set conn_ ""
# for short flows stuff
set sflows_ ""
set nsflows_ 0
set useasim_ 0
$self create_packetformat //创建包结构(这又是一个比较大的话题~涉及比较多,需另开贴)
$self use-scheduler Calendar //指定默认的调度器类型
#$self use-scheduler List
$self set nullAgent_ [new Agent/Null] //nullAgent_是要用在整个仿真场景中,任何需要将包进行丢弃处理的地方,实际上就相当于一个垃圾包回收站,只负责将包释放掉~
$self set-address-format def
if {[lindex $args 0] == "-multicast"} { //如果打开多播的话,还需要运行另外的代码进行配置
$self multicast $args
}
eval $self next $args //调用父类的init过程,也就是直接调用SplitObject的init过程,代码见上段
}
Simulator类的init过程中涉及的代码,均已在代码后作了相关注释,就不再多说明。接下来,我们回到先前讲的SplitObejct 的init过程,也就是所有OTCL实例创建时,都要执行的过程;代码再次引用如下:
SplitObject instproc init args {
$self next
if [catch "$self create-shadow $args"] {
error "__FAILED_SHADOW_OBJECT_" ""
}
}
注:1. 此处$args一般传入的是已创建的OTCL实例唯一ID( _o+ interger形式),其用处 前面已有说明
2. catch的作用是,无论 catch中的command是否成功执行,都不会中止;而只会根据结果返回1或0
这里,我们将讲到OTCL与C++绑定或者说是tclcl机制中最关键的一点:
首先,自问:$self create-shadow $args 何以会精确地调用到 Simulator C++类的构造函数呢?(当然,我们知道,通过tclclass机制,实际上直接调用的是SimulatorClass类的create函数,而create函数封装了Simulator C++类的构造函数而矣)
通过跟踪代码,我们知道, 当OTCL执行到 $self create-shadow $args以后($self 的值为 _o4, $args为空),直接跳入的是C++的TclClass类的create_shadow 函数,其代码是:
{至于为什么能够直接跳,我推测:由_o4属于Simulator类,NS框架自然有办法,构造好相应的ClientData 等参数后,调用create_shadow函数}
int TclClass::create_shadow(ClientData clientData, Tcl_Interp *interp,
int argc, CONST84 char *argv[])
{
TclClass* p = (TclClass*)clientData; // tclclass机制
TclObject* o = p->create(argc, argv); // Simulator C++类的构造函数就封装在 p->create(argc,argv)中
Tcl& tcl = Tcl::instance();
if (o != 0) {
o->name(argv[0]); //此处 argv[0]的值为 _o4 (),也就是$self的值
tcl.enter(o);
if (o->init(argc - 2, argv + 2) == TCL_ERROR) { //????待跟踪~ eclipse中跟踪
tcl.remove(o);
delete o;
return (TCL_ERROR);
}
//至此,OTCL对应的C++影像类创建完毕,且OTCL实例与C++影像的绑定关系,也通过o->name(argv[0]); tcl.enter(o) 这两句代码建立起来了。给出_o4,便可以获C++实例o的地址值~
tcl.result(o->name()); // ?
OTclAddPMethod(OTclGetObject(interp, argv[0]), "cmd", //为新创建的OTCL实例新添加cmd过程
(Tcl_CmdProc *) dispatch_cmd, (ClientData)o, 0);
OTclAddPMethod(OTclGetObject(interp, argv[0]), "instvar", //为新创建的OTCL实例新添加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函数定义时,都有两个参数,一个是int argc,另一个是const char*const* argv。但,creat函数的具体实现一般都是直接 new Class(); 根本用不到定义的这两个参数。
所以,如果tclclass中的create定义时也不带参数的话,上述代码中的 p->create(argc, argv); 其实完全可以p->create (); 搞不明白,为什么要多添加这两个参数?
当然,用处可能是暂时我们还没发现而矣~留着也无碍 `