2014-12-04 19 views
1

好的问题第一: 我需要知道我的远程“对象派遣”(穷人的RPC)MethodCall方法中的难以置信的Evil(TM)开关有什么替代方案。如何实现更多的C++友好/支持OO的联网方法调度机制?

virtual void methodCall(unsigned int method) { 
     bvnet::scoped_lock lock(ctx.getMutex()); 
     bvnet::value_queue &vqueue=ctx.getSendQueue(); 
     switch(method) { 
     case 0: /* GetType */ 
      /* emit object type to output queue as string */ 
      vqueue.push(string(getType())); 
      break; 
     } 

当然有人问我想用这种方法完成什么。

我建立了一个协议(网络),实现了一个轻量级的分布式对象系统,允许一端或另一端在一端或另一端使用对象引用和方法索引进行方法调用,每个对象都是一个整数,网络(所以不能使用指针,ptr-to-method等)。此外,我有一个注册表映射在每个跟踪哪些对象引用是活着的(意味着对象id整数来自另一端的传入方法调用是有效的)。

参数与第四风格的堆栈来处理,所以你不得不进入流的东西,如:

3 5 math.plus() 7 math.multiply() 

实现的(3 + 5)* 7

的vqueue相当于< > .push()和getarg <>()通过vqueue处理将vlaue放到流出流中,并通过getarg读取参数<>()与<>模板化为支持的类型通过线这个动物是基类,其目的是提供注册到特定连接会话的对象(每个端点都有自己的注册表,每当创建新对象以引用远程对象时,都插入该注册表。

/** 
** @brief ABC for remotable objects. 
** 
** Base used for objects exchangeable via object references. 
** 
** Since secure referencing requires a way to 
** track object lifetime a registry reference 
** is required for construction. 
*/ 
class object { 
protected: 
    session &ctx;  /**< @brief for objects to attach to the session's registry */ 
public: 
    /** @brief construction of an object @param sess reference to session to attach */ 
    object(session &sess) : 
     ctx(sess) { 
      LOCK_COUT 
      cout << "object [" << this << "] ctor" << endl; 
      UNLOCK_COUT 
      ctx.register_object(this); 
     } 
    /** @brief base dtor to automatically unregister the object */ 
    virtual ~object() { 
     LOCK_COUT 
     cout << "object [" << this << "] dtor" << endl; 
     UNLOCK_COUT 
     ctx.unregister(this); 
    } 
    /** 
    * @brief Get object's identity. 
    * @return Object identity string 
    * 
    * Overriden by superclass to announce it's identity. 
    */ 
    virtual const char *getType() {return "baseObject";} 
    /** 
    * @brief Method call switchboard. 
    * 
    * Overidden by superclass to implement methods callable 
    * by the remote. Currently the superclasses are using 
    * big switchbanks which looks plain evil but at this 
    * point I'm not sure of what to refactor with. 
    * 
    * @todo 
    * Base class to implement some sort of glue to take out the switch boilerplate? 
    * @todo 
    * some sort of static enum to get rid of the magic number 
    * method call #s from remote POV? 
    * @todo 
    * automatically declare the methods for method calling 
    * via some sort of macro or metacode? 
    * 
    */ 
    virtual void methodCall(unsigned int idx)=0; 
}; 

丑陋在于实现实际接口的派生对象。这是一个在MethodCall(即开关):

class Account : public bvnet::object { 
private: 
    s64 userId; 
public: 
    Account(bvnet::session &sess,s64 who) 
     : bvnet::object(sess),userId(who) {} 
    virtual ~Account() {} 
    virtual const char *getType() {return "userAccount";} 
    virtual void methodCall(unsigned int method) { 
     bvnet::scoped_lock lock(ctx.getMutex()); 
     bvnet::value_queue &vqueue=ctx.getSendQueue(); 
     switch(method) { 
     case 0: /* GetType */ 
      /* emit object type to output queue as string */ 
      vqueue.push(string(getType())); 
      break; 
     } 
    } 
}; 

如此反复,问题是,如果有另一种方式来实现的东西更友好的C++和这样的把戏未来某个对象的编号方法分派决定他想要做

MyFutureAccount : public Account {...} 

(和恐惧的nulcear裂变 - 愤怒的电子邮件,我会从要做到这一点马克塞斯附近的盖革计数器的愤怒维护者得到...)

好像我可能需要在bvnet :: Object的构造中做一些工作并设置一些C++内部(vtable)的形式,可以使用intMethodId-to-ptrToMember的STL映射(给下游提供一种简单的方法来覆盖内容)。然而,bvnet :: Object基类成员指针在下​​游派生类中是否仍按预期工作?还有很多问号。不知道如果我在正确的轨道上或在这个潜在的解决方案上咆哮错误的树...

而不是粘贴更多(我无法预测别人会想看到什么,我可以指出你为嘛GitHub的,因为它是LGPL3开源:https://github.com/gau-veldt/Minetest-Blockiverse/tree/master/blockiverse

最相关的文件将是https://github.com/gau-veldt/Minetest-Blockiverse/blob/master/blockiverse/protocol.hpp而是采取了战利品在ServerRoot的在https://github.com/gau-veldt/Minetest-Blockiverse/blob/master/blockiverse/server.hpp清楚地看到邪恶的开关方法已经成为在野外...

+0

['std :: map'](http://en.cppreference.com/w/cpp/container/map)和['std :: function'](http://en.cppreference.com/瓦特/ CPP /实用程序/功能/功能)。 – 2014-12-04 14:46:59

+0

@CaptainObvlious你能否详细说明一下?我怀疑std :: map是设置vtables的基类wizardry,但std :: function在哪里?我想象一下,典型的用例是一个bvnet :: Object派生对象的对象,它想要写入基类的vtable中,以楔入派生类中的各种方法成员。 std :: function是否处理这种情况?你的意思是一个动物:typedef std :: map overworld_vtable; – 2014-12-04 14:52:43

+0

我不想回答,直到我知道我的攻击计划正在工作,但是它涉及到std :: map,因为我和@CaptainObvlious都提到现在只有我错过了std :: function需要它匹配的签名作为它的模板参数(当你做成员函数调用时给出类名)。作为一个优点,新模式还将旧代码中的某些样板文件转化为单一的点,并允许为未实现的方法引发一个简单的异常。我还添加了标记方法插槽的功能(允许更好的调试信息)。现在回到疯狂的编码......:P – 2014-12-04 16:23:15

回答

0

对这个问题至少有一个赞成使我推断,至少有一个人可能对这个问题的解决方案/回答感兴趣。

我确实设法删除邪恶的开关,并将其替换为我在代码中标记为“调度方法调用”或dmc的内容。它们在从网络进来时被调度以转换为方法调用,因此术语“调度方法调用”

现在,层次结构使用指向成员函数的函数包装在std :: function适配器中,这些函数是a类型的值类型的std ::地图:

typedef std::function<void(value_queue&)> dmcMethod; 
typedef std::map<unsigned int,dmcMethod> call_map; 

有趣的开始在呼叫地图初始化指基类dmc_GetType(DMC仿函数调用可重写的GetType和后的值bvnet ::对象(基类)到传出值队列):

/** 
** @brief ABC for remotable objects. 
** 
** Base used for objects exchangeable via object references. 
** 
** Since secure referencing requires a way to 
** track object lifetime a registry reference 
** is required for construction. 
*/ 
typedef void(bvnet::object::*dmc)(value_queue&); 
class object { 
private: 
    /** 
    * @brief Displateched Method Call 
    * 
    * Implements dispatched method call (dmc) 
    * 
    * Superclass-installed dmc methods in dmcTable are 
    * callable by the remote. A superclass sets up his 
    * dmc methods in his ctor by accessing object's dmcTable 
    * 
    * As a plus the lock and value queue boilerplate has been 
    * been moved to the base class dispatcher and the dmc methods 
    * will be in locked context and given the value queue as a 
    * parameter. He also has an exception now to trap calls to 
    * an unimplemented method index. 
    * 
    * @todo 
    * some sort of static enum to get rid of the magic number 
    * method call #s from remote POV? I'll have the ctors store 
    * method labels in the base class dmcName map while it is 
    * setting up the dmc methods. 
    * 
    * @todo 
    * automatically declare the methods for method calling 
    * via some sort of macro or metacode? 
    * 
    */ 
    friend class session; 
    void methodCall(unsigned int idx) { 
     bvnet::scoped_lock lock(ctx.getMutex()); 
     bvnet::value_queue &vqueue=ctx.getSendQueue(); 
     auto dmcFunc=dmcTable.find(idx); 
     if (dmcFunc!=dmcTable.end()) { 
      (dmcFunc->second)(this,vqueue); 
     } else { 
      // called a method that doesn't exist 
      throw method_notimpl(getType(),idx); 
     } 
    } 
protected: 
    session &ctx;  /**< @brief for objects to attach to the session's registry */ 
    call_map dmcTable; /**< @brief method mapper for method call and OO mechanism */ 
    name_map dmcLabel; /**< @brief name labels of dmc methods */ 

    void dmc_GetType(value_queue&); /**< @brief the GetType dispatched method call (dmc) */ 
private: 
    const string methodLabel(const unsigned int idx) { 
     const auto &s=dmcLabel.find(idx); 
     if (s!=dmcLabel.end()) { 
      return s->second; 
     } 
     return std::to_string(idx); 
    } 
public: 
    /** @brief construction of an object @param sess reference to session to attach */ 
    object(session &sess) : 
     ctx(sess) { 
      LOCK_COUT 
      cout << "object [" << this << "] ctor" << endl; 
      UNLOCK_COUT 
      ctx.register_object(this); 
      // dmtTable[0] is the GetType method 
      dmcTable[0]=&object::dmc_GetType; 
      dmcLabel[0]="GetType"; 
     } 
    /** @brief base dtor to automatically unregister the object */ 
    virtual ~object() { 
     LOCK_COUT 
     cout << "object [" << this << "] dtor" << endl; 
     UNLOCK_COUT 
     ctx.unregister(this); 
    } 
    /** 
    * @brief Get object's identity. 
    * @return Object identity string 
    * 
    * Overriden by superclass to announce it's identity. 
    */ 
    virtual const char *getType() {return "baseObject";} 
}; 

这是一次修改为使用DMC机制的新的ServerRoot对象的例子(单片MethodCall和开关疯狂消失):

class serverRoot : public bvnet::object { 
private: 
    BigInt cli_pub_mod; 
    BigInt cli_pub_exp; 
    Key *clientKey; 
    bool clientValid; 
    string challenge; 
    SQLiteDB db; 
    unsigned int randbits[8]; 
protected: 
    void dmc_LoginClient(value_queue &vqueue); 
    void dmc_AnswerChallenge(value_queue &vqueue); 
    void dmc_GetAccount(value_queue &vqueue); 
public: 
    serverRoot(bvnet::session &sess) 
     : bvnet::object(sess) { 
     dmcLabel[1]="LoginClient"; 
     dmcTable[1]=(dmc)&serverRoot::dmc_LoginClient; 
     dmcLabel[2]="AnswerChallenge"; 
     dmcTable[2]=(dmc)&serverRoot::dmc_AnswerChallenge; 
     dmcLabel[3]="GetAccount"; 
     dmcTable[3]=(dmc)&serverRoot::dmc_GetAccount; 
     clientValid=false; 
     challenge=""; 
     clientKey=NULL; 
    } 
    virtual ~serverRoot() { 
     if (clientKey!=NULL) 
      delete clientKey; 
    } 

    virtual const char *getType() {return "serverRoot";} 

}; 

重要,这里要注意的是,不再有ServerRoot的内MethodCall(或Account或ClientRoot和后来的朋友),也不必对dmcLabel [0]/dmcTabel [0]做任何事情 - 它继承了bvnet :: Object的ctor的设置。同样,一些futureServerRoot子类serverRoot它会继承bvnet :: Object和serverRoot的ctors中的dmc表。因此,我现在在dmc层次结构中有一个适当的继承机制,并且没有来自未来维护者的核弹邮件(无论如何,无论如何)试图实现类futureServerRoot:public serverRoot;)

总而言之,这是一个有用的重构。它消除了一堆锅炉板(在后续的物体上也不需要)。 dmc样板文件在类中体积较小(实现可以从内联或其他类中移出),并且不再陷入从地狱到邪恶的单片开关。

不完美,但它是从我之前所做的一个重大imporvement,它提供了一个我原来的问题的答案。