繁体   English   中英

在C ++中调用任意函数

[英]Calling arbitrary function in C++

我正在用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中使用类似的东西,但是要在如此大的库中找到它却非常棘手...

谢谢你的建议。

克拉西奇

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

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

您可以确定自己具有某种通用值类型(也许将来是std :: experimental :: any )。 或者(更简单,但不太通用),您可以定义一些抽象超类MyValue (可以是Qt的QVariant ,也可以是受其启发),并仅处理映射单个std::vector<MyValue>的函数(概念上表示您的参数) RPC)转换为MyValue结果。 然后,您将仅注册与std::function<MyValue(std::vector<MyValue>))>兼容的lambda表达式 ,并要求它们在运行时检查Arity和类型。

另外,您可以决定限制自己使用几个签名,例如,仅接受不超过4个参数的函数,每个函数都是std::stringint (因此您将一个接一个地处理31个不同的签名)。

您还遇到一个问题,即共享一些公共指针(或子值)的任意值的序列化 查看libs11n

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

您可能会研究libffi 调用具有任意签名的任意原始函数可能是相关的。

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

您可能有一个元编程方法,例如,提供注册函数的签名(以某种定义的格式),在运行时(初始化)为其插件代码生成插件的C ++代码,并编译和动态加载该插件(例如,使用dlopen( 3)在Linux上)。

另请参阅 CORBAONCRPCBoost.RPC

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

基本思路

基本思想是要将函数封装在一些包装器对象中,该包装器对象将处理一些通用输入/输出并将它们映射到基础函数期望的对象。

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

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

现在,我们想包装任何函数指针,以符合此签名。 包装函数应拆开参数,调用实函数并将结果包装在Value中:

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进行序列化/反序列化。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM