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.
- see
Serialize()
and Invoke()
(both the global version and the class centric ones)
- The "initializer list" method of
Serialize()
stores the parameters left to right.
- the "parameter pack expansion" method of
Invoke()
reads them right to left, which leaves them in reverse order.
- Problem 1: I believe this is implementation specific
- Solving it required pushing the parameters in the reverse order with the Reverser class so that when they were pulled out in the right to left order they were in the right parameters.
- the "initializer list"
Invoke()
calls the gets the parameters from left to right, but seems to require std::apply()
to call the function.
- Problem 2: I can't upgrade to C++17, so is there a C++11 mechanism that can do what
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;
}