Friday, October 7, 2011

Calling functions dynamically in C

This is not news that someone can loads a function dynamically from a library and use it regularly. But how
about dynamically choosing the function to call and send the number of arguments based on this decision? One can say that this can be achieved using a BIG switch, but, using a switch or ifs we'll always have to change the code when new functions are included, besides that, it will not be fun! :)

This is simpler than it looks, all we have to do is to input some assembler into the code and et-voilà! We just need to pay attention at the calling convention being used. In the _cdecl calling convention the caller must cleanup the stack and as opposite of that, the _stdcall calling convention, the callee (the target function) will do the job automatically. For more details on calling conventions see this article in Wikipedia.

See below the example for the x86 processor in Windows 32 bits assembler. Notice 64 bits numbers are sent in 2 registers, the most significant byte are pushed first on the stack.


#include <stdio.h>

/**
 * This function will be called dynamically (This will return my IQ coefficient! :-)
 */
__int64 _cdecl test_stack(int first,int second,char *third,__int64 last)
{
    return(180);
}

/**
* example on how to execute a function with dynamic arguments passing
* (example valid for x86 in Windows 32 bits for _stdcall and _cdecl calling conventions)
* see http://en.wikipedia.org/wiki/X86_calling_conventions for more detais
*/
int main(int argc,char *argv[])
{
#define MSB_LL(ll) ((long) (ll >> 32))
#define LSB_LL(ll) ((long) ll)
    int            result,first=4,sec=5;char *third="6";
    void          *f=test_stack;
    __int64    ll=123;
    long          hi=MSB_LL(ll);
    long          lo=LSB_LL(ll);

    /* caller must know the type (and order) of the arguments before calling the function */
    _asm {
        push hi        /* last argument is __int64 - push most significant part first */
        push lo        /* now send the least significant part of int64 */
        push third    /* third argument in the function prototype */
        push sec       /* second argument */
        push first     /* first argument */
        call f             /* call function */
#ifdef _STDCALL_CONVENTION
        /* on _stdcall callee must cleanup the stack */
#else
        /* on _cdecl caller must cleanup the stack */
        add esp, 20    /* Stack cleaning  --> add esp, sizeof(long) * arguments */
#endif
        mov hi,edx     /* get result of hi order (only used for 64 bits returns) */
        mov lo,eax     /* get result of low order or integer response */
    }
    ll=((__int64) hi << 32 | lo);
    return(result);
}


Now, suppose that you receive the function pointer as a parameter and you also receive the arguments to be sent to the function in a va_list fashion? Powerful, don't you think?

Have fun!

No comments:

Post a Comment