在C ++中动态创建函数调用

时间:2014-10-26 16:37:12

标签: c++ c function runtime callstack

大家好,我希望你帮我解决这个问题:

我目前正在为脚本语言实现解释器。该语言需要一个C函数的本机调用接口,比如java有JNI。我的问题是,我想调用原始的C函数而不编写包装函数,它将我的脚本语言的调用堆栈转换为C调用堆栈。这意味着,我需要一种方法,在运行时生成C函数的参数列表。例如:

void a(int a, int b) {
    printf("function a called %d", a + b);
}

void b(double a, int b, double c) {
    printf("function b called %f", a * b + c);
}

interpreter.registerNativeFunction("a", a);
interpreter.registerNativeFunction("b", b);

解释器应该能够调用函数,只知道我的脚本语言的函数原型:native void a(int a, int b);native void b(double a, int b, double c);

有没有办法在C ++中生成C函数调用堆栈,或者我是否必须使用汇编程序完成此任务。汇编程序是一个问题,因为解释器几乎可以在任何平台上运行。

编辑: 解决方案是使用libffi,这是一个库,用于处理许多不同平台和操作系统的调用堆栈创建。 libffi也被一些着名的语言实现使用,如cpython和openjdk。

编辑: @MatsPetersson我的代码中有一个方法,我有一个方法:

void CInterpreter::CallNativeFunction(string name, vector<IValue> arguments, IReturnReference ret) {
    // Call here correct native C function.
    // this.nativeFunctions is a map which contains the function pointers.
}

修改: 感谢你的帮助!我将继续使用libffi,并在所有必需的平台上进行测试。

4 个答案:

答案 0 :(得分:4)

是的,我们可以。不需要FFI库,对C调用没有限制,只有纯C ++ 11。

#include <iostream>
#include <list>
#include <iostream>
#include <boost/any.hpp>

template <typename T>
auto fetch_back(T& t) -> typename std::remove_reference<decltype(t.back())>::type
{
    typename std::remove_reference<decltype(t.back())>::type ret = t.back();
    t.pop_back();
    return ret;
}

template <typename X>
struct any_ref_cast
{
    X do_cast(boost::any y)
    {
        return boost::any_cast<X>(y);
    }
};

template <typename X>
struct any_ref_cast<X&>
{
    X& do_cast(boost::any y)
    {
        std::reference_wrapper<X> ref = boost::any_cast<std::reference_wrapper<X>>(y);
        return ref.get();
    }
};

template <typename X>
struct any_ref_cast<const X&>
{
    const X& do_cast(boost::any y)
    {
        std::reference_wrapper<const X> ref = boost::any_cast<std::reference_wrapper<const X>>(y);
        return ref.get();
    }
};

template <typename Ret, typename...Arg>
Ret call (Ret (*func)(Arg...), std::list<boost::any> args)
{
    if (sizeof...(Arg) != args.size())
        throw "Argument number mismatch!";

    return func(any_ref_cast<Arg>().do_cast(fetch_back(args))...);
}

int foo(int x, double y, const std::string& z, std::string& w)
{
    std::cout << "foo called : " << x << " " << y << " " << z << " " << w << std::endl;
    return 42;
}

试驾:

int main ()
{
    std::list<boost::any> args;
    args.push_back(1);
    args.push_back(4.56);
    const std::string yyy("abc");
    std::string zzz("123");
    args.push_back(std::cref(yyy));
    args.push_back(std::ref(zzz));
    call(foo, args);
}

为读者练习:通过三个简单步骤实施registerNativeFunction

  1. 使用纯call方法创建一个抽象基类,该方法接受boost::any列表,称之为AbstractFunction
  2. 创建一个继承AbstractFunction的可变参数类模板,并添加指向具体类型函数(或std::function)的指针。根据该功能实施call
  3. 创建map<string, AbstractFunction*>(实际使用智能指针)。
  4. 缺点:完全不能用这种方法调用可变参数C风格的函数(例如printf和朋友)。隐式参数转换也不支持。如果将int传递给需要double的函数,它将抛出异常(这比使用动态解决方案可以获得的核心转储略好)。通过专门化any_ref_cast可以部分地解决这个问题。[/ 1}}。

答案 1 :(得分:0)

