NS2中的网络构件一般是由相互关联的两个类来实现的,分别是C++类和OTcl类,这种方式称为分裂对象模型。构件的主要功能是在C++类中实现的,OTcl类用来向用户提供操作C++对象的接口。在NS2中,C++类和OTcl类通常具有对应关系,两者的继承关系保持一致。当实例化一个构件对象时,NS2会同时创建一个OTcl对象和一个与之对应的C++对象。两者的映射和互操作是由Tclcl(OTcl C++ Linkage)机制实现的。Tclcl提供了Tcl、TclObject、TclClass、TclCommand、EmbeddedTcl、InstVar六个类。
Tcl | 提供了C++访问OTcl解释器的方法 |
TclObject | 是所有具有映射关系的OTcl类和C++类的基类 |
TclClass | 定义了OTcl类层次和用户创建TclObject对象的接口 |
TclCommand | 用以定义简单的全局解释命令 |
EmbeddedTcl | 用于定义Tcl命令 |
InstVar | 提供了OTcl访问C++类属性的方法 |
Tclcl主要通过TclObject、TclClass(两个C++类)和SplitObject(OTcl类),实现OTcl类和C++类的关联的。
下面,我们以创建一个Agent/UDP对象为例,解释Tclcl的具体实现方法。
当我们执行 new Ageng/UDP 命令的时候,会调用到tclcl/tcl-object.tcl中的new{}过程
- proc new { className args } {
- # 获取一个唯一的id,形如_oX,其中X表示对象被创建的次序
- set o [SplitObject getid]
- # 下面调用create{}过程,实际上调用的是Class的create{}(Agent/UDP没有create{})
- # 在otcl/doc/class.html文件中,说明了该create{}是在$className没有定义create{}时被unkown{}调用的
- # create{}提供了创建其他对象和类的机制
- 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
- }
运行ns命令,%Agent/UDP info instcommands,输出结果如下,不含有create(如果执行%Agent/UDP info instprocs则输出只有process_data、done)
% Agent/UDP info instcommands
process_data done delete-shadow create-shadow
%
instprocs只给出类的方法,instcommands还会给出通过C++添加的方法
继续分析Class的create{},class.html给出了该过程的概念定义
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}
}
而调用该过程的unknwon过程定义如下
Class instproc unknown {m args} {
if {$m == {create}} then {
error "$self: unable to dispatch $m"
}
eval [list $self] create [list $m] $args
}
此时,两个过程中的$self应该是Agent/UDP,$self info heritage即 Agent SplitObject Object,三者都是Agent/UDP的基类
create{}继续检查Agent/UDP Agent SplitObject Object是某含有alloc这个命令,经过实测,确定Object类含有alloc
% Agent/UDP info commands alloc
% Agent info commands alloc
% SplitObject info commands alloc
% Object info commands alloc
alloc
%
然后,调用Object的alloc{}过程分配对象 (还需跟踪),设置新创建的对象的类型为Ageng/UDP
然后调用init{},通过 * info instprocs可以判断出,Agent/UDP继承了Agent的init{}过程
而Agent 的int{}过程定义在gen/ns_tcl.cc中
Agent instproc init args {\n\
eval $self next $args\n\
} \n\
可见,其调用基类SplitObject的init{},SplitObjecting定义在tclcl/tcl-object.tcl文件中
- SplitObject instproc init args {
- $self next
- if [catch "$self create-shadow $args"] {
- error "__FAILED_SHADOW_OBJECT_" ""
- }
- }
最后,调用Agent/UDP的create-shadow{},到了这里,就需要结合C++源码来分析了。
首先,来看类UdpAgentClass
- static class UdpAgentClass : public TclClass {
- public:
- UdpAgentClass() : TclClass("Agent/UDP") {}
- TclObject* create(int, const char*const*) {
- return (new UdpAgent());
- }
- } class_udp_agent;
UdpAgentClass派生自TclClass(一个纯虚类),并实现了create函数,返回一个UdpAgent实例。
需要注意的是, 这里定义了一个静态对象,也就是说在NS启动是,就会调用构造函数,即执行TclClass("Agent/UDP")
现在,来分析一下TclClass("Agent/UDP")所做的工作。
- TclClass* TclClass::all_;
- TclClass::TclClass(const char* classname) : class_(0), classname_(classname)
- {
- if (Tcl::instance().interp()!=NULL) {
- // the interpreter already exists!
- // this can happen only (?) if the class is created as part
- // of a dynamic library
- 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;
- }
- }
在初始化列表中,成员变量classname_被初始化为"Agent/UDP"
我们应经打开了解释器,进入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的一个实例,可以通过它来操作OTcl对象。
然后,通过evalf方法执行了Tcl命令:SplitObject regsiter Agent/UDP,我们来看看它做了什么工作
- 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
- }
- }
该过程的工作就是注册一系列类,比如,执行%SplitObject A/B/C将会创建三个类,A、A/B、A/B/C,都派生自SplitObject,
并且,A/B说明了B是A的派生类
继续分析,看到了create-shadow,SplitObject 的init{}调用的create-shadow正是OTclAddIMethod添加的,
参数"create-shadow"指明了在OTcl中的方法名称,OTclAddIMethod添加了OTcl类Agent/UDP的create_shadow{}和
TclClass的create_shadow()的映射关系。SplitObject 的init{}调用create-shadow{}时,就会调用TclClass的create_shadow()。
而且this指针,即前面定义的静态对象class_udp_agent的指针,作为参数被传入(篇幅受限,咱不详述)
再来看TclClass中的create_shadow方法,代码如下
- int TclClass::create_shadow(ClientData clientData, Tcl_Interp *interp,
- int argc, CONST84 char *argv[])
- {
- /*
- * clientData正是上述OTclAddIMethod函数传入的静态对象class_udp_agent的指针
- * 然后,调用类UdpAgentClass的create方法,简单返回一个UdpAgent对象指针
- */
- TclClass* p = (TclClass*)clientData;
- TclObject* o = p->create(argc, argv);
- Tcl& tcl = Tcl::instance();
- if (o != 0) {
- /*
- * argv[0]是新创建的对象的名称,就是前面提到的_oX
- * enter()方法将该对象名和对象指针o添加到OTcl中的hash表中,这样就可以在Tcl中通过对象名访问C++对象了
- */
- 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
- */
- tcl.result(o->name());
- /*
- * 给对象所属类,即Agent/UDP添加新的方法:cmd{}和instvar{},分别映射到TclClass的dispatch_cmd()和dispatch_instvar()
- * 在Tcl中执行_oX cmdx args时,将会调用到dispatch_cmd()
- */
- 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);
- }
- }
来看dispatch_cmd()方法
- int TclClass::dispatch_cmd(ClientData clientData, Tcl_Interp *,
- int argc, CONST84 char *argv[])
- {
- TclObject* o = (TclObject*)clientData;
- return (o->command(argc - 3, argv + 3));
- }
参数clientData正是OTclAddPMethod()的参数o,本例中,o就是一个UdpAgent对象指针
class UdpAgent声明和command()实现如下
- class UdpAgent : public Agent {
- public:
- UdpAgent();
- UdpAgent(packet_t);
- virtual void sendmsg(int nbytes, const char *flags = 0)
- {
- sendmsg(nbytes, NULL, flags);
- }
- virtual void sendmsg(int nbytes, AppData* data, const char *flags = 0);
- virtual void recv(Packet* pkt, Handler*);
- virtual int command(int argc, const char*const* argv);
- protected:
- int seqno_;
- };
- int UdpAgent::command(int argc, const char*const* argv)
- {
- if (argc == 4) {
- if (strcmp(argv[1], "send") == 0) {
- PacketData* data = new PacketData(1 + strlen(argv[3]));
- strcpy((char*)data->data(), argv[3]);
- sendmsg(atoi(argv[2]), data);
- return (TCL_OK);
- }
- } else if (argc == 5) {
- if (strcmp(argv[1], "sendmsg") == 0) {
- PacketData* data = new PacketData(1 + strlen(argv[3]));
- strcpy((char*)data->data(), argv[3]);
- sendmsg(atoi(argv[2]), data, argv[4]);
- return (TCL_OK);
- }
- }
- return (Agent::command(argc, argv));
- }
- int UdpAgent::command(int argc, const char*const* argv)
- {
- if (argc == 4) {
- if (strcmp(argv[1], "send") == 0) {
- PacketData* data = new PacketData(1 + strlen(argv[3]));
- strcpy((char*)data->data(), argv[3]);
- sendmsg(atoi(argv[2]), data);
- return (TCL_OK);
- }
- } else if (argc == 5) {
- if (strcmp(argv[1], "sendmsg") == 0) {
- PacketData* data = new PacketData(1 + strlen(argv[3]));
- strcpy((char*)data->data(), argv[3]);
- sendmsg(atoi(argv[2]), data, argv[4]);
- return (TCL_OK);
- }
- }
- return (Agent::command(argc, argv));
- }
- int UdpAgent::command(int argc, const char*const* argv)
- {
- if (argc == 4) {
- if (strcmp(argv[1], "send") == 0) {
- PacketData* data = new PacketData(1 + strlen(argv[3]));
- strcpy((char*)data->data(), argv[3]);
- sendmsg(atoi(argv[2]), data);
- return (TCL_OK);
- }
- } else if (argc == 5) {
- if (strcmp(argv[1], "sendmsg") == 0) {
- PacketData* data = new PacketData(1 + strlen(argv[3]));
- strcpy((char*)data->data(), argv[3]);
- sendmsg(atoi(argv[2]), data, argv[4]);
- return (TCL_OK);
- }
- }
- return (Agent::command(argc, argv));
- }