美文网首页NS2入门教程
NS2入门教程——分裂对象模型

NS2入门教程——分裂对象模型

作者: Vophan | 来源:发表于2019-02-14 13:16 被阅读0次

    再看了一些资料后,感觉自己前面对NS2的框架理解,有些许偏颇,所以现在特地探讨一下NS2的分裂对象模型,来彻底搞清楚NS2的体系结构

    什么是分裂对象模型

    NS2中的网络构件一般是由相互关联的两个类来实现的,分别是C++类和OTcl类,这种方式称为分裂对象模型。构件的主要功能是在C++类中实现的,OTcl类用来向用户提供操作C++对象的接口。在NS2中,C++类和OTcl类通常具有对应关系,两者的继承关系保持一致。当实例化一个构件对象时,NS2会同时创建一个OTcl对象和一个与之对应的C++对象。两者的映射和互操作是由Tclcl(OTcl C++ Linkage)机制实现的

    分裂对象模型如何工作

    首先,我们知道,我们直接上手操作的前端是OTcl,所以实例化构件的时候也是先实例化出OTcl的对象,然后在与C++的对象联系,那么问题来了,怎么与C++的对象联系?

    我们知道:
    所有编译类的基类都是TclObject,
    所有解释类的基类都是SplitObject。

    所以我们要做的就是在生成SplitObject对象的时候,同时生成一个TclObject的对象。

    我们看一个例子:
    现在我们要实例化一个Agent/Udp的构件,这里补充一些内容:

    在Otcl中,斜杠表示继承

    首先,我们要实例化一个SplitObject的子类的对象,我们就要调用到,tclcl/tcl-object.tcl中的new函数:

    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
    }
    

    我们看一下这个new函数,首先他调用getid为新对象得到一个句柄:句柄的一般形式为o_NNN,NNN为整数。

    然后他调用了create函数,并将句柄和参数都传了进去。

    然后,我们来看一下create函数的定义:

    "The create instproc provides a mechanism for classes to create other classes and objects"

    话说,你一定很好奇:代码呢?

    一会你就知道了。

    原来create就是用来创建一个Otcl解释对象的,那么一定绕不开SplitObject的构造方法:

    Class SplitObject
    SplitObject set id 0
    
    SplitObject instproc init args {
     $self next
     if [catch "$self create-shadow $args"] {                      //(1)调用create_shadow()
      error "__FAILED_SHADOW_OBJECT_" ""
     }
    }
    SplitObject instproc destroy {} {
        $self delete-shadow
        $self next
    }
    

    实际上,就是create-shadow这个函数创建了C++的对象。

    注意:Otcl并不会像C++那样会自动调用父类的构造函数,而必须显示的进行调用,也就是上面的: $self next

    所以最后的问题就归结到了,如何用create-shadow创建一个C++的对象呢?而且他是怎么知道应该创建哪个C++的对象呢?

    这里就用到了TclClass

    TclClass是个抽象类,它封装了Otcl类的注册机制

    我们先来看一下TclClass的定义:

    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::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;
        }
    }
    

    很简单,检查Tcl是否被实例化,如果实例化,则调用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里面调用了register函数,大家可能还不知道register是干嘛的?他是SplitObject中的一个成员函数:

    // 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
        }
    }
    

    根据我们前面说的OTcl中的继承关系,我们就知道了:

    实际上这个函数就是创建了一个对象(名字为classname)。

    回到刚才,register后,他又给新类绑定了两个函数,create_shadow和delete_shadow。

    然后,我们看一下create_shadow的定义:

    nt TclClass::create_shadow(ClientData clientData, Tcl_Interp *interp,
                    int argc, CONST84 char *argv[])
    {
        TclClass* p = (TclClass*)clientData;
        TclObject* o = p->create(argc, argv);
        /*以下代码省略…*/
    }
    

    他这里调用了TclClass里面的create,需要注意的是:前面在解释器提到的create和现在TclClass里面的create意义不同。
    而且,TclClass中的create是纯虚函数,也就意味着他的真正实现是在他的子类里面

    我们随便找一个子类作为参考:

    static class AgentClass : public TclClass {
    public:
        AgentClass() : TclClass("Agent") {} 
        TclObject* create(int, const char*const*) {
            return (new Agent(PT_NTYPE));
        }
    } class_agent;
    

    首先我们注意到了关键字static,说明AgentClass是一个静态类,也就是说当NS2刚开始初始化的时候,便会调用该类的构造函数,而构 造函数又做了什么工作呢?上面我们已经分析了,其实就是在Otcl中注册了一个名为Agent的Otcl解释类。当Otcl脚本调用new方法实例化这个 Agent类的时候,最终会调用到AgentClass的create函数,该create函数的实现很简单,就是实例化了一个C++的Agent对象, 并返回该对象的指针。至此Otcl对象终于和C++对象关联起来了。

    img from internet

    总结一下:就是在开始NS2运行时,最开始初始化一个Tcl的对象,实际上就是初始化一个解释器,然后开始初始化各种构建,比如说,上面举到的AgentClass,因为他是一个静态类,所以,在初始化的时候,就会调用到他的构造函数,而他的构造函数就是用来实例化一个TclClass的,然后TclClass的构造函数调用bind,生成了一个OTcl的类。最后,当我们要使用一个构件的时候,进行实例化,然后就调用了new函数,然后调用了create_shadow函数,最终调用到AgentClass的create函数,返回对象指针,整个过程就结束了。

    What doesn't kill u

    相关文章

      网友评论

        本文标题:NS2入门教程——分裂对象模型

        本文链接:https://www.haomeiwen.com/subject/wwceeqtx.html