NS2中使用了tclcl子產品實作編譯層與解釋層的互動,下面将依據TclClass/TclObject以及SatNode子產品簡要分析互動的過程 。
如果有疏漏的地方希望能夠幫忙指正。
詳細的使用者手冊可以參見文檔《NS Manual》/《ns-chinese-manual》。
Tclcl 子產品: 主要包含了Tcl, TclObject, TclClass, InstVar, TclCommand, EmbeddedTcl等c++類:
- Tcl 類: 是解釋器的執行個體, 并提供了c++代碼通路解釋器的接口。
- TclObject 類 : 所有編譯層類的基類, 我們所編寫的新子產品必須繼承該類或它的子類。
- TclClass 類 : 向解釋器注冊解釋層類, 并将對應的解釋層類與編譯層類進行綁定(TclClass::create方法)。
- InstVar 類 : 編譯層類與解釋層類中成員變量進行綁定。
- TclCommand 類 : 注冊解釋器全局指令。
- EmbededTcl 類 : 動态實作裝載腳本。
下面主要進行TclObject, TclClass 的分析, 子產品SatNode為例, 進行如下分析 :
1. 如何向NS添加一個子產品, 在添加子產品的過程中是怎樣的執行過程 ?
2. 在ns中使用新的類時是怎樣的執行過程, 如何實作 ?
3. 建立解釋對象時, 如何實作變量的綁定 ?
1. 如何向NS添加一個子產品, 在添加子產品的過程中是怎樣的執行過程 ?
首先需要建立兩個類, 與該子產品對應的TclObject類以及TclClass類。
以SatNode為例, 我們需要建立
class SatNode : public Node {}
static class SatNodeClass : public TclClass {
public:
SatNodeClass() : TclClass("Node/SatNode") {}
TclObject* create(int , const char*const* ) {
return (new SatNode);
}
} class_satnode;
其中Node類為TclObject的子類。
SatNodeClass繼承了TclClass類, 該靜态類建立了一個全局執行個體對象, 是以在ns運作過程中會執行個體化對象class_satnode, 進而執行SatNodeClass的初始化函數。
SatNodeClass的初始化函數調用了父類TclClass的初始化函數, TclClass("Node/SatNode"), TclClass的初始化函數如下:
TclClass::TclClass(const char* classname) : class_(0), classname_(classname)
{
if (Tcl::instance().interp()!=NULL) {
bind();
}
...
}
初始化函數将執行TclClass::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.evalf("SplitObject register %s", classname_)語句為調用解釋器api(evalf)執行tcl語句, 功能是向解釋器内注冊新類, 類名為classname也就是"Node/SatNode", 在注冊過程中将進行一定的層次分析, 可以參見SplitObject proc register className{}(在tclcl/tcl-object.tcl内)。
接着向解釋器内該類添加方法, create-shadow/deleteshadow函數, 這将在解釋類的執行個體建立時調用。
同時SatNodeClass覆寫了TclClass的虛函數, 并且建立一個新的編譯執行個體(new SatNode)作為傳回參數, 這也會在解釋層執行個體建立時調用。
至此, 已經向解釋器注冊了新的子產品SatNode。(子產品的具體實作在下面介紹, 新子產品的編譯連結可以參考網上的文檔)
2. 在ns中使用新的類時是怎樣的執行過程, 如何實作 ?
在解釋器中, 我們可以使用 new Node/SatNode建立一個解釋類的執行個體, 參見Simulator instproc newsatnode {} (ns/tcl/lib/ns-lib.tcl)。
這将調用函數 proc new { className args } (/tclcl/tcl-object.tcl)
proc new { className args } {
set o [SplitObject getid]
if [catch "$className create $o $args" msg] {
該函數又會調用 create 函數。
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}
}
在create函數中, 将根據解釋類的層次, 對目前的解釋類進行繼承層次解析,并依次配置設定記憶體。,之後eval [list $obj] init $args調用目前解釋類的init函數(注 : 一般所有的init函數第一條語句都是調用父類的init函數,而SplitObject是所有TclClass建立解釋類的基類, 是以會首先調用SplitObject的init函數)。
SplitObject instproc init args {
$self next
if [catch "$self create-shadow $args"] {
error "__FAILED_SHADOW_OBJECT_" ""
}
}
init函數将調用該解釋類的create-shadow函數, 該函數在SatNodeClass建立執行個體的過程中已經向注冊器中注冊。
int TclClass::create_shadow(ClientData clientData, Tcl_Interp *interp,
int argc, CONST84 char *argv[])
{
TclClass* p = (TclClass*)clientData;
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());
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);
}
}
該函數内首先調用編譯類(TclClass)的create函數, 建立一個對應TclObject的執行個體對象, create函數一般建立一個對應TclObject的執行個體作為傳回參數。(注意構造函數調用過程中将會依次調用父類的構造函數, 同時一般會在構造函數内進行解釋執行個體以及編譯執行個體的變量綁定工作, 在下一節分析)
接下來, 将根據解釋器内建立類執行個體時提供的參數進行TclObject的初始化, o->init(argc - 2, argv + 2), o是create函數建立的解釋類執行個體, 調用init函數進行變量的綁定。注意該函數是一個虛函數, 可以在實作的類中進行自己的定義。
而接下來OTclAddPMethod(OTclGetObject(interp, argv[0]), "cmd", (Tcl_CmdProc *) dispatch_cmd, (ClientData)o, 0)等語句則向解釋器内該解釋類執行個體添加了兩個指令cmd和instvar。( 關于cmd的内容可以前面介紹的參考文檔中)
至此, 一個解釋類執行個體的建立過程已經完成。
3. 建立解釋對象時, 如何實作變量的綁定 ?
在上一節中講到在調用編譯類的構造函數時将會依次調用父類的構造函數, 同時在ns中, 一般在編譯類的構造函數中進行instvar的綁定, 如在SatNode中 :
SatNode::SatNode() : ragent_(0), trace_(0), hm_(0)
{
bind_bool("dist_routing_", &dist_routing_);
}
調用bind_bool函數,進行成員變量“dist_routing_”的綁定, bind_bool實作如下。
#define TOB(FUNCTION, C_TYPE, INSTVAR_TYPE, OTHER_STUFF) \
void TclObject::FUNCTION(const char* var, C_TYPE* val) \
{ \
create_instvar(var); \
OTHER_STUFF; \
init(new INSTVAR_TYPE(var, val), var); \
}
TOB(bind_bool, int, InstVarBool, ;)
通過“#define” 功能實作了bind_bool的定義, 在函數中存在兩個語句create_instvar(var)以及init(new InstVarBool(var, val), var), 其中create_instvar定義如下:
void TclObject::create_instvar(const char* var)
{
/*
* XXX can't use tcl.evalf() because it uses Tcl_GlobalEval
* and we need to run in the context of the method.
*/
char wrk[256];
sprintf(wrk, "$self instvar %s", var);
Tcl_Eval(Tcl::instance().interp(), wrk);
}
調用instvar指令, 進行解釋器内全局變量的聲明(?), 接着init(new InstVarBool(var, val), var)語句進行變量的綁定以及初始化的工作。
void TclObject::init(InstVar* v, const char* var)
{
insert(v);
v->init(var);
}
在TclObject的執行個體中, 所有的InstVar是存放在一個連結清單中的, 在init函數中進行了入表和初始化的操作。
當所有的構造函數完成, 也就實作了解釋類成員變量和編譯類成員變量的綁定。