18

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

void *args[]
int argt[]

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

6 回答 6

9

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 };
于 2013-08-06T14:29:59.893 回答
3

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

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

我想到你可以使用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);
}

Wherealt_ctx_被初始化为一个集合函数,该函数接管执行直到打印可以恢复。当打印可以恢复时,打印上下文将通过以下方式恢复:

    setcontext(&es.main_ctx_);

我编写了一个玩具示例,你可以在这里看到它的实际效果。

于 2013-08-06T14:40:54.237 回答
2

使用外部函数调用库,它会为您处理所有蹩脚的平台特定细节。例如,您可以将函数调用推迟到接受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
于 2013-08-19T19:52:13.683 回答
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 测试过)

于 2013-08-19T19:21:15.403 回答
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...
    }
}

这种方法的主要缺点是需要为调度程序实现很多案例。当然,只有当所有这些情况都被先验定义时,它才会起作用。

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

于 2013-08-06T16:53:09.573 回答
-1

你为什么不使用g_timeout_add_seconds()

设置以默认优先级 G_PRIORITY_DEFAULT 定期调用的函数。重复调用该函数,直到返回FALSE,此时超时自动销毁,该函数将不再被调用。

于 2013-08-06T14:06:25.837 回答