17

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

给定一个函数指针,我如何通过存储的参数列表调用它?

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

我看到如何做到这一点的唯一方法是使用汇编(由__asm push等人)或这个:

void (*f)(...);

int main()
{
    f = <some function pointer>;
    int args[]; <stored in a array, just to illustrate>
    int num_args = <some value>;

    switch(num_args)
    {
        case 0:
            f();
        break;

        case 1:
            f(args[0]);
        break;

        case 2:
            f(args[0], args[1]);
        break;

        /* etc */
    }

    return 0;
}

我不太喜欢这种方法...

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

几种脚本语言能够调用 C 函数。

Python 或 Ruby 等脚本语言如何做到这一点?他们如何以可移植的方式实现它?他们最终是否只是将程序集用于多个平台或以上平台?

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

编辑

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

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

更新

PLT 方案的外部接口

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

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

4

4 回答 4

12

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

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

更新

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

更新 2

对于 Python,魔法就在这里:ctypes source code。值得注意的是,它似乎链接了http://sourceware.org/libffi/这可能正是您所需要的。

于 2012-09-04T14:43:10.377 回答
7

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

于 2012-09-04T19:54:07.557 回答
6

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

什么是libffi?

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

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

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

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


历史背景

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


使用 libffi 调用 pow

#include <stdio.h>
#include <math.h>
#include <ffi.h>

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

于 2012-09-04T16:12:27.603 回答
2

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

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

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

于 2012-09-04T13:26:03.710 回答