C ++中回调/ RPC的动态函数Args

时间:2015-09-18 18:19:40

标签: c++ c++11 callback rpc variadic-functions

我需要在带参数的函数列表中注册如下所示的函数。

void func1( int a , char* b ) {}
void func2( vec3f a , std::vector<float> b , double c) {}
...

当我通过适当的参数通过网络接收数据时,回复它们。我想va_list会解决,但它不起作用:

void func1(int  a, char* b)
{
    printf("%d %s",a,b);
}
void prepare(...)
{
    va_list argList; 
    int args = 2;
    va_start(argList, args);
    ((void (*)(va_list))func1)(argList);
    va_end(argList);
}
int main(int argc, char **argv)
{
    prepare(1, "huhu");
    return 0;
}

解决这个问题最优雅的方法是什么? 我知道std :: bind / std :: function具有相似的能力,但是我假设内部数据隐藏在std深处。我只需要一些基本数据类型,不必为任意类型。如果使用## VA_ARGS 预处理器技巧或使用模板解决,我也可以。优先考虑的是它最简单易用。

Edit1:我发现程序集可以解决(How do I pass arguments to C++ functions when I call them from inline assembly) - 但我更喜欢更独立于平台的解决方案。

1 个答案:

答案 0 :(得分:0)

如果你的目标是创建自己的,小的和临时的&#34; rpc&#34;解决方案,可能是决策的主要驱动因素之一应该是:1。最少量的代码2.尽可能简单。

记住这一点,它是值得思考的,以下两种情况之间存在差异:

  1. &#34;真&#34; RPC:处理程序应与您使用rpc-method-specific签名一样。

  2. &#34;消息传递&#34;:处理程序接收&#34;终点确定类型&#34;或者只是统一的消息类型。

  3. 现在,要获得类型1的解决方案需要做些什么?

    对于某些选定的协议,需要将传入的字节流/网络数据包解析为某种消息。然后,根据{serviceContract,serviceMethod}使用一些元信息(契约),需要在数据包中确认一组特定的数据项,如果存在,则需要调用相应的注册处理函数。在该基础结构中的某个地方,您通常有一个(可能是代码生成的)函数,它可以执行类似的操作:

    void CallHandlerForRpcXYCallFoo( const RpcMessage*message )
    {
         uint32_t arg0 = message->getAsUint32(0);
         // ...
         float argN = message->getAsFloat(N);
         Foo( arg0, arg1, ... argN );
    }
    

    当然,所有这些都可以打包到类和虚方法中,其中类是从服务契约元数据生成的。也许,还有一种方法可以通过一些过多的模板巫术来避免生成代码并拥有更通用的元实现。但是,这一切都是有效的,真正的工作。做太多工作只是为了好玩。而不是这样做,使用已经完成的数十种技术之一会更容易。

    到目前为止值得注意的是:在该艺术品的某个地方,可能存在(代码生成的)功能,看起来像上面给出的功能。

    现在,要获得类型2的解决方案需要做些什么?

    少于案例1.为什么?因为你只是在调用那些处理程序方法时停止你的实现,这些方法都将RpcMessage作为它们的单个参数。因此,你可以在不产生&#34; make-it-like-like-a-function-call&#34;这些方法之上的层。

    不仅工作量减少,而且在合同变更的某些情况下也更加强大。如果将另外一个数据项添加到&#34; rpc解决方案&#34;,&#34; rpc函数的签名&#34;必须改变。代码重新生成,应用程序代码适应。而且,无论应用程序是否需要新数据项。另一方面,在方法2中,代码中没有重大变化。当然,根据您的选择和合同中的变化类型,它仍然会破裂。

    因此,最优雅的解决方案是:不要做RPC,做消息传递。最好采用REST-ful方式。

    另外,如果您更喜欢&#34;统一&#34; rpc消息通过一些特定于rpc-contract的消息类型,你删除了代码膨胀的另一个原因。

    以防万一,我说的看起来有点过于抽象,这里有一些模拟虚拟代码,草图解决方案2:

    #include <cstdio>
    #include <cstdint>
    #include <map>
    #include <vector>
    #include <deque>
    #include <functional>
    
    // "rpc" infrastructure (could be an API for a dll or a lib or so:
    
    // Just one way to do it. Somehow, your various data types need 
    // to be handled/represented.
    class RpcVariant
    {
    public:
        enum class VariantType
        {
            RVT_EMPTY,
            RVT_UINT,
            RVT_SINT,
            RVT_FLOAT32,
            RVT_BYTES
        };
    private:
        VariantType m_type;
        uint64_t m_uintValue;
        int64_t m_intValue;
        float m_floatValue;
        std::vector<uint8_t> m_bytesValue;
    
        explicit RpcVariant(VariantType type)
            : m_type(type)
        {
    
        }
    public:
        static RpcVariant MakeEmpty()
        {
            RpcVariant result(VariantType::RVT_EMPTY);
            return result;
        }
        static RpcVariant MakeUint(uint64_t value)
        {
            RpcVariant result(VariantType::RVT_UINT);
            result.m_uintValue = value;
            return result;
        }
        // ... More make-functions
    
        uint64_t AsUint() const
        {
            // TODO: check if correct type...
            return m_uintValue;
        }
        // ... More AsXXX() functions
    
        // ... Some ToWire()/FromWire() functions...
    };
    typedef std::map<uint32_t, RpcVariant> RpcMessage_t;
    typedef std::function<void(const RpcMessage_t *)> RpcHandler_t;
    
    void RpcInit();
    void RpcUninit();
    
    // application writes handlers and registers them with the infrastructure. 
    // rpc_context_id can be anything opportune - chose uint32_t, here.
    // could as well be a string or a pair of values (service,method) or whatever.
    void RpcRegisterHandler(uint32_t rpc_context_id, RpcHandler_t handler);
    
    // Then according to taste/style preferences some receive function which uses the registered information and dispatches to the handlers...
    void RpcReceive();
    void RpcBeginReceive();
    void RpcEndReceive();
    
    // maybe some sending, too...
    void RpcSend(uint32_t rpc_context_id, const RpcMessage_t * message);
    
    int main(int argc, const char * argv[])
    {
        RpcInit();
        RpcRegisterHandler(42, [](const RpcMessage_t *message) { puts("message type 42 received."); });
        RpcRegisterHandler(43, [](const RpcMessage_t *message) { puts("message type 43 received."); });
        while (true)
        {
            RpcReceive();
        }
        RpcUninit();
        return 0;
    }
    

    如果交换RpcMessage,则在std :: shared_ptr中打包时,您甚至可以拥有多个处理程序或对同一消息实例进行一些转发(到其他线程)。这是一个特别恼人的事情,需要另一个&#34;序列化&#34;在rpc方法中。在这里,您只需转发消息。