大家好,我希望你帮我解决这个问题:
我目前正在为脚本语言实现解释器。该语言需要一个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,并在所有必需的平台上进行测试。
答案 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
。
call
方法创建一个抽象基类,该方法接受boost::any
列表,称之为AbstractFunction
AbstractFunction
的可变参数类模板,并添加指向具体类型函数(或std::function
)的指针。根据该功能实施call
。map<string, AbstractFunction*>
(实际使用智能指针)。缺点:完全不能用这种方法调用可变参数C风格的函数(例如printf和朋友)。隐式参数转换也不支持。如果将int
传递给需要double
的函数,它将抛出异常(这比使用动态解决方案可以获得的核心转储略好)。通过专门化any_ref_cast
可以部分地解决这个问题。[/ 1}}。
答案 1 :(得分:0)
执行此操作的方法是使用指向函数的指针:
void (*native)(int a, int b) ;
您将面临的问题是找到要存储在指针中的函数的地址取决于系统。
在Windoze上,您可能正在加载DLL,在DLL中按名称查找函数的地址,然后将该点存储为native以调用该函数。
答案 2 :(得分:0)
在纯标准C ++ (或C;参见n1570或n3337或一些较新的标准规范,用英文编写的文档),功能集已修复 - 也无法更改 - 并由所有translation units(以及来自标准C或C ++库的那些)的并集给出。在纯标准C ++或C中,function pointer仅允许指向某些预先存在的函数(否则为undefined behavior),当您将其用于间接调用时。所有函数在标准C ++(或C)中,在“编译时”已知,并且在某些翻译单元中实际上声明(并且通常在另一个中实现,或者在一些外部库中。)
BTW,在编写解释器(对于某些脚本语言)时,您不需要增加(C或C ++)函数的集合。您只需要用C或C ++编写的(通用)解释函数来处理解释的脚本代码的某些表示(从C ++或C程序的角度来看,它是一些数据),也许是{{3}或某些AST。例如,Unix shell或bytecode或Lua解释器不会创建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)
有很多方法可以做到这一点。
示例
#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);
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);