/******************************************************
-------------------2007/09/25------------
-------------------author : hhj------------
******************************************************/
http://blog.baisi.net/?uid-89519-action-viewspace-itemid-848
经过一段时间的代码阅读和资料查阅,在这里我想试着讲明一个困扰大 多数 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) /
…………………………………………
…………………………………………
我们打开 ns-2.29/mac/mac-802_16 下的 mac-802_16.cc 文件,翻看最后一段代码:
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;
一个声明为 static 的类,在 NS2 初 始化的时候会调用该类的构造函数,在此 NS2 调用了Mac802_16Class : Mac802_16Class() ,这首先调用了 TclClass("Mac/802_16") 。我们接着翻看tclcl-1.17/Tcl.cc 看 TclClass() 是如何工作的。
在 Tcl.cc 文件中 :
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_" ""
}
}
我们继续翻看 TclClass 的 create_shadow() 函数,看它做了些什么:
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);
}
}