执行此操作的方法是使用指向函数的指针:

void (*native)(int a, int b) ;

您将面临的问题是找到要存储在指针中的函数的地址取决于系统。

在Windoze上,您可能正在加载DLL,在DLL中按名称查找函数的地址,然后将该点存储为native以调用该函数。

答案 2 :(得分:0)

在纯标准C ++ (或C;参见n1570n3337或一些较新的标准规范,用英文编写的文档),功能集已修复 - 也无法更改 - 并由所有translation units(以及来自标准C或C ++库的那些)的并集给​​出。在纯标准C ++或C中,function pointer仅允许指向某些预先存在的函数(否则为undefined behavior),当您将其用于间接调用时。所有函数在标准C ++(或C)中,在“编译时”已知,并且在某些翻译单元中实际上声明(并且通常在另一个中实现,或者在一些外部库中。)

BTW,在编写解释器(对于某些脚本语言)时,您不需要增加(C或C ++)函数的集合。您只需要用C或C ++编写的(通用)解释函数来处理解释的脚本代码的某些表示(从C ++或C程序的角度来看,它是一些数据),也许是{{3}或某些AST。例如,Unix shell或bytecodeLua解释器不会创建C或C ++函数。你可以在你的程序中嵌入Lua或Guile。

但是,您可能对生成在运行时创建新的(C或C ++)函数感兴趣,例如在编译脚本时代码到C(一个Guile实践)或机器代码中。这在纯标准C或C ++中是不可能的,但在common的帮助下,在许多实现上实际上是可能的(至少增长或添加operating system,即{ {3}}代码,位于code segments)。

(请注意,任何能够在运行时创建函数的机制都是在C或C ++标准之外,并且会返回指向新机器代码的函数指针)

另请参阅new machine回答(对于一个非常相关的问题,对于C;但您可以将其用于C ++),详细说明实际可行的方式(特别是在Linux上)。

BTW,virtual address space本身 创建新(C,C ++或机器代码)函数的方式,但使用任意参数调用任意签名的现有函数

  

这意味着,我需要一种方法,在运行时生成C函数的参数列表。

libffi只是这样做。它知道你的this(部分用汇编语言编码)。

请注意,如果你的函数集是固定的(如此有限),它们的签名也是有限集,那么你真的不需要libffi(因为你可以特殊情况下所有的签名,所以你的签名任意),即使它很方便。

任意签名的运行时添加新函数后,绝对需要libffi或等效机制(因为即使是一组被调用签名也会增长)。

答案 3 :(得分:0)

有很多方法可以做到这一点。

  1. 使用 boost(见第一个答案)
  2. 使用 std::bind。类似于 boost 但更简单
  3. 使用 C 函数指针。

示例

#define DYNAMIC(p,arg,n) {\
if(0==n) ((void (*)())p)();\
else if(1==n) ((void (*)(int))p)(arg[0]);\
else if(2==n) ((void (*)(int, int))p)(arg[0], arg[1]);\
else if(3==n) ((void (*)(int, int, int))p)(arg[0], arg[1], arg[2]);\
}

void fun0()
{
    printf("no arg \n");
}

void fun1(int a)
{
    printf("arg %x\n", a);
}

void fun2(int a, const char *b)
{
    printf("arg %x %s \n", a,b);
}

void fun3(int a,const char *b, int c)
{
    printf("arg %x %s %x\n", a, b, c);
}

    int a = 0xabcd;
    const char* b = "test dynamic function";
    int c = 0xcdef;

    int d[] = { 1,(int)b,c) };
    DYNAMIC(fun0, d, 0);
    DYNAMIC(fun1, d, 1);
    DYNAMIC(fun2, d, 2);
    DYNAMIC(fun3, d, 3);
  1. 甚至可以更灵活地使用一些汇编代码:
void call(void* p, uint32_t arg[], int n)
{
    for (int i = n-1; i >-1; --i)
    {
        uint32_t u = arg[i];
        __asm push u
    }
    __asm call p
    int n2 = n * 4;
    __asm add esp,n2
}
  
 int a = 0xabcd;
 const char* b = "test dynamic function";
 int c = 0xcdef; 
int d[] = { 1,(int)b,c) };
call(fun3, d, 3);