【问题标题】:How to call native functions from a VM?如何从 VM 调用本机函数?
【发布时间】:2012-12-02 17:39:47
【问题描述】:

本地是指用 C++ 或 C 编写的

我正在开发一种基于 Java 的编程语言,因为它有一个 VM 和一个字节码编译器语言。

实现语言的特性,例如for 循环、变量、算术等等,对我来说不是问题;但是,像 Java 这样执行本机函数就可以了。

我需要本机函数,以便用我的语言编写的程序可以创建窗口、与硬件和操作系统交互,以及做任何不简单的数学运算。

我听说过JNI,它确实是我想要的东西,但是,我不知道如何实现这样的东西。

由于我的 VM 是用 C++ 实现的,我知道我可以在编译时拥有我的本机函数的 #include hpp 文件,然后它可以动态加载 dllso 的文件,但是,这似乎并不是一个好的解决方案,因为每次您希望它能够执行另一个本机函数时都必须重新编译 VM。

问题归结为:C++ 程序(VM)如何动态地(在运行时,按照字节码的指示,更准确地说)加载带有 C++ 函数的库,然后在没有预先声明的情况下执行这些函数在某个头文件中?

【问题讨论】:

  • 能否提供一些您想要的代码示例?
  • 在 Windows 上,您使用 LoadLibrary() 和 GetProcAddress()。 Linux 具有不同名称的等效功能。这些并不能帮助您了解参数和返回值的数量和类型。
  • @BhavikAmbani 当然,我几个小时后回家。
  • @brianbeuning - 我确实知道返回类型和参数的类型,因为该函数是用我的语言创建的,它具有 C/C++ 类型的等价物。是否可以在运行时声明一个函数?

标签: java c++ java-native-interface shared-libraries


【解决方案1】:

看看libffi。它提供了在给定函数地址和调用签名的情况下调用任何函数的方法。

您如何确定该签名应该是什么取决于您的上下文。您可以根据参数类型推断出广泛的调用。 JNA 从显式 Java 接口、方法声明或动态调用参数推断本机调用签名。

超越简单的函数调用来处理构造函数、内存管理和对象方法分派更加复杂,但仍然基于相同的基本原则。

【讨论】:

  • 这绝对是我需要的东西。但是,我不确定我应该如何在一些动态加载的共享对象 (dlopen()) 中获取指向函数的指针。我知道函数本身的名称、参数数量、参数类型和返回类型,因为这是用我的语言描述的,但我不确定如何获取指向本机函数的指针。如果我知道本机函数将被调用X,我可以获得指向它的指针吗?
  • dlsym() 查找给定名称的符号。通常导出的符号要么是您可以调用的函数,要么是用于数据访问的全局变量的地址。
  • 现在我有了dlopen()dlsym(),LibFFI 的实际需求在哪里?
  • 联邦快递小哥说:“我已经有了姓名和地址,我还需要什么?”。如果您的语言已经知道如何将参数放在堆栈和/或寄存器中以准备函数调用,那么您不需要需要 libffi。但既然你说你的语言是基于 Java/字节码而不是 C/C++ 并且你建议需要动态地做事情,我猜它没有。
  • 嗯,my 语言基于 Java,但 VM 是用 C++ 实现的。如果dlsym() 给我一个指向我想要执行的函数的指针,C++ 编译器应该能够处理它,对吧?或者,编译器是否无法处理它,因为它对编译时正在执行的特定函数一无所知?我希望我的虚拟机知道函数需要哪些类型的参数,实际上可以修复类似的问题。
【解决方案2】:

我没有看到讨论过的一个主题是如何将新语言 VM 函数调用中的参数传递给 C++ 堆栈,以及如何将 C++ 函数的返回值传递回你的 VM。

假设您想让 pow(3) 函数在您的新语言中可用。 提醒一下,pow() 签名是

double pow ( double base, double power )

最简单的方法是这样的

void
language::pow( VM * pVM )
{
    double arg2 = pVM->PopDouble();
    double arg1 = pVM->PopDouble();
    double result = pow( arg1, arg2 );
    pVM->PushDouble( result );
}

但这听起来不像你所追求的。合并 dlopen() 和 dlsym() 可以得到类似的东西

void
language::pow( VM * pVM )
{
    double arg2 = pVM->PopDouble();
    double arg1 = pVM->PopDouble();
    void *handle = dlopen("libm", RTLD_LAZY);
    if (!handle) { /*...return; ...*/ }
    typedef double (* pfPow ) ( double, double );
    pfPow pPow = (pfPow) dlsym(handle, "pow");
    if (!pPow) { /*...return; ...*/ }
    double result = (* pPow )( arg1, arg2 );
    pVM->PushDouble( result );
}

但这更糟。对于您希望您的语言能够访问的每个 C++ 函数,您仍然需要一个存根函数。

听起来你希望你的语言有类似的东西

double result = eval_double( "libm", "pow", arg1, arg2 );

我不知道如何在 C++ 中实现它。 Varags 支持获取任何类型的 C++ 参数。但是没有用于推送任意类型的 C++ 参数的 API。

【讨论】:

    猜你喜欢
    • 2014-09-02
    • 2012-08-25
    • 1970-01-01
    • 2020-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-17
    • 1970-01-01
    相关资源
    最近更新 更多