NS 2ノートのOTClとC++

11358 ワード

最近NS 2のいわゆる分裂オブジェクトモデルを理解してきたが,nsがどのようにOtclによってC++オブジェクトを作成するかを知りたい.次は勉強の心得をまとめます.
Otclは実はオブジェクト向けのtclであり,これはC++とCの関係と同じである.
OtclはTclと同様に埋め込み可能であり、CのAPIインタフェースを提供し、これらのインタフェースを通じてOtclオブジェクトにアクセスすることができる.主なインタフェースの説明は~/otcl/otclを参照する.hファイル
OtclのベースクラスはObjectであり,すべてのクラスはObjectから派生している.このクラスには、オブジェクトの初期化と破棄プロセス、クラスメンバー関数とメンバー変数とクエリーを追加する方法が含まれています.以下にまとめる.
Class
オブジェクトの作成
Destroy
オブジェクトを破棄
proc
Tclオブジェクトメソッドの定義
set
Tclオブジェクト変数の定義
instvar
インスタンス変数のバインド
Otclの詳細な構文が表示されます.
http://bmrc.berkeley.edu/research/cmt/cmtdoc/otcl/
次に、OTClでC++オブジェクトを操作する方法を見てみましょう.
一、C++オブジェクトの作成
NS 2で採用されているスプリットオブジェクトモデルは,C++のクラスごとに1つのOtclクラスが対応している.NS 2のすべてのクラスのベースクラスはTclObjectであり,OtclのベースクラスはSplitObjectである.まずSplitObjectというクラスの定義(~/tcl.*/TCl-object.tcl)を見てみましょう.
Class SplitObject
SplitObject set id 0

SplitObject instproc init args {
	$self next
	if [catch "$self create-shadow"text" > $args"] {
		error "__FAILED_SHADOW_OBJECT_" ""
	}
}
SplitObject instproc destroy {} {
	$self delete-shadow
	$self next
}

各Otclオブジェクトの作成はinitプロシージャを呼び出すため、SplitObjectに継承されているすべてのOtclクラスの初期化は、最終的にSplitObjectのinit()というinstprocに呼び出されます.
上記のコードから、init()は実際にcreate-shadowという関数を呼び出しており、この関数はC++オブジェクトを作成するために使用されており、後で分析されます.
また、SplitObjectはdestroy()関数を再ロードし、delete-shadowというメソッドを呼び出し、その名の通り、対応するC++オブジェクトを破棄します.
注意:OtclはC++のように親クラスのコンストラクション関数を自動的に呼び出すのではなく、呼び出すために表示する必要があります.つまり、$self nextです.
OtclスクリプトではnewコマンドでOtclオブジェクトを作成し、deleteでオブジェクトを破棄します.この2つのコマンドは~/tclcl.*/Tcl-object.tclには次の定義があります.
#    object
#     create  
proc new { className args } {
	set o [SplitObject getid]
	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
}
#    object
proc delete o {
	$o delete_tkvar
	$o destroy
}

newメソッドは実際にクラスのcreate関数を呼び出して初期化していることがわかりますが、このcreate関数はいったいどういうことなのでしょうか.振り返ってOtclの文法をよく見てみましょう.
"The create instproc provides a mechanism for classes to create other classes and objects"
もともとcreateはOtcl解釈オブジェクトを作成するために使用され、オブジェクト作成時にinit()メソッドが呼び出され、最終的にcreate-shadowが呼び出され、それに対応するC++shadowオブジェクトが作成されます.
では、問題はまた来て、create-shadowはどのようにC++オブジェクトを作成しますか?また、どのC++クラスに対応するオブジェクトを作成するかをどのように知っていますか?次にOtclクラスとC++クラスがどのように関連しているかを見てみましょう.OtclベースクラスSplitObjectではregisterというメソッドが用意されています.
	
// This routine invoked by TclClass::bind.
//    
SplitObject proc register className {
	set classes [split $className /]
	set parent SplitObject
	set path ""
	set sep ""
	foreach cl $classes {
		set path $path$sep$cl
		if ![$self is-class $path] {
			Class $path -superclass $parent
		}
		set sep /
		set parent $path
	}
}

