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

时间:2022-11-07 17:01:59

Suppose there's a list of arguments stored somehow, in a array for example.

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

Given a function pointer, how could I make a call to it passing the stored list of arguments?

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

I'm not trying to pass the array as an argument ok. You got it, ok? I want to pass each of its elements as an argument. An array is just to illustrate, I could be storing the arguments in some tuple structure. Also, look that I have at hand a function pointer and may have a signature in string format. I'm not trying to just define a function that is able to deal with a variadic list.

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

The only way I see how to do that is by employing assembly (by __asm push et al.) or this:

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

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

I don't like this approach too much...

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

Is there another portable and shorter form?

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

Several script languages are able to call C functions.

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

How script languages like Python or Ruby do that? How they implement it in a portable way? Does they just use assembly for several platforms or the above in the end?

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

Look that I'm really not asking about details of parameter marshaling and other stuff from script languages to C, I'm interested only in how, in the end, internally, the call to the C function by the script language is built.

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

EDIT

I'll keep the question's title but I think a better way for asking it is:

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

How to call a C function with its pointer and signature available only at runtime?

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

UPDATE

From Foreign Interface for PLT Scheme:

来自外国接口的PLT计划:

A call-out is a normal function call. In a dynamic setting, we create a “call-interface” object which specifies (binary) input/output types; this object can be used with an arbitrary function pointer and an array of input values to perform a callout to the function and retrieve its result. Doing this requires manipulating the stack and knowing how a function is called, these are details that libffi deals with.

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

Thanks @AnttiHaapala for searching, finding and pointing libffi. It's what I was looking for, it's being used by a bunch of script languages, it's a portable library, implemented across several architectures and compilers.

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

4 个解决方案

#1


5  

I am the author of libffi. It will do what you are asking.

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

#2


12  

You asked what is the portable way to call any function pointer with given number of arguments. The correct answer is that there is no such way.

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

For example python is able to call C functions through the ctypes module, but this is portable only for as long as you know the exact prototype and calling conventions. In C the easiest way to achieve the same is to know the prototype of the function pointer at compile time.

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

Update

For python / ctypes example, on each platform that has the ctypes module enabled, python knows how to write the calling stack for a given set of arguments. On Windows for example, python knows of 2 standard calling conventions - cdecl with C order of parameters on stack, and stdcall with "pascal style ordering". On Linux it does need to worry about whether to call 32 or 64 bit shared objects, and so forth. If python is compiled to another platform, the ctypes needs changes as well; the C code in ctypes module is not, as such, portable.

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

Update 2

For Python the magic is in here: ctypes source code. Notably it seems to link http://sourceware.org/libffi/ which might be just what you needed.

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

#3


5  

@AnttiHaapala pointed out libffi. Here's some information about it:

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

What is libffi?

Some programs may not know at the time of compilation what arguments are to be passed to a function. For instance, an interpreter may be told at run-time about the number and types of arguments used to call a given function. ‘libffi’ can be used in such programs to provide a bridge from the interpreter program to compiled code.

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

The ‘libffi’ library provides a portable, high level programming interface to various calling conventions. This allows a programmer to call any function specified by a call interface description at run time.

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

FFI stands for Foreign Function Interface. A foreign function interface is the popular name for the interface that allows code written in one language to call code written in another language. The ‘libffi’ library really only provides the lowest, machine dependent layer of a fully featured foreign function interface. A layer must exist above ‘libffi’ that handles type conversions for values passed between the two languages.

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

‘libffi’ assumes that you have a pointer to the function you wish to call and that you know the number and types of arguments to pass it, as well as the return type of the function.

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


Historic background

libffi, originally developed by Anthony Green (SO user: anthony-green), was inspired by the Gencall library from Silicon Graphics. Gencall was developed by Gianni Mariani, then employed by SGI, for the purpose of allowing calls to functions by address and creating a call frame for the particular calling convention. Anthony Green refined the idea and extended it to other architectures and calling conventions and open sourcing libffi.

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


Calling pow with libffi

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

