Thread: Calling a function pointer with unknown parameters

  1. #1
    Registered User
    Join Date
    Dec 2006
    Posts
    69

    Calling a function pointer with unknown parameters

    I'm implementing remote procedure calls in my networking library. I want the user to be able to register functions with any kind of signature, so I use a void* for this.

    I receive a char array over the net that contains the serialized parameters of the function to be called. So if the function takes two ints, it will be a char array of length 8. I have a void* that points to a C function that is to be called with the received parameters. How do I copy the parameter list to the stack at the location where the C function expects the parameters?

    The following kinda seems to work, but is obviously a bad solution:
    Code:
    struct LargeStruct
    {
       int a;
       int b;
       int c;
       int d;
    };
    typedef void (*CFunc)(LargeStruct x);
    DLLFUNC void tst(int x, void* funcPtr)
    {
       CFunc myFunc = static_cast<CFunc>(funcPtr);
       LargeStruct x;
       x.a = 0;
       x.b = x;
       x.c = 0;
       x.d = 0;
       (*myFunc)(x); // calls the function pointed to by funcPtr with parameters (0, 0, x, 0);
       // I could copy my char array over the LargeStruct
    }
    This has some disadvantages:
    -There is a limit to how large (in bytes) a parameter list can be (sizeof(LargeStruct)).
    -I always have to copy the maximum amount of bytes, and if the function has a shorter parameter list, I will be writing to unknown memory.

    Thanks

  2. #2
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,660
    Rather than serialising your parameters as an amorphous block of memory, why not try a string.

    "a=123 b=456"

    So all functions which need to accept "any kind of signature" are wrapped up in a function which just accepts a const char *. The wrapper function parses out the parameters, and calls the wrapped function with the correct (and perhaps validated) arguments.

    1. It's a lot easier to make it work.
    2. It's a lot easier to test, from say looking at network traffic.
    3. It fixes the endian and alignment problems in the binary serialisation.
    4. It has some measure of safety associated with it.
    5. It isn't arbitrarily constrained by the size of LargeStruct
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  3. #3
    Ethernal Noob
    Join Date
    Nov 2001
    Posts
    1,901
    The Java Reflection library does something similar to what Salem is talking about.

  4. #4
    Registered User
    Join Date
    Dec 2006
    Posts
    69
    Thank you,

    Yes that would be possible but:
    - It would require the user of my library to do the string creation OR use an awkward interface for specifying the type of function when registering it.
    - It would be less bandwidth efficient.

    How could I copy an arbitrarily long piece of data to the parameter list? If I know this I can decide on the method to use (string or other method), or possibly decide to simplify the RPCs.

    I've been messing with variable argument lists, without success.
    I've also tried passing a whole array, but it doesn't seem to copy the array. I don't know how this works in C++.

    Is there some way to create an arbitrarily sized datatype at runtime?

    EDIT:
    Got it to work:
    Code:
    typedef void (*func)( ... );
    
    unsigned char c[8] = {0,0,0,0,0,0,0,0}; // dummy of the incoming parameter data
    struct {char x[8];} a;
    memcpy(&a, c, 8);
    (*myFunc)(a);
    EDIT2: damn, I cannot make the length of the array inside the struct a variable...
    Last edited by C+/-; 06-12-2007 at 08:00 AM.

  5. #5
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    C+/-, no matter what you do, you need some protocols or conventions so that your caller can supply data, and your function can actually receive the intended data. That means your caller needs to do some work of packaging the data (eg writing it to a string) and your callee (eg tst()) needs to do some work of unpacking it (eg reading the set of values back from the string).

    The general solution would be to provide a set of helper functions so your caller can construct the string (or whatever) to be passed, and your callee then interpret that string (eg by unpacking it into a linked list that is then passed to your myFunc()).

    Variable argument lists (stdargs) are not a solution to your problem, as that mechanism implicitly relies on some agreement between caller and the callee, so the callee can work out the number and type of arguments. This is how functions like printf() work: the format string argument is used to work out what the type of subsequent arguments is, and how many arguments there are -- so the caller is required to go through the effort of creating and passing a valid format string.

    But, if you're looking for some means by which your caller can just throw some arbitrary data at your function, and your function can magically interpret it without being supplied with any other information, then you are out of luck. Arbitrary data is not passed around with information about what type it is.

    Incidentally, there are some libraries (eg associated with CORBA, DCOM) that support reflection capability in C++ (and other languages). However, such things are a bit of overkill for your application .... and they still rely on some agreed convention between caller and callee. The only difference is that the additional information is generated via another preprocessor or via complex invocation schemes (and, if not generated by such tools, must still be generated by the programmer).
    Last edited by grumpy; 06-12-2007 at 08:11 AM.

  6. #6
    Registered User
    Join Date
    Dec 2006
    Posts
    69
    I'm being a bit stubborn here, but I really think this is possible with only a very small agreement between caller and callee: the size of the argument list.

    My idea is this:
    Both caller and callee register a function with an ID and a length parameter that specifies the length of the parameter list of the function (in bytes). These functions are not the same on caller and callee.

    The caller has a local dummy function that has the same arguments as the remote function. This function must call a function from my library and pass a pointer to the first argument, and the ID the function was registered with. In my library, I then use that pointer and the length information passed when it was registered to create a RPC packet and send it.

    The receiver/callee receives an RPC packet that holds the ID of the function that is to be called (IDs must match for caller and callee), and the parameter data.

    It all works up to that point (not coded yet, but my tests show it can be done that way).

    So now the callee knows: what function to call, what parameter data to pass, and the length of the parameter data. All I need is a way to pass that data (by value).

    What I am thinking about is treating all the RPC functions as functions taking one parameter of type X (in my library, not in the client code). It must be possible to create a type X with any size at runtime (and it must be on the stack so it can be passed by value to the RPC function).

    Is such a thing possible?
    I tried another thing, but it doesn't work either:
    Code:
    template <int N> struct memblock {char mem[N];};
    But I can't instantiate it with a local variable. I could do some extremely ugly hack with a switch..case where I instantiate the memblock for values 0 to 100 bytes (where 100 would be the maximum).. Is there a cleaner way to do this, while still providing the same (or as simple of an) interface as explained above?
    Last edited by C+/-; 06-12-2007 at 11:40 AM.

  7. #7
    Kiss the monkey. CodeMonkey's Avatar
    Join Date
    Sep 2001
    Posts
    937
    Code:
    template <typename T> 
    struct basic_memblock
    {
      public:
          memblock() : mem(0) {}
          memblock(unsigned int size)  { mem = new T[size]; }
         ~memblock() { if(mem) delete [] mem; }  // ~T() better not throw an exception
          T* mem;
    }
    
    typedef basic_memblock<char> memblock;
    Watch out for when memblock goes out of scope. This is kind of a weak, sloppy auto-pointer.

    I really think you should go Salem's way. You could even write special function objects to make it extra fun.

    *edit* On the stack? Well, crap. If there is no simple and elegant way to do this, then you must reconsider. Let's see what the smarter folks come up with...
    *edit* Maybe all of the stack stuff needs to be known at runtime? I know templates must. How would you refer to a dynamically allocated stack segment?
    Last edited by CodeMonkey; 06-12-2007 at 10:45 PM.
    "If you tell the truth, you don't have to remember anything"
    -Mark Twain

  8. #8
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,660
    @C+/-
    How do you feel about using assembler - bearing in mind the complete loss of portability in addition to everything else?
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  9. #9
    Registered User
    Join Date
    Dec 2006
    Posts
    69
    CodeMonkey, I'm trying to copy an arbitrarily sized memory section (stack or heap) to the (stack) memory section where the to-be-called-function expects its parameter data. One thing I tried with the template is to create a type that has an arbitrary size and can be allocated on the stack.

    I searched for "dynamic allocation on the stack", and found alloca(). Could this be of any use? (don't think so, I already knew how to allocate a dynamic amount of memory on the stack (array), I just don't know how to pass it in its entirety)

    How do you feel about using assembler - bearing in mind the complete loss of portability in addition to everything else?
    Yes, I've had that thought, but I've never used assembler. Portability is not an issue since the engine I'm developing this for is windows-only. Where do you suggest I start learning assembler, and what topic should I study in particular to do this? Or is this trivial enough for you to be willing to write it for me?

    I read here that you can pass an array by value, but I can't get this to work right.. I tried modifying the function pointer typedef to have a "const char []" as parameter and passing a stack-allocated array, but it doesn't work. How does "it" (whatever does the copying of parameters (runtime library?)) know the size of the array in that case?

    Regarding the other solution where I cast the RPC function void* to a function taking a LargeStruct, I have another question. I'm not so concerned about the limitation on parameter list length or the performance impact of copying a whole largeStruct. What I am concerned about is the fact that in many cases, sizeof(LargeStruct ) is greater than the parameter list length, so I will be writing to memory I do not own. Is this a problem (in C), or is it safe to write past the end of the memory section of the parameter list? I guess not.
    Last edited by C+/-; 06-14-2007 at 07:43 AM.

  10. #10
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,660
    Passing arrays by value inside a struct only works because the array size is fixed and known at compile time. You can't hide a variable length array inside a struct.

    So long as all your calls fit inside LargeStruct, there isn't a memory problem. You reserve sizeof LargeStruct and fill it in with whatever you need.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  11. #11
    Registered User
    Join Date
    Dec 2006
    Posts
    69
    So long as all your calls fit inside LargeStruct, there isn't a memory problem. You reserve sizeof LargeStruct and fill it in with whatever you need.
    Ok, that makes sense. Since it is allocated like any function call, that cannot be a problem.

    How about the fact that the compiler which has compiled the called function expects a smaller amount of parameter data to be passed? Will it free all memory that was allocated at the call?


    The assembler solution is still more flexible, so I'd still like to give that a try. Do you think it will take a lot of time to learn assembler to a level needed to write the required code? Could you provide an example?
    Last edited by C+/-; 06-14-2007 at 12:20 PM.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 4
    Last Post: 05-13-2011, 08:28 AM
  2. Compiling sample DarkGDK Program
    By Phyxashun in forum Game Programming
    Replies: 6
    Last Post: 01-27-2009, 03:07 AM
  3. Direct3D problem
    By cboard_member in forum Game Programming
    Replies: 10
    Last Post: 04-09-2006, 03:36 AM
  4. c++ linking problem for x11
    By kron in forum Linux Programming
    Replies: 1
    Last Post: 11-19-2004, 10:18 AM
  5. Question on function syntax and calling function
    By cbrman in forum C Programming
    Replies: 10
    Last Post: 10-05-2003, 05:32 PM