【问题标题】:Calling arbitrary function in C++在 C++ 中调用任意函数
【发布时间】:2016-11-16 00:41:19
【问题描述】:

我正在用 C++ 开发一个非常小的 RPC 库。我想注册这样的 RPC 函数:

void foo(int a, int b) {
    std::cout << "foo - a: " << a << ", b: " << b << std::endl;
}

myCoolRpcServer->registerFnc("foo", foo(int,int))

客户端请求将作为函数名和参数数组到达。 服务端会检查是否注册了相应的函数,如果有,就会执行该函数。

MyCoolRpcServer::handleRequest(string fnc, vector<FncArg> args)
{
    // Check if we have the function requested by the client:
    if (!this->hasFunction(fnc)) {
        throw ...
    }

    if (!this->function(fnc).argCnt != args.count()) {
        throw ...
    }

    // I think this is the hardest part - call an arbitrary function:
    this->function(fnc)->exec(args); // Calls the function foo()               
}

我的问题是如何存储函数引用(包括参数类型)以及如何再次调用它。我知道当我调用 SLOT(...) 宏时必须在 Qt 中使用类似的东西,但是在这么大的库中找到它是相当棘手的...

感谢您的建议。

Klasyc

【问题讨论】:

  • 如果您在参数列表中有指针,这确实是一项非常复杂的任务。为什么不使用 op system RPC 实现?
  • 使用 std::function 可以轻松存储函数。存储具有不同原型的函数数组很难(我什至会说不可能)。您可能可以使用更高级的类型擦除来做一些事情,但在需要时您会缺少真实类型。您要么需要对函数强制执行单个原型(并使用 std::function 数组),要么编写一些生成器以便在需要时检索类型(使用更高级的类型擦除)。
  • 你将如何编组指针?
  • 我的感觉是你低估了你的目标有多难。

标签: c++ rpc


【解决方案1】:

您可能会使用std::function,但您的主要问题是注册函数的签名(即参数的数量和类型,结果的类型)是什么,以及如何在编译时和运行时知道它(以及另外,如何调用任意的、运行时已知的、签名的函数)。请注意,C++ 通常为 erasing types(它们在运行时被“遗忘”)。

请注意,函数的签名在 C 和 C++(对于编译器)中非常重要,因为 calling conventionsABI 可能需要不同的机器代码来调用它们。

你可以决定你有一些通用的值类型(也许是未来的std::experimental::any)。或者(更简单,但不太通用)您可以定义一些抽象超类 MyValue(它可能是来自 Qt 的 QVariant,或受其启发)并仅处理映射单个 std::vector&lt;MyValue&gt; 的函数(概念上表示您的参数RPC) 到MyValue 结果。然后您将只注册与std::function&lt;MyValue(std::vector&lt;MyValue&gt;))&gt; 兼容的lambda-expressions,并要求他们在运行时检查arity 和type。

或者,您可以决定将自己限制为几个签名,例如只接受不超过 4 个参数的函数,每个参数要么是 std::string 要么是 int (因此您将一一处理 31 个不同的签名)。

您还有一个与serialization 相关的问题,即任意值共享一些公共指针(或子值)。查看libs11n

您还可以使用一些机制来注册签名本身。您可能会利用现有的元数据机制(例如 Qt 元对象协议)。您可以有一些类型和签名的文本描述,并编写一些 C++ 代码生成器来处理它们。

您可以查看libffi。调用具有任意签名的任意原始函数可能是相关的。

如果足够通用,您的库就不会很小。您可能会限制自己,例如JSON 值和表示。见JSONRPC

您可能有metaprogramming 方法,例如给出注册函数的签名(以某种定义的格式),在运行时(初始化)为它们的胶水代码生成plugin的C++代码,并编译和dynamically load那个插件(例如在Linux上使用dlopen(3))。

同时查看CORBA & ONCRPC & Boost.RPC

PS。我假设你的 C++ 至少是 C++11。顺便说一句,如果你想要一个通用的解决方案,你低估了你的目标有多困难。您可能会为此花费数月或数年的时间。

【讨论】:

  • 感谢您的建议。 JSONRPC 链接将我带到AnyRPC 库,这正是我想要的。到目前为止,它似乎使用了一种“通用”函数类型,它接受您在此处建议的通用数据类型数组。
【解决方案2】:

基本思路

基本思想是您希望将函数封装在一些包装对象中,该对象将处理一些通用输入/输出并将它们映射到您的底层函数所期望的内容。

首先让我们创建一个用于存储任何值的类型:

// Dummy implementation which only works for some type.
class Value {
  long value_;
public:
  template<class T>
  T get()
  {
    return (T) value_;
  }  
  template<class T>
  Value& operator=(T const& x)
  {
    value_ = x;
    return *this;
  }
};

