[英]Calling Python function with arbitrary number (not known at compilation time) of parameters from 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::string
或int
(因此您将一个接一个地处理31个不同的签名)。
您还遇到一个问题,即共享一些公共指针(或子值)的任意值的序列化 。 查看libs11n 。
您还可以使用一些机制来注册签名本身。 您可能会利用现有的元数据机制(例如Qt元对象协议)。 您可能需要一些关于类型和签名的文字描述,并编写一些处理它们的C ++代码生成器。
您可能会研究libffi 。 调用具有任意签名的任意原始函数可能是相关的。
如果您的库足够通用,它将不会很小。 您可能会限制自己使用例如JSON值和表示形式。 参见JSONRPC 。
您可能有一个元编程方法,例如,提供注册函数的签名(以某种定义的格式),在运行时(初始化)为其插件代码生成插件的C ++代码,并编译和动态加载该插件(例如,使用dlopen( 3)在Linux上)。
另请参阅 CORBA & ONCRPC & Boost.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_sequence
和make_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.