經(jīng)過一段時(shí)間的代碼閱讀和資料查閱,在這里我想試著講明一個(gè)困擾大多數(shù)NS2 Beginner的問題:Otcl和C++的交互,我們寫的新協(xié)議(假若有的話)是如何被NS2執(zhí)行的。
就簡(jiǎn)單的從我們現(xiàn)有的來自長(zhǎng)庚大學(xué)的802.16的補(bǔ)丁說起。移植完16的補(bǔ)丁我們的NS2就可以執(zhí)行MAC層協(xié)議為“Mac/802_16“的tcl腳本代碼。但是這個(gè)補(bǔ)?。╳imax_v2.03)里面的代碼全都是用C++編寫的底層代碼,在tcl腳本中設(shè)置MAC層協(xié)議為Mac/802_16,tcl解釋器是如何正確的知道是執(zhí)行我們的補(bǔ)丁呢?
這是tcl腳本中設(shè)置16協(xié)議的地方:
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:
//構(gòu)造函數(shù)Mac802_16Class()將Otcl中的類名Mac/802_16作為參數(shù)傳給其父類TclClass的構(gòu)造函數(shù);
//要注意,這里實(shí)際上是創(chuàng)建了兩個(gè)類:Mac和802_16,并且802_16是Mac的子類;
Mac802_16Class() : TclClass("Mac/802_16") {}
//而create方法則創(chuàng)建一個(gè)要與Otcl類對(duì)應(yīng)的C++類的對(duì)象實(shí)例,然后返回;
TclObject* create(int, const char*const*)
{
return (new Mac802_16());
}
} class_mac802_16;
一個(gè)聲明為static的類,在NS2初始化的時(shí)候會(huì)調(diào)用該類的構(gòu)造函數(shù),在此NS2調(diào)用了Mac802_16Class:Mac802_16Class(),這首先調(diào)用了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語言解釋器已存在的話,調(diào)用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環(huán)境中注冊(cè)該類名:Mac802_16,其父類是SpliteObject
//需要注意的是:SpliteObject存在于otcl環(huán)境中,與C++中的TclObject相對(duì)應(yīng)
tcl.evalf("SplitObject register %s", classname_);
//注冊(cè)了之后,為這個(gè)類添加兩個(gè)命令:create-shadow和delete-shadow,注意:這兩個(gè)命令的執(zhí)行程序?qū)嶋H上就是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();
}
然后當(dāng)我們?cè)趎s腳本中:new Mac802_16時(shí),在tclcl-1.17/tcl-object.tcl中:
proc new { className args } {
set o [SplitObject getid]
//調(diào)用了該類的create函數(shù),即Mac802_16:create()函數(shù),也就是調(diào)用了其父類SpliteObject:create()函數(shù)
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
}
但是問題出現(xiàn)了:實(shí)際上SpliteObject并沒有實(shí)現(xiàn)create()函數(shù)!如何解決呢?我們往上找找看SpliteObject類是如何聲明的:Class SpliteObject,原來這實(shí)際上是調(diào)用了Class的Create函數(shù):
Class instproc create() {
...
alloc();
init();
...
}
這就會(huì)調(diào)用SpliteObject instproc init()函數(shù)
SplitObject instproc init args {
$self next
//調(diào)用類的create-shadow函數(shù),在這個(gè)例子中,就是調(diào)用了Mac802_16 instproc create_shadow函數(shù)
//如前面所講,也就是調(diào)用了TclClass::create-shadow()函數(shù)
if [catch "$self create-shadow $args"] {
error "__FAILED_SHADOW_OBJECT_" ""
}
}
我們繼續(xù)翻看TclClass的create_shadow()函數(shù),看它做了些什么:
int TclClass::create_shadow(ClientData clientData, Tcl_Interp *interp, int argc, char *argv[])
{
TclClass* p = (TclClass*)clientData;
//在這里調(diào)用了Mac802_16Class::create()函數(shù),也就是調(diào)用了C++環(huán)境中的:new Mac802_16,到這里為止,otcl中的Mac802_16類對(duì)應(yīng)的shadow object(影象對(duì)象)就生成了
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添加兩個(gè)命令:cmd和instvar,其中cmd命令是meet the Tcl Unknown mechanism——Tcl的unknown機(jī)制,這樣一來,當(dāng)你在ns腳本中輸入了一個(gè)該類未知的命令,Tcl的unknown機(jī)制就會(huì)調(diào)用該類的cmd命令,具體的過程可以翻看NS手冊(cè)的相應(yīng)部分,有比較詳細(xì)的說明;
//而cmd()命令激活影像對(duì)象的command()方法,并將cmd()的參數(shù)以向量的形式傳遞給command()方法,因此在實(shí)現(xiàn)某類的C++部分時(shí),你必須實(shí)現(xiàn)該類的Command()過程,仔細(xì)看看NS2中的大部分類,是不是都有一個(gè)Command()函數(shù)?其實(shí)就是這么來的
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()這個(gè)函數(shù)實(shí)現(xiàn)所有的命令分發(fā),下面摘抄手冊(cè)的一部分來說明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));
}
函數(shù)調(diào)用時(shí)需要兩個(gè)參數(shù):argc和argv,第一個(gè)參數(shù)(argc)代表解釋器中該行命令說明的參數(shù)個(gè)數(shù)。
命令行向量(argv)包括:
——argv[0]為方法的名字,"cmd"。
——argv[1]為所要求的操作。
——如果用戶還有其他特殊的參數(shù), 他們就被放在argv[2...(argc-1)]。
參數(shù)是以字符串的形式傳遞的;他們必須被轉(zhuǎn)換成適合的數(shù)據(jù)形式。
如果操作成功匹配,將通過前面(3.3.3)的方法返回操作的結(jié)果。
command()必須以TCL_OK或TCL_ERROR作為函數(shù)的返回代碼,來
表明成功或者失敗。
如果操作在這個(gè)方法中沒有找到匹配的,它將調(diào)用其父類的command
方法,同時(shí)也就返回相應(yīng)的結(jié)果。
這就允許用戶創(chuàng)建和對(duì)象過程或編譯方法一樣層次特性的操作。
當(dāng)command方法是為多繼承的類定義時(shí),程序員可以自由的選擇其中
一個(gè)實(shí)現(xiàn);
1)可以調(diào)用其中一個(gè)的父command方法,然后返回其相應(yīng)的結(jié)構(gòu),或
2)可以以某種順序依次調(diào)用每一個(gè)的父command方法,然后返回第一個(gè)
調(diào)用成功的結(jié)果。如果沒有調(diào)用成功的,將返回錯(cuò)誤。
在我們這個(gè)文件里,我們把通過command()執(zhí)行的操作叫做準(zhǔn)成員函數(shù),這個(gè)名字反映了這些操作作為一個(gè)對(duì)象的OTcl實(shí)例過程的用途。
-------------------------------------------------------------------------------------------------------------------
我想講到這里大概可以明白開篇所說的:“我們寫的新協(xié)議(假若有的話)是如何被NS2執(zhí)行的”了,但是,假若你不想弄的太明白,那么你只需要了解以下實(shí)事:
如果我們要往NS2中添加自己的模塊,那么我們至少要實(shí)現(xiàn)兩個(gè)類:
一,首先要有一個(gè)類繼承自TclObject類或者其子類,例如這個(gè)Mac802_16類的繼承關(guān)系為:TclObject/NsObject/Mac/Mac802_16.這個(gè)類里面實(shí)現(xiàn)了C++類里面的變量與Otcl類的變量的綁定關(guān)系,以及我們的模塊要實(shí)現(xiàn)的一系列算法等等,這個(gè)類負(fù)責(zé)的就是協(xié)議的實(shí)現(xiàn)。
這個(gè)類,一般需要有構(gòu)造函數(shù)中執(zhí)行變量的綁定,使用bind()函數(shù),將Otcl變量與C++的成員變量綁定起來。
聲明為protected的command()函數(shù):為Otcl類提供方法,對(duì)Otcl中的類的方法進(jìn)行翻譯并執(zhí)行;對(duì)于沒有考慮到的或者不能解析的命令,調(diào)用該C++類的父類的command方法。當(dāng)在Otcl類中調(diào)用某個(gè)方法時(shí),首先去tcl類中查找并執(zhí)行該方法;若查找失敗,則在該Otcl類對(duì)應(yīng)的C++類的command方法中查找,若查找仍然失敗,則沿著該類的父類一直往上找,嘗試調(diào)用它們的command方法;若所有父類的command方法都不能解析,則報(bào)告該命令無法執(zhí)行。
其他的成員變量和成員函數(shù),這是用于實(shí)現(xiàn)自己的算法模塊的內(nèi)容。
二,其次我們要定義一個(gè)聲明為static的類,繼承自TclClass類,這個(gè)類實(shí)現(xiàn)了C++環(huán)境里面的類與Otcl環(huán)境里面的類的關(guān)聯(lián),簡(jiǎn)單點(diǎn)來說,這個(gè)類負(fù)責(zé)與Otcl環(huán)境進(jìn)行關(guān)聯(lián)。取最開頭的那段代碼;
static class Mac802_16Class : public TclClass
{
public:
c802_16Class() : TclClass("Mac/802_16") {}
TclObject* create(int, const char*const*)
{
return (new Mac802_16());
}
} class_mac802_16;
這一段代碼里面,包含了一個(gè)將Otcl的類名作為參數(shù)傳給其父類的構(gòu)造函數(shù);一個(gè)create方法:創(chuàng)建一個(gè)C++類的對(duì)象實(shí)例并返回;該方法的返回類定定義為TclObject*。C++類的類型包含在create方法中,Otcl類的類型包含在TclClass類的構(gòu)造函數(shù)中,因此可以實(shí)現(xiàn)C++類和Otcl類的連接。
接下來,如果我們要實(shí)現(xiàn)的類完成以后,將頭文件和源文件放置于~ns目錄下自己新建的一個(gè)子目錄,然后打開~ns/Makefile文件,將“類名.o”添加到該Makefile的OBJ_CC宏定義中,對(duì)ns進(jìn)行編譯的時(shí)候就能夠能夠找到該模塊的源文件并將其編譯到ns中;如果類中定義了一些變量,打開~ns/tcl/lib/ns-default.tcl文件,為該類對(duì)應(yīng)的Otcl類設(shè)置一些初始值。最后,對(duì)Makefile執(zhí)行指令:make clean,make,對(duì)整個(gè)ns重新編譯,我們的模塊就可以添加到ns2中了。
本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)
點(diǎn)擊舉報(bào)。