Create function call dynamically in C++

2019-04-08 23:39发布

Hello people I hope you an help me out with this problem:

I am currently implementing an interpreter for a scripting language. The language needs a native call interface to C functions, like java has JNI. My problem is, that i want to call the original C functions without writing a wrapper function, which converts the call stack of my scripting language into the C call stack. This means, that I need a way, to generate argument lists of C functions at runtime. Example:

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

The interpreter should be able to call the functions, with only knowing the function prototypes of my scripting language: native void a(int a, int b); and native void b(double a, int b, double c);

Is there any way to generate a C function call stack in C++, or do I have to use assembler for this task. Assembler is a problem, because the interpreter should run on almost any platform.

Edit: The solution is to use libffi, a library, which handles the call stack creation for many different platforms and operating systems. libffi is also used by some prominent language implementations like cpython and openjdk.

Edit: @MatsPetersson Somewhere in my code I have a method like:

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

Edit: Thanks for all your help! I will stay with libffi, and test it on all required platforms.

3条回答
Emotional °昔
2楼-- · 2019-04-08 23:57

In pure standard C++ (or C; see n1570 or n3337 or some newer standard specification, a document written in English), the set of functions is fixed -so cannot change-, and given by the union of all your translation units (and by those from the standard C or C++ library). And in pure standard C++ or C, a function pointer is allowed only to point to some pre-existing function (otherwise it is undefined behavior), when you use it for indirect calls. All functions are, in standard C++ (or C), known at "compile-time", and practically declared in some translation unit (and often implemented in another one, or in some external library).

BTW, when coding an interpreter (for some scripting language), you don't need to grow the set of your (C or C++) functions. You just need to have (generic) interpreting functions coded in C or C++ dealing with some representation of the interpreted scripting code (which, from the point of view of your C++ or C program, is some data), perhaps an AST or some bytecode. For example, a Unix shell, or a Lua or Guile interpreter, don't create C or C++ functions. You could embed Lua or Guile in your program.

However, you might be interested in generating or creating new (C or C++) functions at runtime, for example when compiling your scripting code into C (a common practice) or in machine code. This is not possible in pure standard C or C++, but is practically possible on many implementations, with help from the operating system (at least to grow or add code segments, i.e. new machine code, in your virtual address space).

(notice that any mechanism able to create functions at runtime is outside of the C or C++ standard, and would return function pointers to new machine code)

See also this answer (to a very related question, for C; but you could adapt it for C++), detailing how that is practically possible (notably on Linux).

BTW, libffi is by itself not a way of creating new (C, C++, or machine code) functions, but of calling existing functions of an arbitrary signature with arbitrary arguments.

This means, that I need a way, to generate argument lists of C functions at runtime.

The libffi is only doing that. It knows your ABI (and is partly coded in assembler).

Notice that if your set of functions is fixed (so finite), their signatures are also in a finite set, then you don't really need libffi (because you could special case all your signatures, so your signatures are not arbitrary), even if it could be convenient.

Once you are adding new functions at runtime of arbitrary signatures, libffi or an equivalent mechanism is absolutely needed (because even the set of called signatures could grow).

查看更多
家丑人穷心不美
3楼-- · 2019-04-09 00:01

Yes we can. No FFI library needed, no restriction to C calls, only pure 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;
}

Test drive:

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

Exercise for the reader: implement registerNativeFunction in three easy steps.

  1. Create an abstract base class with a pure call method that accepts a list of boost::any, call it AbstractFunction
  2. Create a variadic class template that inherits AbstractFunction and adds a pointer to a concrete-type function (or std::function). Implement call in terms of that function.
  3. Create an map<string, AbstractFunction*> (use smart pointers actually).

Drawback: totally cannot call variadic C-style functions (e.g. printf and friends) with this method. There is also no support for implicit argument conversions. If you pass an int to a function that requires a double, it will throw an exception (which is slightly better than a core dump you can get with a dynamic solution). It is possible to partially solve this for a finite fixed set of conversions by specializing any_ref_cast.

查看更多
Lonely孤独者°
4楼-- · 2019-04-09 00:04

The way to do this is to use pointers to functions:

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

The problem you will face is finding the address of the function to store in the pointer is system dependent.

On Windoze, you will probably be loading a DLL, finding the address of the function by name within the DLL, then store that point in native to call the function.

查看更多
登录 后发表回答