使用 CNI 从 AOT 编译的 Java 应用程序调用本机代码

问题描述 投票:0回答:1

GNU Java 编译器提供了两种从 Java 应用程序调用本机代码的方法。

  1. 首先,有一个由 Sun Microsystems 编写的 JNI 规范,GCJ 遵循此规范。
  2. 其次,还有CNI,由 GCJ 单独实现的Compiled Native Interface,它几乎没有文档记录,但允许用 C++ 实现本机方法,提供更高级别的 API 来在本机代码和 JVM 之间进行交互。

现在,由于 GCJ 还允许提前编译,这为我们提供了以下从 Java 代码调用本机代码的机会:

  1. 使用
    gij
    运行常规 JVM,加载主类,该主类将使用
    System.loadLibrary()
    加载本机库,并调用本机方法。这是我们处理 Sun/Oracle JVM 时惯用的传统方式。
  2. 与上面相同,但使用
    gcj
    将字节码 AOT 编译为本机代码,并使用
    -l
    开关将生成的可执行文件动态链接到外部本机库。为了兼容性,可能会保留对
    System.loadLibrary()
    的调用,但实际上是不必要的,因为在创建 JVM 之前很久,动态解释器就已经加载了该库。实际上有三个子选项:
    • 让 JVM 通过
      System.loadLibrary()
      加载库,与
      dlopen()
      非常相似。
    • 通过
      ld
      将库链接到
      gcj -l
      。这比
      loadLibrary()
      方法更方便,因为您可以将
      -rpath
      开关传递给链接器,而无需在运行可执行文件之前手动设置
      LD_LIBRARY_PATH
    • 以上都不是,但通过
      LD_PRELOAD
      预加载了库。
  3. 将 AOT 编译的 Java 对象与本机代码部分生成的对象静态链接。这完全使我们无需调用
    System.loadLibrary()
    (因为没有库),使本机方法的代码在生成的可执行文件中立即可用。

由于我们有两种协议来访问本机代码(JNI 和 CNI),因此可用选项的数量增加了一倍(从 36)。

虽然 CNI 与静态链接的方法非常有效,但我还想探索动态链接如何与 CNI 和 AOT 编译一起工作,但这在链接时失败了。

考虑我有一个简单的主类:

public final class Main {
        static {
                System.loadLibrary("foo");
        }

        private Main() {
                assert false;
        }

        public static native void foo();

        public static void main(final String args[]) {
                foo();
        }
}

从中生成的

Main.h
CNI 标头如下所示:

#ifndef __Main__
#define __Main__

#pragma interface

#include <java/lang/Object.h>
#include <gcj/array.h>

extern "Java"
{
    class Main;
}

class Main : public ::java::lang::Object
{

  Main();
public:
  static void foo();
  static void main(JArray< ::java::lang::String * > *);
public: // actually package-private
  static jboolean $assertionsDisabled;
public:
  static ::java::lang::Class class$;
};

#endif // __Main__

...这是共享库的相当标准的默认实现:

#include <iostream>

#include <gcj/cni.h>

#include "Main.h"

void Main::foo() {
        std::cout << "Hello, World!" << std::endl;
}

将 Java 主类生成的对象与外部库链接时:

gcj -pie -fPIE -save-temps --main=Main -o cni-dynamic-native -L/opt/gcc64/6.5/lib64 -Wl,-rpath=/opt/gcc64/6.5/lib64 -L. -Wl,-rpath='$ORIGIN' -lstdc++ -lgcj -lfoo Main.class

我收到以下错误:

/usr/bin/ld: Main.o: in function `void Main::main(JArray<java::lang::String*>*)':
Main.java:13: undefined reference to `hidden alias for void Main::foo()'
/usr/bin/ld: Main.o:(.data.rel+0xc0): undefined reference to `hidden alias for void Main::foo()'
collect2: error: ld returned 1 exit status

确实,如果我用

Main.o
检查
nm
对象,就会出现未定义的符号:

                 U hidden alias for void Main::foo()

该符号存在于我链接的共享库 (

libfoo.so
) 中。它在目标文件中是 global (
T
),但在共享库中是 static (
t
)。

0000000000001180 t hidden alias for void Main::foo()

这个错误报告中描述了同样的问题,但不幸的是,没有解决方案。 正如我所说,当使用 CNI 静态链接两个对象或使用 JNI 动态链接两个对象时,一切都完美运行(示例代码)。

那么,如何使用 CNI 接口将本机库加载到 AOT 编译的可执行文件中? CNI 曾经用于动态链接吗?

java java-native-interface ld gcj libgcj
1个回答
0
投票
一种可能的解决方案是通过将其声明为共享库中的外部 (E) 符号来使该符号对链接器可见。

__attribute__((visibility("default"))) void Main::foo()
您可以尝试上述解决方案,看看它是否适合您,而对于 CNI 来说,它不太适合 AOT 编译和动态链接。建议使用 JNI 与 AOT 编译的 Java 应用程序进行动态链接。如果必须使用 CNI,建议坚持静态链接或使用 LD_PRELOAD 预加载库。

© www.soinside.com 2019 - 2024. All rights reserved.