上記のコードから分かるように、registerの機能は、classnameを指定するOtclクラスを定義することです.
ここではNS 2のクラスネーミングルールも簡単に紹介しなければなりません.NS 2は、クラス間の継承関係を表すために、分割子として文字'/'を用いる.具体的には、例えば「Agent/TCP/Reno」というOtclクラスは、「Agent/TCP」クラスに継承される.同じように「Agent/TCP」は「Agent」クラスに継承され、Agentクラスは最後にベースクラスSplitObjectに継承されます.クラスのネーミングルールが分かれば、上のコードはよく理解できます.つまり、提供されたclassnameに基づいて区切り記号を使用して分割した後、対応する派生クラスを作成します.ここで-superclassはクラス間の継承関係を示します.
では、誰がこのregisterメソッドを呼び出してOtclクラスの登録を行ったのでしょうか.そんなときにTclClassを紹介しなければいけないので、やっとOtclスクリプトからおなじみのC++に転向できるようになりました~~.TclClassは純虚クラスであり、Otclクラスの登録メカニズムをカプセル化している.くだらないことは言わないで、まずこのクラスの定義を見てみましょう(~/tclcl.*/tcll.h)
class TclClass {
public:
	static void init();
	virtual ~TclClass();
protected:
	TclClass(const char* classname);
	virtual TclObject* create(int argc, const char*const*argv) = 0;
private:
	static int create_shadow(ClientData clientData, Tcl_Interp *interp,
				 int argc, CONST84 char *argv[]);
	static int delete_shadow(ClientData clientData, Tcl_Interp *interp,
				 int argc, CONST84 char *argv[]);
	static int dispatch_cmd(ClientData clientData, Tcl_Interp *interp,
				int argc, CONST84 char *argv[]);
	static int dispatch_init(ClientData clientData, Tcl_Interp *interp,
				 int argc, char *argv[]);
	static int dispatch_instvar(ClientData clientData, Tcl_Interp *interp,
				 int argc, CONST84 char *argv[]);
	static TclClass* all_;
	TclClass* next_;
protected:
	virtual void otcl_mappings() { }
	virtual void bind();
	virtual int method(int argc, const char*const* argv);
	void add_method(const char* name);
	static int dispatch_method(ClientData, Tcl_Interp*, int ac, CONST84 char** av);

	OTclClass* class_;
	const char* classname_;
};

まず、その構造関数TclClass::TclClass(const char*classname);
TclClass::TclClass(const char* classname) : class_(0), classname_(classname)
{
	if (Tcl::instance().interp()!=NULL) {
		// the interpreter already exists!
		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;
	}
}

ええ、とても簡単そうに見えます...Otcl解釈器の存在を判断した後にbindというメンバー関数を呼び出し,このbind関数の具体的な実装を見てみよう
void TclClass::bind()
{
	Tcl& tcl = Tcl::instance();
	
	//Register classname in OTCL
	tcl.evalf("SplitObject register %s", classname_);

	class_ = OTclGetClass(tcl.interp(), (char*)classname_);

	//Add 2 method for this class
	//create-shadow & delete-shadow
	OTclAddIMethod(class_, "create-shadow",
		       create_shadow, (ClientData)this, 0);
	OTclAddIMethod(class_, "delete-shadow",
		       delete_shadow, (ClientData)this, 0);
	otcl_mappings();
}

bind関数がSplitObjectを呼び出すregisterメソッドがclassname_を登録したのですOtclクラス、次にクラスに2つのメソッドを追加しました:create-shadowとdelete-shadow.これは、Otclオブジェクトの作成と破棄がTclClassの2つのメンバー関数:create_shadowとdelete_shadowがバインドされた.ではcreateを見てみましょうshadowの部分実装(~/tclcl.*/)
int TclClass::create_shadow(ClientData clientData, Tcl_Interp *interp,
			    int argc, CONST84 char *argv[])
{
	TclClass* p = (TclClass*)clientData;
	TclObject* o = p->create(argc, argv);
	/*      …*/
}

冒頭のコードから分かるように、純粋な虚関数virtual TclObject*create(int argc,const char*const*argv)=0を呼び出した.では、このcreate関数はいったい何をしているのでしょうか.次に、TclClassの派生クラスAgentClassの一例として説明するが、その定義は以下の通りである(~/ns 2.*/common/Agent.cc)
static class AgentClass : public TclClass {
public:
	AgentClass() : TclClass("Agent") {} 
	TclObject* create(int, const char*const*) {
		return (new Agent(PT_NTYPE));
	}
} class_agent;

まずキーワードstaticに気づき,AgentClassが静的クラスであること,すなわちNS 2が初期化を開始するとそのクラスのコンストラクション関数が呼び出され,コンストラクション関数はどのような役割を果たすのかを説明した.上で分析しましたが、実はOtclにAgentというOtcl解釈クラスが登録されています.Otclスクリプトがnewメソッドを呼び出してこのAgentクラスをインスタンス化すると、最終的にAgentClassのcreate関数が呼び出されます.このcreate関数の実装は簡単です.C++のAgentオブジェクトをインスタンス化し、オブジェクトのポインタを返します.これでOtclオブジェクトはついにC++オブジェクトに関連付けられた.