【问题标题】:how to postpone a function call in c如何在c中推迟函数调用
【发布时间】:2013-08-06 14:00:01
【问题描述】:

我正在尝试推迟函数调用(使用函数包装器),方法是将其参数保存在 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) 真正的问题: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++;
}

【问题讨论】:

  • function 是如何声明的?如果它取决于参数,你如何决定调用哪个函数?
  • 看起来 libffi 接近你想要的,至少对于动态调度。它并不完全有助于延迟部分,但这可能特定于您所针对的平台。向我们提供有关您的平台(Linux、Windows、Mac OSX 等)的一些信息,我们可以为您提供更多帮助。
  • @jxh, function 就像 printf,它可以接受不同类型的参数。我需要的只是将正确转换(之前存储的)参数传递给函数。
  • @gg.kaspersky: printf() 从格式说明符中获取有关类型的提示。为什么不将argt 传递给function()
  • @RichardJ.RossIII,我使用的是 Linux 的自定义版本,安装了有限的库。我依靠纯c。此外,我不需要基于超时的延迟,它不必是异步的。

标签: c


【解决方案1】:

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

cond ? expression of type 1 : expression of type 2

这两个表达式需要计算为相同的类型,这对您没有太大帮助。

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

  1. 使用 VARARGS / 可变参数函数

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

  2. 放入汇编器并破解堆栈

告诉过你这很讨厌。给定一个要调用的函数:-

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

** 另一个更新 **

正如我在 cmets 中提到的,还有第三种选择,那就是使用消息传递系统。与其调用函数,不如创建一个如下形式的消息对象:-

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

【讨论】:

  • 我实际上是从 va_list 存储变量。如果我可以复制一个 va_list 以供以后使用,那就太好了:(。我考虑了第二个变体,但我仍然认为它应该与宏一起使用。
  • @gg.kaspersky 但是,您可以复制 va_list...va_copy
  • @gg.kaspersky 陷阱。在这种情况下,您要么必须重新发明轮子,要么使用 libffi 之类的库。这确实是您的最佳选择。
  • 这个解决方案虽然令人印象深刻,但仍然仅限于 x86 架构并且有些脆弱。
  • @Skizz,好的,但是我对像这样的技术先进的低级解决方案的担忧是,它总是基于对目标架构的一组假设,我个人更喜欢这样的解决方案利用编译器来协调参数堆叠,但我看不出在这种情况下怎么可能
【解决方案2】:

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

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

我想到您可以使用swapcontext()setcontext() 来推迟通话。基本上,不是将参数存储到数据结构中,并从您的 print 函数返回以用于解包隐藏的参数的未来调用,而是使用 swapcontext() 跳转到您想要接管的函数,直到您的 print可以恢复。如果只需要来回翻转,则只需要两个上下文。

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 中看到它。

【讨论】:

  • 感谢您的回答,我很乐意将 argt 传递给函数,但函数会进行一些处理并最终调用 vprintf。另外,我有 5 个以上的参数,所以 FUNCTION 宏不是解决方案...
  • @gg.kaspersky:我很想知道你对我的新建议有何看法。
  • 完成后我会通知您。
【解决方案3】:

使用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

【讨论】:

    【解决方案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
    

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

    【讨论】:

      【解决方案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...
          }
      }
      

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

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

      【讨论】:

      • 如果你想让ftype 接受任意数量的参数,标准方法是让它接受... 而不是不指定任何参数。
      • 您需要在每个被调用函数中使用va_list 来获取参数。在这里,我假设我们无法更改这些功能代码。在这种情况下,空括号可以工作,... 不行。
      【解决方案6】:

      你为什么不用g_timeout_add_seconds()

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

      【讨论】:

      • -1,非标准,仅在使用 GTK 时有效(OP 没有说明他正在使用)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多