OTcl Linkage 联接
添加新的基础网络对象去扩展NS,通常需要从C++代码中得到OTcl linkage(联接), 对象的类用C++来编写由于它的高效性。 本节介绍了NS中C++/OTcl联接, 并给出新建一个简单的名叫"MyAgent"代理的示例,"MyAgent"代理没有任何行为(i.e. no packet creation and transmission). Figures 18 到 21 列出了C++ source file for "MyAgent"。 本节最后是一个OTcl脚本用来测试"MyAgent"是否被实现。
假设用C++新建了一个网络对象的类"MyAgent",它是从"Agent"类衍生出来, 然后他能够建立一个OTcl对象的实例。为了实现这个功能我们必须定义一个联接对象"MyAgentClass", 它从"TclClass"衍生得来。 这个联接对象创建了一个有指定命名的OTcl对象(这个例子名为是"Agent/MyAgentOtcl"), 并建立一个联接在OTcl对象和C++对象("MyAgent")之间, 它的实例运行"create"成员函数中指定的程序。Figure 18 表示"MyAgent类"和联接类的定义。
Figure 18. Example C++ Network Component and The Linkage Object
当NS开始运行, 它运行静态变量(static variable) "class_my_agent"的构造函数, 这样一来"MyAgentClass"的实例被建立了。 在这个处理过程中, "Agent/MyAgentOtcl"类和他的方法method(成员函数)也在OTcl的空间内被建立。 无论何时用户在OTcl空间内建立一个对象的实例就会使用命令"new Agent/MyAgentOtcl", 它调用"MyAgentClass::create"去建立"MyAgent"对象并返回它的地址。 这里要当心, 从OTcl中建立一个C++对象的实例并不意味着能够从OTcl调用C++对象实例的成员函数或访问它的成员变量。
假设C++对象"MyAgent"有两个参数变量"my_var1"和"my_var2", 并且我们想轻松的在OTcl中使用模拟脚本来配置(改变)它们 。这需要使用对每个想要输出的C++类的变量的绑定函数(binding function)。 一个绑定函数建立一个给出名字的新成员变量(绑定函数的第一个变量)在对应的OTcl对象类("Agent/MyAgentOtcl")中, 并建立一个双向绑定在OTcl类的变量和C++变量(第二个变量指定的地址)之间。 Figure 19表明如何给Figure 18中"my_var1" 和"my_var2" 建立绑定。
Figure 19. Variable Binding Creation Example
绑定函数位于"MyAgent"的构造函数中,这样在建立对象的实例时就建立了绑定。NS 支持4种不同的绑定函数对应5种变量类型:
- bind(): real or integer variables - bind_time(): time variable - bind_bw(): bandwidth variable - bind_bool(): boolean variable |
以这种方式就可以设计和运行一个模拟并且能使用OTcl脚本去改变或访问C++实现的网络组件的配置参数(或变量的值) 。 无论何时导出一个C++变量, 建议还要去设置这个变量的默认值在"ns-2/tcl/lib/ns-lib.tcl"文件中。 否则在建立新对象的实例时将收到警告信息。
给C++对象("MyAgent")定义一个"command"成员函数作为OTcl命令的解释器。 事实上对于用户来说, 在一个C++对象的"command"成员函数中定义的OTcl命令, 和对应的OTcl对象中的成员函数看起来是一样的。Figure 20给出Figure 18中"MyAgent"对象定义的一个"command"成员函数的例子。
Figure 20. Example OTcl command interpreter
当一个OTcl影像的实例匹配上在OTcl空间中建立的"MyAgent"对象(i.e. set myagent [new Agent/MyAgentOtcl]), 并且用户调用这个对象的一个成员函数(i.e. $myagent call-my-priv-func), OTcl搜索这个已给的成员函数在OTcl对象中。 如果这个成员函数的名字找不到, 则调用"MyAgent::command"传递已调用的OTcl成员函数名和参数以argc(参数个数)/argv(参数值)的格式。 如果有为OTcl成员函数定义的行为(action)在"command"成员函数中, 则运行它并返回结果。 如果没有, 则递归地调用父类、祖类的"command"函数直到找到为止。 如果在父类、祖类中都找不到的话, 则返回一个错误信息(error message)给OTcl对象, 然后OTcl对象提示错误信息(error message)给用户。 这样,OTcl空间中的用户不能控制C++对象的行为。
当用C++实现一个新网络对象时, 可能需要运行OTcl命令在C++对象中。 Figure 21 示例了Figure 18中"MyAgent"的成员函数"MyPrivFunc"的实现, 它让OTcl解释器打印出私有的成员变量"my_var1"和"my_var2"的值。
Figure 21. Execute OTcl command from a C++ Object
从C++中运行OTcl命令, 必须先得到以静态成员函数形式声明的"Tcl::instance()" 的一个参照(reference), 通过它可以传递OTcl命令到解释器(the first line of "MyPrivFunc" does this)。 这个例子演示了2中方式去传递OTcl命令到解释器。 完整的OTcl命令传值函数(OTcl command passing functions)的列表请参考NS文档。
通过运行和测试"MyAgent"例子去更好的理解NS提供的OTcl联接机制。
Figure 22. Test OTcl Script and The Result
MyAgent C++ (ex-linkage.cc)源代码
#include <stdio.h> #include <string.h> #include "agent.h" class MyAgent : public Agent { public: MyAgent(); protected: int command(int argc, const char*const* argv); private: int my_var1; double my_var2; void MyPrivFunc(void); }; static class MyAgentClass : public TclClass { public: MyAgentClass() : TclClass("Agent/MyAgentOtcl") {} TclObject* create(int, const char*const*) { return(new MyAgent()); } } class_my_agent; MyAgent::MyAgent() : Agent(PT_UDP) { bind("my_var1_otcl", &my_var1); bind("my_var2_otcl", &my_var2); } int MyAgent::command(int argc, const char*const* argv) { if(argc == 2) { if(strcmp(argv[1], "call-my-priv-func") == 0) { MyPrivFunc(); return(TCL_OK); } } return(Agent::command(argc, argv)); } void MyAgent::MyPrivFunc(void) { Tcl& tcl = Tcl::instance(); tcl.eval("puts \"Message From MyPrivFunc\""); tcl.evalf("puts \" my_var1 = %d\"", my_var1); tcl.evalf("puts \" my_var2 = %f\"", my_var2); }