Как отложить вызов функции в c

Я пытаюсь отложить вызов функции (используя оболочку функции), сохраняя его аргументы в списке указателей void:

void *args[]
int argt[]

Аргт используется для запоминания типа данных, хранящегося в местоположении void *.

Позже мне нужно вызвать отложенную функцию:

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

но проблема в том, что я должен правильно указать их тип.

Я использую макрос, например:

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

и вызов функции будет:

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

У меня есть две проблемы:

1) предупреждение: несоответствие типа указателя/целого в условном выражении, генерируемое определением макроса (обратите внимание, что я могу жить с ним, см. 2))

2) реальная проблема: длинный длинный аргумент не передается правильно (каждый раз получаю 0)

Мне явно не хватает чего-то, поэтому кто-нибудь может объяснить (подробно), почему макрос работает неправильно или предлагает другой подход?

EDIT: Я добавляю здесь часть аргументов хранения (соответствующие данные, я разбираю va_list), он получает их тип на основе спецификатора формата:

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++;
}

Ответы

Ответ 1

Ваше предупреждение вызвано тройными операторами, имеющими несколько типов в своих подвыражениях, то есть: -

cond ? expression of type 1 : expression of type 2

два выражения должны оцениваться одним и тем же типом, что на самом деле вам не очень помогает.

Чтобы решить вашу проблему, я могу представить два решения, оба из которых немного неприятны.

  • Используйте функции VARARGS/variadic

    Определите свою функцию с помощью параметра "..." и сохраните параметры где-нибудь с помощью заданных макросов и определите целевую функцию как принятие va_list. Вы теряете каждый бит безопасности типа, проверяете компилятор и требуете дополнительных метаданных для функций, а также переписываете целевую функцию для использования va_list.

  • Откажитесь от ассемблера и взломайте стек

Говорил, что это было противно. Для функции: -

void FuncToCall (type1 arg1, type2 arg2);

создать функцию: -

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

который копирует параметры в стек динамически выделенному блоку памяти, который возвращается. Затем, когда вы хотите вызвать функцию: -

void CallDelayedFunction (void *params); 

с указателем, возвращаемым вызовом FuncToCallDelayed. Затем это подталкивает параметры в стек и вызывает функцию. Параметры и указатель функции находятся в параметре params.

Этот метод привязывает вас к определенному типу процессора, но по крайней мере сохраняет некоторую форму проверки типов в списке параметров.

Обновление

Здесь версия метода 2, созданная для Visual Studio 2012, IA32, работающая на 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;
}

** Другое обновление **

Существует третий вариант, как я упоминал в комментариях, и это использование системы обмена сообщениями. Вместо вызова функции создайте объект сообщения формы: -

struct MessageObject
{
   int message_id;
   int message_size;
};

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

а затем выполнить поиск между message_id и действительными функциями, с функциями и поиском, определенными как: -

void Function1 (Function1Object *message)
{
}

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

Lookup lookups [] = { {Message1ID, (void (*) (void *data)) Function1}, etc };

Ответ 2

Ваша попытка не работает, потому что истинные и ложные результирующие операнды оператора ?: должны быть совместимыми типами.

Мое первоначальное предположение о том, что вы создаете макрос оболочки вызова функции, который расширяет аргументы с любой возможной комбинацией, на самом деле не является жизнеспособным решением для вас, поскольку вы действительно хотите поддерживать не только два типа и два аргумента.

Мне пришло в голову, что вы можете использовать swapcontext() и setcontext(), чтобы отложить вызов. В принципе, вместо того, чтобы сбрасывать аргументы в структуру данных и возвращаться из вашей функции печати для будущего вызова, который распаковывает ваши спрятанные аргументы, вы используете swapcontext(), чтобы перейти к функции, которую вы хотите захватить, до тех пор, пока ваша печать может возобновиться. Если вам нужно только перевернуть назад и вперед, вам понадобятся только два контекста.

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

Ваша функция печати может выглядеть примерно так:

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);
}

Где alt_ctx_ инициализируется функцией рандеву, которая берет на себя выполнение до тех пор, пока печать не возобновится. Когда печать может возобновиться, контекст печати восстанавливается с помощью:

    setcontext(&es.main_ctx_);

Я закодировал игрушечный пример, вы можете увидеть его в действии здесь.

Ответ 3

Используйте библиотеку вызовов внешних функций, она позаботится обо всех деталях для конкретной платформы. Например, здесь вы можете отложить вызов функции функции с параметрами int, void* и long long и вернуть 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

Ответ 4

Вы можете использовать что-то вроде этого:

#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;
}

Секрет здесь заключается в использовании объединения.

Этот код даст много предупреждений, потому что компилятор не может правильно определить тип возвращаемого значения из макроса.

Приведенный выше код будет правильно печатать:

a, 65535, hello, 
a, 65535, hello

(Протестировано с помощью gcc и clang on linux)

Ответ 5

Я предлагаю следующий способ решить эту проблему. Прежде всего, позвольте избавиться от проверки типа аргумента во время вызова функции:

#include <stdio.h>

int function(int a, long long b)
{
    printf("a = %d\n", a);
    printf("b = %lld\n", b);
    return 0;
}

int function2(double c, char *d)
{
    printf("c = %f\n", c);
    printf("d = %s\n", d);
    return 0;
}

typedef int (*ftype)(); // The type of function which can take undefined number of arguments and return 'int'

int main(int argc, char *argv[])
{
    ftype f1, f2;

    f1 = (ftype)function;
    f2 = (ftype)function2;
    f1(10, 100500);
    f2(2.3, "some string");
    return 0;
}

Затем мы можем реализовать "диспетчер", который будет правильно выполнять вызов функции:

int dispatch(void **args, int call_type, ftype function)
{
    int ret_val;
    switch(call_type)
    {
        0: ret_val = function(*(int*)args[0], *(double*)args[1]);
           break;
        1: ret_val = function(*(long long*)args[0], *(int*)args[1]);
           break;

        etc etc...
    }
}

Основным недостатком этого подхода является необходимость внедрения большого количества случаев для диспетчера. И, конечно же, он будет работать только в том случае, если все эти случаи определены априори.

Наконец, я должен сказать, что это чрезвычайно опасная реализация. Он легко может стать источником странных и опасных ошибок.

Ответ 6

Почему вы не используете g_timeout_add_seconds()

Устанавливает функцию, которая должна вызываться через регулярные интервалы с приоритетом по умолчанию, G_PRIORITY_DEFAULT. Функция вызывается повторно, пока не вернется FALSE, после чего тайм-аут будет автоматически уничтожен, и функция не будет вызываться снова.