Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to postpone a function call in c

Tags:

c

I am trying to postpone a function call (using a function wrapper), by saving its arguments in a list of void pointers:

void *args[]
int argt[]

The argt is used in order to remember the data type stored at void * location.

Later, I need to call the postponed function:

function(args[0], args[1])

but the problem is that I must specify their type correctly.

I use a macro, like this:

#define ARGTYPE(arg, type) type == CHARP ? (char *) arg : (type == LONGLONG ? *((long long *) arg) : NULL)

and the function call becomes:

function(ARGTYPE(args[0], argt[0]), ARGTYPE(args[1], argt[1]))

I have two issues:

1) warning: pointer/integer type mismatch in conditional expression, generated by the macro definition (please note I can live with it, see 2))

2) the real problem: the long long argument isn't passed correctly (i get 0 every time)

I am clearly missing something, so can anyone please explain (in details) why the macro isn't working properly, or suggest another approach?

EDIT: I add here the storing arguments part (the relevant details, I parse a va_list), it gets their type based on a format specifier:

while (*format)
{
    switch(*format)
    {
        case 's':
            saved_arguments[i] = strdup(arg);
            break;
        case 'l':
            saved_arguments[i] = malloc(sizeof(long long));
            *((long long *) saved_arguments[i]) = arg;
            break;
    }
    i++;
    format++;
}
like image 443
kaspersky Avatar asked Aug 06 '13 14:08

kaspersky


4 Answers

Your warning is caused by the ternary operators having multiple types in their sub-expressions, i.e.:-

cond ? expression of type 1 : expression of type 2

the two expressions need to evaluate to the same type, which doesn't really help you much.

To solve your problem, I can think of two solutions, both of which are a bit nasty.

  1. Use VARARGS / variadic functions

    Define your function using the '...' parameter and store the parameters somewhere using the given macros and define the target function as accepting a va_list. You do lose every bit of type safety, compiler checking and require extra meta data for the functions as well as re-writing the target function to use a va_list.

  2. Drop to assembler and hack the stack

Told you it was nasty. Given a function to call:-

void FuncToCall (type1 arg1, type2 arg2);

create a function:-

void *FuncToCallDelayed (void (*fn) (type1 arg1, type2 arg2), type1 arg1, type2 arg2);

which copies the parameters on the stack to a dynamically allocated block of memory which is returned. Then, when you want to call the function:-

void CallDelayedFunction (void *params); 

with the pointer the call to FuncToCallDelayed returned. This then pushes the parameters onto the stack, and calls the function. The parameters and the function pointer are in the params parameter.

This method does tie you to a specific processor type but at least keeps some form of type checking on the parameter list.

Update

Here's a version of Method 2, built for Visual Studio 2012, IA32, running on Win7:-

#include <iostream>
using namespace std;

__declspec (naked) void *CreateDelayedFunction ()
{
    __asm
    {
        mov esi,ebp
        mov eax,[esi]
        sub eax,esi
        add eax,4
        push eax
        call malloc
        pop ecx
        or eax,eax
        jz error
        mov edi,eax
        sub ecx,4
        mov [edi],ecx
        add edi,4
        add esi,8
        rep movsb
      error:
        ret
    }
}

void CallDelayedFunction (void *params)
{
    __asm
    {
        mov esi,params
        lodsd
        sub esp,eax
        mov edi,esp
        shr eax,2
        mov ecx,eax
        lodsd
        rep movsd
        call eax
        mov esi,params
        lodsd
        add esp,eax
    }
}

void __cdecl TestFunction1 (int a, long long b, char *c)
{
    cout << "Test Function1: a = " << a << ", b = " << b << ", c = '" << c << "'" << endl;
}

void __cdecl TestFunction2 (char *a, float b)
{
    cout << "Test Function2: a = '" << a << "', b = " << b << endl;
}

#pragma optimize ("", off)

void *__cdecl TestFunction1Delayed (void (*fn) (int, long long, char *), int a, long long b, char *c)
{
    return CreateDelayedFunction ();
}

void *__cdecl TestFunction2Delayed (void (*fn) (char *, float), char *a, float b)
{
    return CreateDelayedFunction ();
}

#pragma optimize ("", on)

int main ()
{
    cout << "Calling delayed function1" << endl;
    void *params1 = TestFunction1Delayed (TestFunction1, 1, 2, "Hello");
    cout << "Calling delayed function2" << endl;
    void *params2 = TestFunction2Delayed (TestFunction2, "World", 3.14192654f);
    cout << "Delaying a bit..." << endl;
    cout << "Doing Function2..." << endl;
    CallDelayedFunction (params2);
    cout << "Doing Function1..." << endl;
    CallDelayedFunction (params1);
    cout << "Done" << endl;
}

** Another Update **

