I have my RPC demo program below, but I'm stuck with the problems of reliably pushing parameters into the pipe and pulling them out in the same order.
Serialize()
and Invoke()
(both the global version and the class centric ones)Serialize()
stores the parameters left to right.Invoke()
reads them right to left, which leaves them in reverse order.
Invoke()
calls the gets the parameters from left to right, but seems to require std::apply()
to call the function.
std::apply()
is doing? (plus work on member functions as well, which I couldn't figure out the syntax)TL;DR: I'm trying to come up with a reliable, non-implementation specific way to create some template functions to store their parameters into a pipe and restore them later in the same order and call the function. (All driven by the function's signature).
If all else fails, I have a working solution(using the Reverser
class), it just feels hacky.
godbolt here: https://godbolt.org/z/bh4snvn57
#include <stdint.h>
#include <stdio.h>
#include <tuple>
#include <iostream>
#include <cstring>
#define REVERSER 1
#define SHOW_PUSH_PULL 0
//////////////////////////////////////////////////////////////////////
//string hashing operator and function
namespace detail
{
// FNV-1a 32bit hashing algorithm.
inline constexpr uint32_t fnv1a_32(char const *s, size_t count)
{
return count ? (fnv1a_32(s, count - 1) ^ s[count - 1]) * 16777619u : 2166136261u;
}
} // namespace detail
inline constexpr uint32_t operator"" _hash(const char * s, size_t count)
{
return detail::fnv1a_32(s, count);
}
constexpr uint32_t hash(char const *s,size_t count)
{
return detail::fnv1a_32(s,count);
}
//////////////////////////////////////////////////////////////////////
#if REVERSER
///////////////////////////////////////////////////////////////////////////////////
//Reverser class to reverse the order of parameters
class Reverser
{
uint8_t storage[1024+1];
uint8_t *writeptr;
public:
template<typename T>
void push(T &data)
{
#if SHOW_PUSH_PULL
printf(" Push(%s)\n",typeid(data).name());
#endif
writeptr -= sizeof(data);
memcpy(writeptr,&data,sizeof(data));
}
Reverser() {
writeptr = &storage[1024];
}
void GetPtr(uint8_t *&ptr, size_t &bytes)
{
ptr = writeptr;
bytes = uintptr_t(&storage[1024]) - uintptr_t(writeptr);
}
};
///////////////////////////////////////////////////////////////////////////////////
#endif
///////////////////////////////////////////////////////////////////////////////////
//The "IPC" mechanism
class ByteQueue
{
uint8_t storage[1024];
uint8_t *writeptr;
public:
void push(uint8_t *pPtr, size_t bytes)
{
memcpy(writeptr,pPtr,bytes);
writeptr += bytes;
}
template<typename T>
void push(T &data)
{
memcpy(writeptr,&data,sizeof(data));
writeptr += sizeof(data);
}
template<typename T>
void pull(T&data)
{
memcpy(&data,storage,sizeof(data));
uint32_t uAmountToCopy = uintptr_t(writeptr)-uintptr_t(storage)-sizeof(data);
memmove(storage,storage+sizeof(data),uAmountToCopy);
writeptr -= sizeof(data);
}
ByteQueue() {
writeptr = storage;
}
};
ByteQueue g_ByteQueue;
void send_to_IPC_Pipe(uint8_t *pPtr, size_t uLength)
{
g_ByteQueue.push(pPtr,uLength);
}
template<typename T>
void send_to_IPC_Pipe(T data)
{
#if SHOW_PUSH_PULL && !REVERSER
printf(" Push(%s)\n",typeid(data).name());
#endif
g_ByteQueue.push(data);
}
template<typename T>
T get_from_IPC_Pipe()
{
T var;
#if SHOW_PUSH_PULL
printf(" Pull(%s)\n",typeid(T).name());
#endif
g_ByteQueue.pull(var);
return var;
}
template<typename ... Args>
void Serialize(uint32_t FunctionID, Args ... args)
{
send_to_IPC_Pipe(FunctionID);
#if REVERSER
Reverser MyReverser;
//push it into the reverser (oddly, it seems the parameters are parsed
//in reverse order on the way Invoke() call, so we have to reverse them)
//some magical syntax for C++11
int dummy[]= { 0,(MyReverser.push(args),0)...};
//hide the warning
(void)dummy;
uint8_t *pPtr;
size_t uLength;
MyReverser.GetPtr(pPtr,uLength);
send_to_IPC_Pipe(pPtr,uLength);
#else
int dummy[]= { 0,(send_to_IPC_Pipe(args),0)...};
//hide the warning
(void)dummy;
#endif
}
template<typename ... Args>
void Invoke(void(*function)(Args...))
{
#if REVERSER
function(get_from_IPC_Pipe<Args>()...);
#else
std::tuple<Args...> args {get_from_IPC_Pipe<Args>()...};
std::apply(function,std::move(args));
#endif
}
//////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////
void MyFunction(int a, float b, bool c)
{
printf("ClientSide: %s, %d, %f, %i\n",__PRETTY_FUNCTION__,a,b,c);
Serialize(hash(__PRETTY_FUNCTION__,strlen(__PRETTY_FUNCTION__)),a,b,c);
}
void MyFunctionServerSide(int a, float b, bool c)
{
printf("ServerSide: %s, %d, %f, %i\n",__PRETTY_FUNCTION__,a,b,c);
}
void MyFunction2(int a, float b, bool c)
{
printf("ClientSide: %s, %d, %f, %i\n",__PRETTY_FUNCTION__,a,b,c);
Serialize(hash(__PRETTY_FUNCTION__,strlen(__PRETTY_FUNCTION__)),a,b,c);
}
void MyFunction2ServerSide(int a, float b, bool c)
{
printf("ServerSide: %s, %d, %f, %i\n",__PRETTY_FUNCTION__,a,b,c);
}
void MyFunction3(float a, float b)
{
printf("ClientSide: %s, %f, %f\n",__PRETTY_FUNCTION__,a,b);
Serialize(hash(__PRETTY_FUNCTION__,strlen(__PRETTY_FUNCTION__)),a,b);
}
void MyFunction3ServerSide(float a, float b)
{
printf("ServerSide: %s, %f, %f\n",__PRETTY_FUNCTION__,a,b);
}
///////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////
void MyFunction4()
{
printf("ClientSide: %s\n",__PRETTY_FUNCTION__);
Serialize(hash(__PRETTY_FUNCTION__,strlen(__PRETTY_FUNCTION__)));
}
void MyFunction4ServerSide()
{
printf("ServerSide: %s\n",__PRETTY_FUNCTION__);
}
///////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////
struct ClientSideClass
{
template<typename ... Args>
void Serialize(uint32_t FunctionID, Args ... args)
{
auto *pName = typeid(*this).name();
uint32_t uClassID = hash(pName,strlen(pName));
send_to_IPC_Pipe(uClassID);
send_to_IPC_Pipe(FunctionID);
#if REVERSER
Reverser MyReverser;
//push it into the reverser (oddly, it seems the parameters are parsed
//in reverse order on the way Invoke() call, so we have to reverse them)
//some magical syntax for C++11
int dummy[]= { 0,(MyReverser.push(args),0)...};
//hide the warning
(void)dummy;
uint8_t *pPtr;
size_t uLength;
MyReverser.GetPtr(pPtr,uLength);
send_to_IPC_Pipe(pPtr,uLength);
#else
int dummy[]= { 0,(send_to_IPC_Pipe(args),0)...};
//hide the warning
(void)dummy;
#endif
}
void Method1(int a)
{
printf("ClientSide: %s: %d\n",__PRETTY_FUNCTION__,a);
Serialize(hash(__PRETTY_FUNCTION__,strlen(__PRETTY_FUNCTION__)),a);
}
void Method1(int a,int b)
{
printf("ClientSide: %s: %d,%d\n",__PRETTY_FUNCTION__,a,b);
Serialize(hash(__PRETTY_FUNCTION__,strlen(__PRETTY_FUNCTION__)),a,b);
}
void Method2(int a,int b)
{
printf("ClientSide: %s: %d,%d\n",__PRETTY_FUNCTION__,a,b);
Serialize(hash(__PRETTY_FUNCTION__,strlen(__PRETTY_FUNCTION__)),a,b);
}
};
struct ServerSideClass
{
template<typename ... Args>
void Invoke(void(ServerSideClass::*function)(Args...))
{
#if REVERSER
(*this.*(function))(get_from_IPC_Pipe<Args>()...);
#else
std::tuple<Args...> args {get_from_IPC_Pipe<Args>()...};
std::apply(function,std::move(args));
#endif
}
void Method1(int a) { printf("ServerSide: %s: %d\n",__PRETTY_FUNCTION__,a); }
void Method1(int a,int b) { printf("ServerSide: %s: %d,%d \n",__PRETTY_FUNCTION__,a,b); }
void Method2(int a,int b) { printf("ServerSide: %s: %d,%d \n",__PRETTY_FUNCTION__,a,b); }
void InvokeIt()
{
uint32_t uFunctionID = get_from_IPC_Pipe<uint32_t>();
switch (uFunctionID)
{
case "void ClientSideClass::Method1(int)"_hash:
{
void (ServerSideClass::*function)(int) = &ServerSideClass::Method1;
Invoke(function);
}
break;
case "void ClientSideClass::Method1(int, int)"_hash:
{
void (ServerSideClass::*function)(int,int) = &ServerSideClass::Method1;
Invoke(function);
}
break;
case "void ClientSideClass::Method2(int, int)"_hash:
Invoke(&ServerSideClass::Method2);
break;
default:
printf("Unknown method\n");
}
}
};
///////////////////////////////////////////////////////////
ServerSideClass g_ServerSide;
void runRPCs()
{
uint32_t uFunctionID = get_from_IPC_Pipe<uint32_t>();
// printf("runRPC:function id(%u)\n",uFunctionID);
switch (uFunctionID)
{
case "15ClientSideClass"_hash:
g_ServerSide.InvokeIt();
break;
case "void MyFunction(int, float, bool)"_hash:
Invoke(MyFunctionServerSide);
break;
case "void MyFunction2(int, float, bool)"_hash:
Invoke(MyFunction2ServerSide);
break;
case "void MyFunction3(float, float)"_hash:
Invoke(MyFunction3ServerSide);
break;
case "void MyFunction4()"_hash:
Invoke(MyFunction4ServerSide);
break;
default:
printf("Unknown function id\n");
break;
}
}
int main()
{
ClientSideClass client;
// auto *pName = typeid(ClientSide).name();
// printf("--%s--\n",pName);
// printf("%u\n","10ClientSide"_hash);
// printf("%u\n",hash(pName,strlen(pName)));
MyFunction(2,4.33,true);
MyFunction4();
MyFunction2(4,-4.33,false);
MyFunction3(3.144,-4.33);
client.Method1(5);
client.Method1(3,7);
client.Method2(8,9);
runRPCs();
runRPCs();
runRPCs();
runRPCs();
runRPCs();
runRPCs();
runRPCs();
return 0;
}
Massive amounts of boilerplate on the road ahead. You might want to double-check if Boost offers something similar, it's likely. The simplified solution here.
Note that it's not entirely complete, i.e.:
a. INVOKE
does not take all the subtelties of std::reference_wrapper and the like into account.
b. apply
accepts the tuple by value for brevity. All the necessary lvalue-const-rvalue overloads need to be added.
#include <tuple>
#include <utility>
#include <iostream>
namespace std14
{
template<typename T, T... Ints>
struct integer_sequence
{
typedef T value_type;
static constexpr std::size_t size() { return sizeof...(Ints); }
};
template<std::size_t... Ints>
using index_sequence = integer_sequence<std::size_t, Ints...>;
template<typename T, std::size_t N, T... Is>
struct make_integer_sequence : make_integer_sequence<T, N-1, N-1, Is...> {};
template<typename T, T... Is>
struct make_integer_sequence<T, 0, Is...> : integer_sequence<T, Is...> {};
template<std::size_t N>
using make_index_sequence = make_integer_sequence<std::size_t, N>;
template<typename... T>
using index_sequence_for = make_index_sequence<sizeof...(T)>;
}
template <class F, class... Args>
constexpr auto INVOKE(F&& f, Args&&... args) ->
decltype(std::forward<F>(f)(std::forward<Args...>(args)...)) {
return std::forward<F>(f)(std::forward<Args...>(args)...);
}
template<typename Fptr, typename Class, typename... Args>
constexpr auto INVOKE(Fptr fptr, Class&& obj, Args&& ...args)
-> decltype((std::forward<Class>(obj).*fptr)(std::forward<Args>(args)...))
{
return (std::forward<Class>(obj).*fptr)(std::forward<Args>(args)...);
}
template<typename F, typename Tuple, std::size_t...I>
constexpr auto apply_impl(F&& f, Tuple&& t, std14::index_sequence<I...>)
-> decltype(INVOKE(std::forward<F>(f), std::get<I>(std::forward<Tuple>(t))...))
{
return INVOKE(std::forward<F>(f), std::get<I>(std::forward<Tuple>(t))...);
}
template<typename F, typename ...Args>
constexpr auto apply(F&& f, std::tuple<Args...> tup)
-> decltype(apply_impl(
std::forward<F>(f),
std::forward<std::tuple<Args...>>(tup),
std14::make_index_sequence<sizeof...(Args)>{}
))
{
return apply_impl(
std::forward<F>(f),
std::forward<std::tuple<Args...>>(tup),
std14::make_index_sequence<sizeof...(Args)>{}
);
}
struct S
{
char f(int i) const { return 'a' + 1;}
char g(char i, int j) const { return j + i;}
};
struct F
{
void operator()(int i) {std::cout <<i << '\n';}
};
int add(int i) { return i + 2;}
int main()
{
S s{};
std::cout << INVOKE(&S::f, s, 5) << '\n';
std::cout << INVOKE(&S::g, s, 'b', 1) << '\n';
INVOKE(F{}, 8);
std::tuple<S&, char, int> t {s, 'c', 2};
std::cout << apply(&S::g, t) <<'\n';
std::cout << INVOKE(&add, 5) <<'\n';
return 0;
}
Index sequence implementation taken from here: https://gist.github.com/ntessore/dc17769676fb3c6daa1f Demo: https://godbolt.org/z/e1Wfj85q7
BTW, have a look here for reverting the tuple.