Как отложить вызов функции в 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, после чего тайм-аут будет автоматически уничтожен, и функция не будет вызываться снова.