转自:http://www.baisi.net/thread-65064-1-1.html
经过一段时间的代码阅读和资料查阅,在这里我想试着讲明一个困扰大多数NS2 Beginner的问题:Otcl和C++的交互,我们写的新协议(假若有的话)是如何被NS2执行的。
就简单的从我们现有的来自长庚大学的802.16的补丁说起。移植完16的补丁我们的NS2就可以执行MAC层协议为“Mac/802_16“的tcl脚本代码。但是这个补丁(wimax_v2.03)里面的代码全都是用C++编写的底层代码,在tcl脚本中设置MAC层协议为Mac/802_16,tcl解释器是如何正确的知道是执行我们的补丁呢?
这是tcl脚本中设置16协议的地方:
set val(chan) Channel/WirelessChannel ;# channel type set val(prop) Propagation/TwoRayGround ;# radio-propagation model set val(netif) Phy/WirelessPhy ;# network interface type set val(mac) Mac/802_16 ;# MAC type ………………………………………… ………………………………………… $ns node-config -adhocRouting $val(rp) \ -llType $val(ll) \ -macType $val(mac) \ ………………………………………… …………………………………………
static class Mac802_16Class : public TclClass { public: //构造函数Mac802_16Class()将Otcl中的类名Mac/802_16作为参数传给其父类TclClass的构造函数; //要注意,这里实际上是创建了两个类:Mac和802_16,并且802_16是Mac的子类; Mac802_16Class() : TclClass("Mac/802_16") {} //而create方法则创建一个要与Otcl类对应的C++类的对象实例,然后返回; TclObject* create(int, const char*const*) { return (new Mac802_16()); } } class_mac802_16;
TclClass::TclClass(const char* classname) : class_(0), classname_(classname) { if (Tcl::instance().interp()!=NULL) { //如果Otcl语言解释器已存在的话,调用bind(): // 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; } }往下找到bind():
void TclClass::bind() { //获取Tcl Tcl& tcl = Tcl::instance(); //在Otcl环境中注册该类名:Mac802_16,其父类是SpliteObject //需要注意的是:SpliteObject存在于otcl环境中,与C++中的TclObject相对应 tcl.evalf("SplitObject register %s", classname_); //注册了之后,为这个类添加两个命令:create-shadow和delete-shadow,注意:这两个命令的执行程序实际上就是TclClass类的create_shadow()和TclClass::delete_shadow(). 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(); }
然后当我们在ns脚本中:new Mac802_16时,在tclcl-1.17/tcl-object.tcl中: |
proc new { className args } { set o [SplitObject getid] //调用了该类的create函数,即Mac802_16:create()函数,也就是调用了其父类SpliteObject: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 }但是问题出现了:实际上SpliteObject并没有实现create()函数!如何解决呢?我们往上找找看SpliteObject类是如何声明的:Class SpliteObject,原来这实际上是调用了Class的Create函数:
Class instproc create() { ... alloc(); init(); ... }这就会调用SpliteObject instproc init()函数
SplitObject instproc init args { $self next //调用类的create-shadow函数,在这个例子中,就是调用了Mac802_16 instproc create_shadow函数 //如前面所讲,也就是调用了TclClass::create-shadow()函数 if [catch "$self create-shadow $args"] { error "__FAILED_SHADOW_OBJECT_" "" } }
int TclClass::create_shadow(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[]) { TclClass* p = (TclClass*)clientData; //在这里调用了Mac802_16Class::create()函数,也就是调用了C++环境中的:new Mac802_16,到这里为止,otcl中的Mac802_16类对应的shadow object(影象对象)就生成了 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()); //在这里再次为otcl中的类Mac802_16添加两个命令:cmd和instvar,其中cmd命令是meet the Tcl Unknown mechanism——Tcl的unknown机制,这样一来,当你在ns脚本中输入了一个该类未知的命令,Tcl的unknown机制就会调用该类的cmd命令,具体的过程可以翻看NS手册的相应部分,有比较详细的说明; //而cmd()命令激活影像对象的command()方法,并将cmd()的参数以向量的形式传递给command()方法,因此在实现某类的C++部分时,你必须实现该类的Command()过程,仔细看看NS2中的大部分类,是不是都有一个Command()函数?其实就是这么来的 OTclAddPMethod(OTclGetObject(interp, argv[0]), "cmd", dispatch_cmd, (ClientData)o, 0); OTclAddPMethod(OTclGetObject(interp, argv[0]), "instvar", 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); } }command()这个函数实现所有的命令分发,下面摘抄手册的一部分来说明command()的定义(ASRMAgent::command()为例):
int ASRMAgent::command(int argc, const char*const*argv) { Tcl& tcl = Tcl::instance(); if (argc == 3) { if (strcmp (argv[1],"distance?") == 0) { int sender = atoi (argv[2]); SRMinfo* sp = get_state(sender); tcl.tesultf("%f", sp->distance_); return TCL_OK; } 12 } return (SRMAgent::command(argc,argv)); }函数调用时需要两个参数:argc和argv,第一个参数(argc)代表解释器中该行命令说明的参数个数。