Dynamically create a function call in C ++

I welcome you, I hope you will help me deal with this problem:

I am currently using an interpreter for a scripting language. The language needs its own calling interface for C functions, for example java has a JNI. My problem is that I want to call the original C functions without writing a wrapper function that converts the call stack of my scripting language to 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 functions, knowing only the prototypes of the functions of my scripting language: native void a(int a, int b); and native void b(double a, int b, double c);

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

Edit: The solution is to use the libffi library, which handles the creation of the call stack for different platforms and operating systems. libffi is also used by some well-known language implementations such as 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 your help! I will stay with libffi and test it on all necessary platforms.

+5
source share
3 answers

Yes we can. There is no FFI library, no restrictions on 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: complete registerNativeFunction in three easy steps.

  • Create an abstract base class with a clean call method that takes a boost::any list, name it AbstractFunction
  • Create a template for a variational class that inherits AbstractFunction and adds a pointer to a function of a particular type (or std::function ). Make a call in terms of this function.
  • Create map<string, AbstractFunction*> (actually use smart pointers).

Disadvantage: cannot fully invoke C-style function variables (e.g. printf and friends) using this method. There is also no support for implicit argument conversions. If you pass an int function that requires a double , it throws an exception (which is slightly better than a kernel dump that you can get with a dynamic solution). This problem can be partially solved for a finite fixed set of transformations, specializing in any_ref_cast .

+3
source

The way to do this is to use function pointers:

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

The problem you will encounter is finding the address of the function to store in the pointer depends on the system.

In Windoze, you probably load the DLL by finding the address of the function by name in the DLL, and then save that point in native to call the function.

0
source

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

BTW, when coding the interpreter (for some scripting languages) you do not need to increase the set of your functions (C or C ++). You just need to have (general) interpretation functions encoded in C or C ++ regarding some representation of the interpreted script code (which from the point of view of your C ++ or C program is some data), possibly AST or bytecode . For example, a Unix or Lua or Guile shell, do not create C or C ++ functions. You can include Lua or Guile in your program.

However, you may be interested in creating or creating new (C or C ++) functions at run time, for example, when compiling script code in C (a common practice ) or in machine code. This is not possible in pure standard C or C ++, but it is practically possible in many implementations using the operating system (at least to grow or add code segments , that is, a new computer , in the virtual address space ).

(note that any mechanism that can create functions at runtime goes beyond the standard C or C ++ and returns pointers to new machine code)

See also this answer (to a very close question, for C, but you can adapt it for C ++), describing in detail how this is practically possible (especially on Linux).

BTW, libffi itself is not a way to create new (C, C ++ or machine code) functions, but a call to 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.

libffi does just that. He knows your ABI (and partially encoded in assembler).

Please note that if your set of functions is fixed (so finite), their signatures are also in a finite set, then you really do not need libffi (because you can use all your signatures in a special case, so your signatures are not arbitrary), even if it may be convenient.

As soon as you add new functions during the execution of arbitrary signatures, libffi or an equivalent mechanism is absolutely necessary (because even a set of called signatures can grow).

0
source

Source: https://habr.com/ru/post/1205553/


All Articles