There is a third option, as I mentioned in the comments, and that is to use a messaging system. Instead of calling a function, create a message object of the form:-

struct MessageObject
{
   int message_id;
   int message_size;
};

struct Function1Message
{
   MessageObject base;
   // additional data
};

and then have a lookup between message_id and actual functions, with the functions and the lookup defined like:-

void Function1 (Function1Object *message)
{
}

struct Lookup
{
  int message_id;
  void (*fn) (void *data);
};

Lookup lookups [] = { {Message1ID, (void (*) (void *data)) Function1}, etc };
like image 154
Skizz Avatar answered Nov 03 '22 12:11

Skizz


Your attempt is failing because the true and false result operands of the ?: operator need to be compatible types.

My original suggestion that you create a function call wrapper macro that expands the arguments with every possible combination is not really a viable solution to you given that you actually want to support more than just two types and two arguments.

It occurred to me that you could use swapcontext() and setcontext() to postpone the call. Basically, instead of stashing away the arguments into a data structure, and returning from your print function for a future call that unpacks your stashed arguments, you use swapcontext() to jump to the function you want to take over until such time that your print can resume. If you only need to flip back and forth, you only need two contexts.

struct execution_state {
    /*...*/
    ucontext_t main_ctx_;
    ucontext_t alt_ctx_;
    char alt_stack_[32*1024];
} es;

Your print function could look something like this:

void deferred_print (const char *fmt, ...) {
    va_list ap;
    while (need_to_defer()) {
        /*...*/
        swapcontext(&es.main_ctx_, &es.alt_ctx_);
    }
    va_start(ap, fmt);
    vprintf(fmt, ap);
    va_end(ap);
}

Where alt_ctx_ is initialized to a rendezvous function that takes over execution until the print can resume. When the print can resume, the print context is restored with:

    setcontext(&es.main_ctx_);

I coded up a toy example, you can see it in action here.

like image 36
jxh Avatar answered Nov 03 '22 13:11

jxh


Use the foreign function call library, it takes care of all the grungy platform-specific details for you. For example, here's how you might postpone a function call to a function taking an int, void*, and long long parameters and returning int:

#include <avcall.h>

int my_function(int a, void *b, long long c)
{
    // Do stuff
}

...

av_list alist;    // Stores the argument list
int return_value; // Receives the return value

// Initialize the argument list
av_start_int(alist, &my_function, &return_value);

// Push the arguments onto the list
av_int(alist, 42);                 // First arg 'a'
av_ptr(alist, &something);         // Second arg 'b'
av_longlong(alist, 5000000000LL);  // Third arg 'c'

// We're done -- stash away alist and return_value until we want to call the
// function later.  If the calling function needs to return, then you'll need
// to allocate those variables on the heap instead of on the stack

...

// Now we're ready to call the stashed function:
av_call(alist);
// Return value from the function is now stored in our return_value variable
like image 41
Adam Rosenfield Avatar answered Nov 03 '22 12:11

Adam Rosenfield


You may use something like this:

#include <stdio.h>
#include <string.h>
#include <stdarg.h>

enum e_type {
    CHAR = 0,
    INT,
    LONG,
    CHARPTR
};

struct type {
    enum e_type type;
    union {
        char c;
        int i;
        long l;
        char *s;
    } value;
};

#define convert(t) (t.type == CHAR ? t.value.c : (t.type == INT ? t.value.i : (t.type == LONG ? t.value.l : t.value.s)))

void test_fun(int argc, ...)
{
    va_list args;
    int i = 0, curr = 0;
    struct type t;

    va_start(args, argc);

    while (i++ < argc)
    {
        t = va_arg(args, struct type);

        switch (t.type) {
            case CHAR:
                printf("%c, ", convert(t));
                break;

            case INT:
                printf("%d, ", convert(t));
                break;

            case LONG:
                printf("%ld, ", convert(t));
                break;

            case CHARPTR:
                printf("%s, ", convert(t));
                break;
        }
    }
    printf("\n");
    va_end(args);
}

void test_fun2(char c, long l, char *s)
{
    printf("%c, %ld, %s\n", c, l, s);
}

int main()
{
    struct type t1, t2, t3;
    t1.type = CHAR;
    t1.value.c = 0x61;

    t2.type = LONG;
    t2.value.l = 0xFFFF;

    t3.type = CHARPTR;
    t3.value.s = "hello";

    test_fun(3, t1, t2, t3);
    test_fun2(convert(t1), convert(t2), convert(t3));

    return 0;
}

The secret here is to use the union.

This code will give a lot of warnings because the compiler cannot correctly figure the type of the returned value from the macro.

The code above will correctly print:

a, 65535, hello, 
a, 65535, hello

(Tested with gcc and clang on linux)

like image 1
Victor Dodon Avatar answered Nov 03 '22 13:11

Victor Dodon