在C中,给定变量参数列表,如何使用它们构建函数调用?

假设有一个以某种方式存储的参数列表,例如,在数组中。

给定一个函数指针 ,如何调用它来传递存储的参数列表?

我不是试图将数组作为参数传递好。 你明白了,好吗? 我想将每个元素作为参数传递。 数组只是为了说明,我可以将参数存储在一些元组结构中。 另外,看看我手头有一个函数指针,并且可能有字符串格式的签名 。 我不是只想定义一个能够处理可变列表的函数。

我看到如何做到这一点的唯一方法是使用汇编(通过__asm push et al。)或者:

 void (*f)(...); int main() { f = ; int args[];  int num_args = ; switch(num_args) { case 0: f(); break; case 1: f(args[0]); break; case 2: f(args[0], args[1]); break; /* etc */ } return 0; } 

我不太喜欢这种做法……

还有另一种便携式和更短的forms吗?

几种脚本语言可以调用C函数。

Python或Ruby等脚本语言如何做到这一点? 他们如何以便携方式实现它? 他们最终只是为几个平台或上面的平台使用组件吗?

看看我真的不是在询问参数编组的细节以及从脚本语言到C的其他内容,我只对内部如何构建脚本语言对C函数的调用感兴趣。

编辑

我会保留问题的标题,但我认为更好的方式是:

如何调用C函数,其指针和签名仅在运行时可用?

UPDATE

来自外国接口的PLT计划 :

呼出是正常的函数调用。 在动态设置中,我们创建一个“调用接口”对象,它指定(二进制)输入/输出类型; 此对象可以与任意函数指针和输入值数组一起使用,以执行函数的调用并检索其结果。 这样做需要操纵堆栈并知道如何调用函数,这些是libffi处理的细节。

感谢@AnttiHaapala搜索,查找和指向libffi 。 这就是我所寻找的,它被一堆脚本语言所使用,它是一个可移植的库,在多个架构和编译器中实现。

我是libffi的作者。 它会做你要求的。

您询问使用给定数量的参数调用任何函数指针的可移植方式是什么。 正确的答案是没有这样的方法

例如,python能够通过ctypes模块调用C函数,但只要您知道确切的原型和调用约定,它就是可移植的。 在C中,实现相同的最简单方法是在编译时知道函数指针的原型。

更新

对于python / ctypes示例,在每个启用了ctypes模块的平台上,python都知道如何为给定的参数集编写调用堆栈。 例如,在Windows上,python知道2个标准调用约定 – 在堆栈上带有C顺序参数的cdecl,以及带有“pascal样式排序”的stdcall。 在Linux上,它确实需要担心是否调用32或64位共享对象,等等。 如果python被编译到另一个平台,那么ctypes也需要改变; ctypes模块中的C代码本身不是可移植的。

更新2

对于Python,魔术在这里: ctypes源代码 。 值得注意的是,似乎链接http://sourceware.org/libffi/可能正是您所需要的。

@AnttiHaapala指出了libffi 。 以下是有关它的一些信息:

什么是libffi?

某些程序在编译时可能不知道要将哪些参数传递给函数。 例如,可以在运行时告诉解释器关于用于调用给定函数的参数的数量和类型。 ‘libffi’可用于此类程序,以提供从解释程序到编译代码的桥梁。

‘libffi’库为各种调用约定提供了可移植的高级编程接口。 这允许程序员在运行时调用由调用接口描述指定的任何函数。

FFI代表外部function接口。 外部函数接口是接口的流行名称,它允许用一种语言编写的代码调用用另一种语言编写的代码。 ‘libffi’库实际上只提供function齐全的外部函数接口的最低机器相关层。 一个层必须存在于’libffi’之上,它处理两种语言之间传递的值的类型转换。

‘libffi’假定您有一个指向要调用的函数的指针,并且您知道要传递它的参数的数量和类型,以及函数的返回类型。


历史背景

libffi最初由Anthony Green(SO用户: anthony-green )开发,受到Silicon Graphics的Gencall库的启发。 Gencall由Gianni Mariani开发,然后由SGI雇用,目的是允许通过地址调用函数并为特定调用约定创建调用框架。 安东尼格林改进了这个想法,并将其扩展到其他架构和调用约定以及开源libffi。


用libffi调用pow

 #include  #include  #include  int main() { ffi_cif call_interface; ffi_type *ret_type; ffi_type *arg_types[2]; /* pow signature */ ret_type = &ffi_type_double; arg_types[0] = &ffi_type_double; arg_types[1] = &ffi_type_double; /* prepare pow function call interface */ if (ffi_prep_cif(&call_interface, FFI_DEFAULT_ABI, 2, ret_type, arg_types) == FFI_OK) { void *arg_values[2]; double x, y, z; /* z stores the return */ z = 0; /* arg_values elements point to actual arguments */ arg_values[0] = &x; arg_values[1] = &y; x = 2; y = 3; /* call pow */ ffi_call(&call_interface, FFI_FN(pow), &z, arg_values); /* 2^3=8 */ printf("%.0f^%.0f=%.0f\n", x, y, z); } return 0; } 

我认为我可以断言libffi是一种可行的方式来做我所要求的,这与Antti Haapala断言没有这样的方式相反。 如果我们不能将libffi称为便携式技术,考虑到它在编译器和体系结构中的移植/实现程度,以及哪个接口符合C标准,我们也无法调用C或其他任何可移植的技术。

提取的信息和历史:

https://github.com/atgreen/libffi/blob/master/doc/libffi.info

http://en.wikipedia.org/wiki/Libffi

为了安全起见,您应该在变量发送之前解压缩变量。 使用汇编程序来破解参数堆栈可能无法在编译器之间移植。 呼叫约定可能会有所不同。

我不能代表Ruby,但我已经使用Perl和Python的C接口编写了很多程序。 Perl和Python变量不能与C变量直接比较,它们具有更多的function。 例如,Perl标量可能具有双字符串和数字值,其中只有一个在任何时候都有效。

Perl / Python变量和C之间的转换是使用packunpack (在Python的struct模块中)。 在C接口,您必须调用特定的API来执行转换,具体取决于类型。 因此,它不仅仅是一个直接指针传输,它肯定不涉及汇编程序。