让我们使用泛型参数隐藏我们的函数:

typedef std::function<Value(std::vector<Value>&)> Function;

我们现在想要包装任何函数指针,以符合这个签名。包装函数应该解开参数,调用真正的函数并将结果包装在一个值中:

template<class F> class FunctionImpl;

template<class R, class... T>
class FunctionImpl<R(*)(T...)>
{
  R(*ptr)(T... args);
  template<std::size_t... I>
  Value call(std::vector<Value>& args, integer_sequence<std::size_t, I...>)
  {
    Value value;
    value = ptr(args[I].get< typename std::tuple_element<I, std::tuple<T...>>::type >()...);
    return value;
  }
public:
  FunctionImpl(R(*ptr)(T... args)) : ptr(ptr) {}
  Value operator()(std::vector<Value>& args)
  {
    constexpr std::size_t count = std::tuple_size<std::tuple<T...>>::value;
    if (args.size() != count)
      throw std::runtime_error("Bad number of arguments");
    return call(args, make_integer_sequence<std::size_t, std::tuple_size<std::tuple<T...>>::value>());
  }
};

integer_sequencemake_integer_sequence 是标准 C++17 库的一部分,但您可以编写自己的实现。

我们现在定义一个注册可调用函数的类型:

class Functions {
private:
  std::unordered_map<std::string, Function> functions_;
public:
  template<class F>
  void add(std::string const& name, F f)
  {
    functions_[name] = FunctionImpl<F>(std::move(f));  
  }
  Value call(std::string name, std::vector<Value>& args)
  {
    return functions_[name](args);
  }
};

我们可以使用它:

int foo(int x, int y)
{
  std::printf("%i %i\n", x, y);
  return x + y;
}

int main()
{
  Functions functions;
  functions.add("foo", &foo);

  std::pair<std::string, std::vector<Value>> request = parse_request();
  Value value = functions.call(request.first, request.second);
  generate_answer(value);

  return 0;
}

具有虚拟 RPC 通信功能:

std::pair<std::string, std::vector<Value>> parse_request()
{
  std::vector<Value> args(2);
  args[1] = 8;
  args[0] = 9;
  return std::make_pair("foo", std::move(args));
}

void generate_answer(Value& value)
{
  std::printf("%i\n", value.get<int>());
}

我们得到:

8 9
17

当然,这是高度简化的,如果你想概括它,你会面临很多问题:

  • 您可能还想传播异常;

  • 整数类型(例如long)在不同平台上的大小不同;

  • 如果你想处理指针和引用,它开始变得复杂(你可能不应该);

  • 您必须为您正在使用的所有类型添加序列化/反序列化代码。

序列化

处理序列化的方法是使用通用编程进行序列化/反序列化:

template<class T> class Type {};
typedef std::vector<char> Buffer;

// I'm clearly not claiming this would be efficient, but it gives
// the idea. In pratice, you might want to consume some streaming I/O
// API.
class Value {
  Buffer buffer_;
public:
  template<class T>
  T get()
  {
    return deserialize(Type<T>(), buffer_);
  }  
  template<class T>
  Value& operator=(T const& x)
  {
    serialize(x, buffer_);
    return *this;
  }
};

inline std::uint32_t deserialize(Type<std::uint32_t>, Buffer const& buffer)
{
  if (buffer.size() != sizeof(std::uint32_t))
    throw std::runtime_error("Could not deserialize uint32");
  std::uint32_t res;
  memcpy(&res, buffer.data(), sizeof(std::uint32_t));
  return be32toh(res);
}
inline void serialize(std::uint32_t value, Buffer const& buffer)
{
  buffer.resize(sizeof(std::uint32_t));
  value = htobe32(value);
  memcpy(buffer.data(), &value, sizeof(std::uint32_t));
}

另一种可能性是使用泛型编程,让Function 进行序列化/反序列化。

【讨论】:

  • 你可以使用std::tuple_size&lt;std::tuple&lt;T...&gt;&gt;::value而不是sizeof...(T)
  • 这个地址如何编组容器类型(例如远程发送一些std::map&lt;std::string,std::shared_ptr&lt;long&gt;&gt;)?
  • @BasileStarynkevitch,它不直接 :) “我的问题是如何存储函数引用(包括参数类型)以及如何再次调用它”您可以注册序列化/反序列化处理程序或让函数使用通用编程进行序列化/反序列化。但很明显,我并没有声称在 50 LoC 中编写了一个功能齐全的 RPC 生产就绪 RPC 实现 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-10-27
  • 1970-01-01
  • 2018-02-11
  • 1970-01-01
  • 2021-03-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多