1.Tcl類
這個類封裝是OTcl解釋器的真正執行個體,可以當成解釋器了解。其中定義了解釋器通路及通信的方法。這個類是在~tclcl/tclcl.h和~tclcl/tcl.cc中定義的,提供了以下的操作方法:
1.1 獲得 Tcl 執行個體的一個指針
在類定義有 static Tclinstance_;擷取的方法是通過一個靜态的内聯函數static inline Tcl& instance() {return (instance_); }實作的。
1.2 通過解釋器調用 OTcl 過程
通過解釋器執行個體tcl來調用OTcl指令,他們在調用參數方面有本質的差別。每個函數都傳遞一個字串(string)給解
釋器,然後解釋器通過一個全局文本來識别這個字元串。如果解釋器傳回TCL_OK,則這些函數将會返一個相應的OTcl
過程。反過來,如果解釋器傳回TCL_ERROR,則這些函數将調用tkerror{}。使用者可以重載(overload)個過程,以便有選
擇地忽略某些類型的錯誤。
1.3 取出或将結果傳回解釋器
//tcl的解釋器
Tcl_Interp*tcl_; Tcl_Interp是在~include/tcl.h中的定義的,
typedefstruct Tcl_Interp {
char*result;
void(*freeProc) _ANSI_ARGS_((char *blockPtr));
interrorLine;
}Tcl_Interp;
結果就儲存在tcl_->result中。
取出結果:tcl.result(void)必須用于取回結果。注意這裡的結果是一個字元串,它必須被轉化成一個适合結果類型的内部格式。
結果傳給解釋器,即設定tcl_->result的值,a)tcl.result(const char*s),b)tcl.resultf(const char* fmt, . . . )
1.4 報告錯誤狀态并以統一的方法退出
編譯代碼中提供了一種統一的報告錯誤的方法。
tcl.error(const char* s)執行以下功能:将s寫入stdout;将tcl_->result寫入stdout;退出,并将錯誤代碼(error code)置1。
tcl.resultf("cmd = %s", cmd);
tcl.error("invalid command specified");
1.5 存儲并查找“TclObjects“
ns将每個TclObject在編譯層次的一個指針(reference)存在一個hash表中;這樣就能快速地通路該對象了。該hash
表在解釋器的内部。Ns使用者以TclObject的名字為關鍵字(key)在hash表中進行插入、查找或者删除TclObject的操作。
tcl.enter(TclObject* o)将在hash表插入一個指向TclObjecto的指針(pointer)。它被TclClass::create_shadow()
用來在對象建立時,将其插入表中。
tcl.lookup(char*s)将取回名為s的TclObject。可以這樣被使用:TclObject::lookup()。
tcl.remove(TclObject* o)将删除hash表中TclObjecto的指針。可以用TclClass::delete_shadow()來移出hash表中
存在的入口,此時該對象已經被删除。
2.TclObject類
TclObject類是解釋和編譯層次大多數其它類的基類(baseclass)。TclObject類中的每個對象都由使用者從解釋器中創
建。編譯層次中同時有一個與之對應的影子對象(shadow object)被建立。這兩個對象互相緊密聯系。下一小節描述的
TclClass類,包含了執行這種投射(shadowing)的機制。
TclObject類包含了早期的NsObject類的函數。是以,它儲存了變量綁定(bindings)接口,這些變量綁定綁定了解釋對象中的執行個體變量(instance variables)和相應的編譯對象中的C++成員變量(membervariables)。這種綁定比ns版本1要強,因為OTcl變量的任何變化都是被跟蹤(trapped)的,而且每次目前的C++和OTcl的值被解釋器通路後都要保持一緻。這種一緻性是由InstVar類來完成的。同樣,和ns版本1不同的是,tclObject類的對象不再存儲在一個全局連結清單(link list)中。而是存儲在Tcl類的一個hash表中。
2.1 建立(creating)和撤銷(Destroying)TclObjects
當使用者建立一個新的TclObject時,通常調用new{}和delete{}過程(procedures),這些過程定義在~tclcl/tcl-object.tcl中【未找到】。它們可以用于建立和撤銷所有類的對象。
建立TclObjects:使用者可以調用new{}來建立一個解釋類的TclObject。這時解釋器将執行這個對象的構造函數
(constructor)init{},同時給它傳遞使用者提供的任何參數。ns自動建立相應的編譯對象。shadow對象是通過基類TclObject
的構造函數被建立的。是以,新的TclObject的構造函數必須首先調用父類的構造函數。new{}方法傳回一個對象的handle,
使用者可以通過這個handle對對象進行進一步的操作。
如下面是Agent/SRM/Adaptive的構造函數:
Agent/SRM/Adaptive instproc init args {
eval $selfnext $args
$self arrayset closest_ "requestor 0 repairor 0"
$self seteps_ [$class set eps_]
}
建立一個gent/SRM/Adaptive對象時執行的步驟:
2.1.1 從TclObject的名字空間(namespace)擷取一個惟一的新的對象的handle。這個handle将傳回給使用者。ns中大多數handle都是以_o的形式出現的,這裡的是一個整數。這個handle由getid{}建立。它可以從C++中的name(){}的方法獲得。
在TclObject類中定義:
#if0
#defineTCLCL_NAME_LEN 12
charname_[TCLCL_NAME_LEN];
#else
char*name_;
#endif
inline constchar* name() { return (name_); }
voidname(const char*);
2.1.2執行新對象的構造函數。任何的特定的使用者輸入參數都将作為構造函數的參數傳入。這個構造函數必調用其父類的構造函數。
在上面的例子裡,Agent/SRM/Adaptive在第一行就調用了其父類。
需要注意的是,每個構造函數,依次調用其父類的構造函數。 那麼ns中的最後一個構造函數就是TclObject的構造函數。
這個構造函數用來建立對應的shadow對象,并執行其它的初始化工作與綁定。是以最好在構造函數先調用父類的構造函數,然後再初始化。這樣可以使shadow對象先被建立,進而有變量可以綁定。
2.1.3TclObject的構造函數為Agent/SRM/Adaptive類調用create-shadow{}執行個體過程。
2.1.4當shadow對象建立以後,ns為編譯對象調用所有的構造函數,它們每個都有可能為類中的對象建立相應的變量綁定,同時執行其它的必要的初始化工作。是以我們最好把調用父類的構造函數的語句放在類初始化語句之前。
2.1.5在shadow對象成功建立後。這樣就通過create_shadow(void)将建立的TclObject對象添加到tcl類的hash表中,如上文。使cmd{}成為一個新建立的解釋對象的執行個體化過程。這個執行個體化過程會調用編譯對象中的command()方法。在接下來的一節裡,我們将介紹command方法是如何定義并被調用的。
2.1.6 注意映射機制(shadowingmechanisms)隻有當使用者通過解釋器建立新的TclObject的時候才有效。但如果程式員隻是單向建立編譯TclObject,這個機制将會失效。是以,程式員不要直接用C++的新方法來建立編譯對象。
TclObject的撤銷delete操作将同時撤銷解釋對象和相當的shadow對象,
delete過程來移除預設的連結清單計劃(list scheduler),同時在原處執行個體化一個替代的計劃。
Simulatorinstproc use-scheduler type {
$selfinstvar scheduler_
deletescheduler_# 首先删除已有的list scheduler
setscheduler_ [new Scheduler/$type]
}
同構造函數一樣,對象的析構函數必須明确地調用其父類的析構函數,作為該析構函數的聲明的最後部分。TclObject的析構數将調用delete-shadow執行個體過程,進而依次調用對應的編譯方法來撤銷shadow對象。解釋對象将由解釋器自身撤銷。
2.2 變量綁定(Variable Bindings)
大多數情況下,通路編譯成員變量隻能通過編譯代碼,同樣地,通路解釋成員變量隻能通過解釋代碼;但是,在它們之建立一個雙向綁定是有可能的,這就使得解釋成員變量和編譯成員變量都可以通路同樣的資料,而且它們中的任何一個變值的變化均可以使另一個相應地變為同樣的值。
這種綁定是對象在執行個體化的時候,由編譯對象的構造函數建立起來的;它也作為一個執行個體變量被解釋對象自動通路。ns持五種不同的資料類型:實型(reals) 帶寬變量 , (bandwidth valued variables) 時間變量,(time valuedvariables),整型(integers),布爾型(booleans)。
如下例:ASRMAgent類(這是個編譯類)的構造函數:
SRMAgent::ASRMAgent() {
bind("pdistance_", &pdistance_);
bind("requestor_", &requestor_);
bind_time("lastSent_", &lastSessSent_);
bind_bw("ctrlLimit_", &ctrlBWLimit_);
bind_bool("running_", &running_);
}
上述所有的函數都需要兩個參數:OTcl變量的名字和綁定的相應的編譯成員變量的位址。通常來說,這些綁定是在由構造函數建立的,但是也可以由其它方式完成。如可以通過InstVar類來完成。
每個被綁定的變量在對象建立的初始化時候,自動地被賦予預設值。這些預設值被指定為解釋類變量。這個初始化過程是在init-instvar{}中被執行的,而這又是被Instvar類中的方法調用的。init-instvar{}檢查(check)解釋對象的類和該對象所有的父類,進而找到定義變量的第一個類。它利用那個類中的變量值來初始化這個對象。大部分綁定的初始化值定義在~ns/tcl/lib/ns-default.tcl中。
需要注意的是,實際的綁定過程是在InstVar類的對象執行個體化過程中完成的。InstVar類中的每個對象綁定一個編譯成員變量和一個解釋成員變量。一個TclObject存儲一個InstVar對象及相應的成員變量的連結清單。這個連結清單頭被存儲在TclObject的成員變量instvar_裡。
2.3 變量跟蹤(Variable Tracing)
除變量綁定以外,TclObject同時也支援對C++和Tcl執行個體變量的跟蹤。一個被跟蹤的變量既可以在C++又可以在Tcl中建和配置。如果在Tcl層建立變量跟蹤,變量必須在Tcl下是可視的,這也就意味着它必須是一個綁定的C++/Tcl,或者是一個純Tcl執行個體變量。
如果一個TclObject去跟蹤變量,它必須擴充C++中的trace方法,那本來是定義在TclObject中的一個虛函數。Trace類隻實作一個簡單的trace方法,是以,它可以作為一個一般的變量跟蹤函數。
下面是一個在Tcl中設定跟蹤變量的簡單的例子:
#$tcp跟蹤它自己的變量cwnd_
$tcp tracecwnd_
#$tcp中的變量ssthresh_ 被一個一般的$tracer跟蹤
set tracer[new Trace/Var]
$tcp tracessthresh_ $tracer
2.4 command方法: 定義與調用(Invocation)
參考http://csutornado.blog.sohu.com/43543936.html
對于每個建立的TclObject,ns都将建立cmd()執行個體過程,作為一個挂鈎(hook)使編譯的影子(shadow)對象可以執行一些方法。cmd()過程自動調用影子對象的command()方法,并将cmd()的參數以向量的形式傳遞給command()方法。使用者可以通過下面兩種方法調用cmd():顯示地調用過程,将所要進行的操作作為第一個參數;隐示地調用,就好像有一個同名的執行個體過程作為所需的操作。大多數模拟腳本都采用後者,是以,我們将先介紹後者。
我們都知道SRM 中的距離計算是在編譯對象中執行的;但是,它卻通常被解釋對象使用。它通常以如下方式被調用:$srmObjectdistance? (agentAddress)
如果沒有執行個體過程叫做distance?,那麼解釋器将調用執行個體過程unknown(),這個過程在基類TclObject中定義。然後unknown 過程将調用
$srmObject cmd distance?
通過編譯對象的command()過程來執行操作。當然,使用者可以顯示的直接調用操作。其中一個原因可能是用一個同名的執行個體過程來重載這個操作。例如,
Agent/SRM/Adaptive instproc distance? addr {
$self instvar distanceCache_
if ![info exists distanceCache_($addr)] {
set distanceCache_($addr) [$self cmd distance? $addr]
}
set distanceCache_($addr)
}
我們現在就來說明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;
}
}
return (SRMAgent::command(argc,argv));
}
我們可以從上述代碼中得到一下幾點提示:
函數調用時需要兩個參數:第一個參數(argc)代表解釋器中該行指令說明的參數個數。指令行向量(argv)包括:
——argv[0]為方法的名字,"cmd"。
——argv[1]為所要求的操作。
——如果使用者還有其他特殊的參數,他們就被放在argv[2...(argc-1)]。
參數是以字元串的形式傳遞的;他們必須被轉換成适合的資料形式。如果操作成功比對,将通過前面(3.3.3)的方法傳回操作的結果。command()必須以TCL_OK或TCL_ERROR 作為函數的傳回代碼,來表明成功或者失敗。
如果操作在這個方法中沒有找到比對的,它将調用其父類的command方法,同時也就傳回相應的結果。這就允許使用者建立和對象過程或編譯方法一樣層次特性的操作。當command方法是為多繼承的類定義時,程式員可以自由的選擇其中一個實作;
1)可以調用其中一個的父command 方法,然後傳回其相應的結構,或
2)可以以某種順序依次調用每一個的父command 方法,然後傳回第一個調用成功的結果。如果沒有調用成功的,将傳回錯誤。
在我們這個檔案裡,我們把通過command()執行的操作叫做準成員函數(instproc-likes)。這個名字反映了這些操作作為一個對象的OTcl執行個體過程的用途,但是也存在一些微小的實際和用途上的差距。
3 TclClass類
這個編譯類(classTclClass)是一個純虛類。從這個基類派生的類提供兩種功能:建構與編譯類層次鏡像的解釋類層次;提供執行個體化新TclObjects的方法。每一個這樣的派生類與編譯類層次中的特定的編譯類互相關聯,同時可以執行個體化這個關聯類的新對象。
static class RenoTcpClass:public TclClass {
public:
RenoTcpClass() : TclClass("Agent/TCP/Reno") {}
TclObject*create(int argc, const char*const* argv) {
return (newRenoTcpAgent());
}
}class_reno;
當ns初次啟動時,執行對象的構造函數。
這個構造函數以解釋類名為其參數,調用TclClass的構造函數。
TclClass的構造函數存儲這個類的名字,并把這個對象插入到TclClass對象的連結清單中。
在模拟器的初始化過程中,Tcl_AppInit(void)調用TclClass::bind(void)。
對TclClass對象連結清單中的每一個對象,bind()調用register{},以解釋器名作為其參數。
register{}建立層次類,建立那些必需,卻還建立的類。
最後,bind()為新類定義create-shadow和delete-shadow兩個執行個體過程。
4 TclCommand類
該類(TclCommand類)僅僅為ns提供向解釋器輸出簡單指令的機制,然後由解釋器在全局範圍内執行。
5 EmbeddedTcl類
ns允許對編譯代碼或者解釋代碼的功能擴充,這個擴充代碼将在初始化的時候被執行。
6 InstVar類
該類定義了将編譯的shadow對象中的C++成員變量綁定到與之對應的解釋對象中的Tcl執行個體變量上的方法與機制。這種綁定的建立可以使變量的值在任何時候都能從解釋器或編譯代碼中設定和通路。