GNU Java 编译器提供了两种从 Java 应用程序调用本机代码的方法。
现在,由于 GCJ 还允许提前编译,这为我们提供了以下从 Java 代码调用本机代码的机会:
gij
运行常规 JVM,加载主类,该主类将使用 System.loadLibrary()
加载本机库,并调用本机方法。这是我们处理 Sun/Oracle JVM 时惯用的传统方式。gcj
将字节码 AOT 编译为本机代码,并使用 -l
开关将生成的可执行文件动态链接到外部本机库。为了兼容性,可能会保留对 System.loadLibrary()
的调用,但实际上是不必要的,因为在创建 JVM 之前很久,动态解释器就已经加载了该库。实际上有三个子选项:
System.loadLibrary()
加载库,与 dlopen()
非常相似。ld
将库链接到 gcj -l
。这比 loadLibrary()
方法更方便,因为您可以将 -rpath
开关传递给链接器,而无需在运行可执行文件之前手动设置 LD_LIBRARY_PATH
。LD_PRELOAD
预加载了库。System.loadLibrary()
(因为没有库),使本机方法的代码在生成的可执行文件中立即可用。由于我们有两种协议来访问本机代码(JNI 和 CNI),因此可用选项的数量增加了一倍(从 3 到 6)。
虽然 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 曾经用于动态链接吗?
__attribute__((visibility("default"))) void Main::foo()
您可以尝试上述解决方案,看看它是否适合您,而对于 CNI 来说,它不太适合 AOT 编译和动态链接。建议使用 JNI 与 AOT 编译的 Java 应用程序进行动态链接。如果必须使用 CNI,建议坚持静态链接或使用 LD_PRELOAD 预加载库。