如何推迟c中的函数调用

时间:2013-08-06 14:00:01

标签: c

我试图通过将参数保存在void指针列表中来推迟函数调用(使用函数包装器):

void *args[]
int argt[]

使用argt是为了记住存储在void * location的数据类型。

稍后,我需要调用推迟的函数:

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)真正的问题:long long参数没有正确传递(我每次都得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++;
}

6 个答案:

答案 0 :(得分:9)

您的警告是由三元运算符在其子表达式中有多种类型引起的,即: -

cond ? expression of type 1 : expression of type 2

这两个表达式需要评估为相同的类型,这对您没有多大帮助。

为了解决你的问题,我可以想到两个解决方案,这两个解决方案都有点讨厌。

  1. 使用VARARGS / variadic函数

    使用'...'参数定义函数,并使用给定的宏将参数存储在某处,并将目标函数定义为接受va_list。你确实失去了类型安全,编译器检查的每一点,并且需要函数的额外元数据以及重写目标函数以使用va_list。

  2. 删除汇编程序并破解堆栈

  3. 告诉你它很讨厌。给定一个调用函数: -

    void FuncToCall (type1 arg1, type2 arg2);
    

    创建一个函数: -

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

    将堆栈上的参数复制到动态分配的内存块中。然后,当您想要调用该函数时: -

    void CallDelayedFunction (void *params); 
    

    使用指针返回对FuncToCallDelayed的调用。然后将参数压入堆栈,并调用该函数。参数和函数指针位于params参数中。

    此方法确实将您绑定到特定的处理器类型,但至少在参数列表上保留某种形式的类型检查。

    <强>更新

    这是为Win7上运行的Visual Studio 2012,IA32构建的方法2的一个版本: -

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

答案 1 :(得分:3)

您的尝试失败,因为?:运算符的真假结果操作数必须是兼容类型。

我最初建议你创建一个函数调用包装器宏来扩展每个可能组合的参数并不是一个真正可行的解决方案,因为你实际上只想支持两种类型和两个参数。

我突然想到您可以使用swapcontext()setcontext()来推迟通话。基本上,不是将参数存储到数据结构中,而是从print函数返回以解压缩存储参数的未来调用,而是使用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_);

我编写了一个玩具示例,您可以在行动here中看到它。

答案 2 :(得分:2)

使用foreign function call library,它会为您处理所有蹩脚的平台特定细节。例如,以下是如何将函数调用推迟到采用intvoid*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

答案 3 :(得分:1)

您可以使用以下内容:

#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

(在linux上用gcc和clang测试过)

答案 4 :(得分:0)

我建议用以下方法解决这个问题。首先,让我们在函数调用期间摆脱参数类型检查:

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

这种方法的主要缺点是必须为调度员实施大量案例。当然,只有在所有这些案例都是先验地定义的情况下,它才会起作用。

最后,我应该说这是非常不安全的实施。它很容易成为奇怪和危险错误的来源。

答案 5 :(得分:-1)

为什么不使用g_timeout_add_seconds()

使用默认优先级G_PRIORITY_DEFAULT以固定间隔设置要调用的函数。重复调用该函数,直到它返回FALSE,此时超时自动销毁,并且不会再次调用该函数。