1. 问题的由来
在使用BlueHoc的过程中,发现它直接修改了ns2的一些核心文件,那么是否有一种方式可以不必修改NS2核心文件而达到同样的目的呢?
2. 思路
Ns2使用TCL/CL来将OTCL代码与C++代码结合起来,所有的仿真过程都是通过OTCL代码调用的。那么是否可以重载OTCL中相同名称的类,在它们创建时将其指向自己的C++类呢?
3. 测试代码
为了测试使用同名的OTCL类是否可以重载的问题,写了以下的测试代码:
#include <math.h>
#include "tclcl.h"
extern "C" {
#include <otcl.h>
}
// 定义一个准备让TCL脚本调用的类,此类必须从TclObject继承
class CTestObject : public TclObject
{
private:
int m_nCount;
public:
CTestObject()
{
m_nCount = 0;
// bind之后就可以在TCL脚本中访问这个类的成员变量了,
// 对于没有使用bind的变量是无法通过脚本访问的。
// 在此只展示一个简单的整数类型,如果是复杂数据则需要从TracedVar继承一个子类进行控制
this->bind("Count", &m_nCount);
}
virtual int init(int argc, const char*const* argv)
{
// 在脚本中进行实例化时将调用此函数
printf("CTestObject::init called/n");
m_nCount = 100;
return (TCL_OK);
}
virtual int command(int argc, const char*const* argv)
{
// 当脚本中进行对象的函数调用时将使用此函数
printf("CTestObject::command called/n");
if(argc >= 2 && strcmp(argv[1], "GetCount") == 0)
{
char result[20];
sprintf(result, "%d", m_nCount);
Tcl::instance().result(result);
return TCL_OK;
}
else
return TclObject::command(argc, argv);
}
};
class CSubTestObject : public CTestObject
{
private:
int m_nSubCount;
public:
CSubTestObject()
{
m_nSubCount = 0;
this->bind("SubCount", &m_nSubCount);
}
virtual int init(int argc, const char*const* argv)
{
// 在脚本中进行实例化时将调用此函数
CTestObject::init(argc, argv);
printf("CSubTestObject::init called/n");
m_nSubCount = 200;
return (TCL_OK);
}
virtual int command(int argc, const char*const* argv)
{
// 当脚本中进行对象的函数调用时将使用此函数
printf("CSubTestObject::command called/n");
if(argc >= 2 && strcmp(argv[1], "GetSubCount") == 0)
{
char result[20];
sprintf(result, "%d", m_nSubCount);
Tcl::instance().result(result);
return TCL_OK;
}
else
return CTestObject::command(argc, argv);
}
};
// 定义一个TCL脚本中可以使用的类信息
class CTestClass : public TclClass
{
public:
CTestClass()
: TclClass("CTestClass")
{
}
virtual TclObject* create(int argc, const char* const* argv)
{
// 创建对象时将调用此函数,如果CTestObject的构造函数需要参数也可以从这里取得
printf("CTestClass::create called/n");
return new CTestObject;
}
};
// 不可使用这种变量的方式,否则无法重载
//static CTestClass _cls;
// 定义一个TCL脚本中可以使用的类信息
class CSubTestClass : public TclClass
{
public:
CSubTestClass()
: TclClass("CTestClass") // 使用与CTestClass相同的类名称,看看发生的结果
{
}
virtual TclObject* create(int argc, const char* const* argv)
{
// 创建对象时将调用此函数,如果CTestObject的构造函数需要参数也可以从这里取得
printf("CSubTestClass::create called/n");
return new CSubTestObject;
}
};
// 不可使用这种变量的方式,否则无法重载
//static CSubTestClass _sub_cls;
// 应先初始化Tcl::init之后再注册类型信息,否则无法重载
void ClassInit()
{
new CTestClass;
new CSubTestClass;
}
4. 测试中出现的问题
在此测试过程中,刚开始使用的是静态全局变量进行类的注册,但是发现此种情况下无法重载。跟踪代码时发现,在调用TclClass类构造函数时,如果Tcl::instance()未初始化,则构造函数仅仅是将我们的类放在一个单链表中,而不会立即进行OTCL类和C++类的绑定。注意此链表中后插入的数据是放在表头的,而初始化完成后进行C++类和OTCL类绑定的时候则是从表头向表尾按顺序进行的,因此就造成了这个问题。
根据分析,解决问题的方式也有两种。
第一种仍然使用静态全局变量的方式,但是要更改父类和子类初始化的先后顺序,先定义子类的静态全局变量,再定义父类的静态全局变量。
第二种不使用全局静态变量,而是在Tcl::init()调用完成后再进行类的初始化,如上述代码中的ClassInit(),此时应先初始化父类,再初始化子类。
经过上述调整,即可得到正确的结果。