I think I can assert libffi is a portable way to do what I asked, contrary to Antti Haapala's assertion that there isn't such a way. If we can't call libffi a portable technology, given how far it's ported/implemented across compilers and architectures, and which interface complies with C standard, we too can't call C, or anything, portable.

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

Information and history extracted from:

提取的信息和历史:

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

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

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

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

#4


2  

For safety you should unpack the variables before they are sent. Using assembler to hack the parameter stack might not be portable between compilers. Calling conventions might vary.

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

I can't speak for Ruby, but I have written quite a few programs using the C interfaces to Perl and Python. Perl and Python variables are not directly comparible with C variables, they have many more features. For example, a Perl scalar might have dual string and numeric values, only one of which is valid at any one time.

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

Conversion between Perl/Python variables and C is done using pack and unpack (in the struct module in Python). At the C interface you have to call specific APIs to do the conversion, depending on type. So, it is not just a straight pointer transfer, and it certainly does not involve assembler.

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

#1


5  

I am the author of libffi. It will do what you are asking.

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

#2


12  

You asked what is the portable way to call any function pointer with given number of arguments. The correct answer is that there is no such way.

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

For example python is able to call C functions through the ctypes module, but this is portable only for as long as you know the exact prototype and calling conventions. In C the easiest way to achieve the same is to know the prototype of the function pointer at compile time.

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

Update

For python / ctypes example, on each platform that has the ctypes module enabled, python knows how to write the calling stack for a given set of arguments. On Windows for example, python knows of 2 standard calling conventions - cdecl with C order of parameters on stack, and stdcall with "pascal style ordering". On Linux it does need to worry about whether to call 32 or 64 bit shared objects, and so forth. If python is compiled to another platform, the ctypes needs changes as well; the C code in ctypes module is not, as such, portable.

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

Update 2

For Python the magic is in here: ctypes source code. Notably it seems to link http://sourceware.org/libffi/ which might be just what you needed.

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

#3


5  

@AnttiHaapala pointed out libffi. Here's some information about it:

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

What is libffi?

Some programs may not know at the time of compilation what arguments are to be passed to a function. For instance, an interpreter may be told at run-time about the number and types of arguments used to call a given function. ‘libffi’ can be used in such programs to provide a bridge from the interpreter program to compiled code.

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

The ‘libffi’ library provides a portable, high level programming interface to various calling conventions. This allows a programmer to call any function specified by a call interface description at run time.

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

FFI stands for Foreign Function Interface. A foreign function interface is the popular name for the interface that allows code written in one language to call code written in another language. The ‘libffi’ library really only provides the lowest, machine dependent layer of a fully featured foreign function interface. A layer must exist above ‘libffi’ that handles type conversions for values passed between the two languages.

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

‘libffi’ assumes that you have a pointer to the function you wish to call and that you know the number and types of arguments to pass it, as well as the return type of the function.

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


Historic background

libffi, originally developed by Anthony Green (SO user: anthony-green), was inspired by the Gencall library from Silicon Graphics. Gencall was developed by Gianni Mariani, then employed by SGI, for the purpose of allowing calls to functions by address and creating a call frame for the particular calling convention. Anthony Green refined the idea and extended it to other architectures and calling conventions and open sourcing libffi.

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


Calling pow with libffi

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

I think I can assert libffi is a portable way to do what I asked, contrary to Antti Haapala's assertion that there isn't such a way. If we can't call libffi a portable technology, given how far it's ported/implemented across compilers and architectures, and which interface complies with C standard, we too can't call C, or anything, portable.

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

Information and history extracted from:

提取的信息和历史:

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

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

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

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

#4


2  

For safety you should unpack the variables before they are sent. Using assembler to hack the parameter stack might not be portable between compilers. Calling conventions might vary.

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

I can't speak for Ruby, but I have written quite a few programs using the C interfaces to Perl and Python. Perl and Python variables are not directly comparible with C variables, they have many more features. For example, a Perl scalar might have dual string and numeric values, only one of which is valid at any one time.

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

Conversion between Perl/Python variables and C is done using pack and unpack (in the struct module in Python). At the C interface you have to call specific APIs to do the conversion, depending on type. So, it is not just a straight pointer transfer, and it certainly does not involve assembler.

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