Вызов собственного кода из приложения Java, скомпилированного AOT, с использованием CNI
Компилятор GNU для Java предоставляет два метода для вызова собственного кода из приложения Java.
- Во-первых, существует спецификация JNI , разработанная Sun Microsystems , и GCJ придерживается этой спецификации.
- Во-вторых, есть CNI, скомпилированный собственный интерфейс , реализованный только GCJ, который почти не документирован, но позволяет реализовать собственные методы на C++, предоставляя API более высокого уровня для взаимодействия между собственным кодом и JVM.
Теперь, поскольку GCJ также допускает предварительную компиляцию, это дает нам следующие возможности вызывать собственный код из кода Java:
- Запустите обычную JVM с помощью
gij
загрузите свой основной класс, который загрузит собственную библиотеку, используяи вызовите собственный метод. Это обычный способ, к которому мы привыкли при работе с JVM Sun/Oracle. - То же, что и выше, но AOT-компилирует ваш байт-код в собственный код, используя
gcj
и динамически связать полученный исполняемый файл с внешней собственной библиотекой с помощью-l
выключатель. Вызов можно сохранить для совместимости, но на самом деле он не нужен, поскольку библиотека уже будет загружена динамическим интерпретатором задолго до создания JVM. На самом деле есть три подварианта:- Загрузите библиотеку с помощью JVM через , аналогично
dlopen()
. - Связать библиотеку с помощью
ld
, с помощьюgcj -l
. Это удобнее, чемloadLibrary()
подход, поскольку вы можете пройти-rpath
переключитесь на компоновщик и избегайте необходимости вручную устанавливатьLD_LIBRARY_PATH
перед запуском исполняемого файла. - Ничего из вышеперечисленного, но библиотека предварительно загружена через
LD_PRELOAD
.
- Загрузите библиотеку с помощью JVM через , аналогично
- Статически свяжите объект Java, скомпилированный с помощью AOT, с объектом, созданным из части собственного кода. Это полностью избавляет нас от необходимости звонить
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();
}
}
The 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
) Я ссылаюсь против. Это глобально (T
) в объектном файле, но статический (t
) в общей библиотеке.
0000000000001180 t hidden alias for void Main::foo()
Та же проблема описана в этом отчете об ошибке , но решения, к сожалению, нет. Как я уже сказал, всё работает отлично при статическом связывании двух объектов с помощью CNI или динамическом с помощью JNI (пример кода ).
Итак, как мне загрузить собственную библиотеку в исполняемый файл, скомпилированный с помощью AOT, с помощью интерфейса CNI? Был ли CNI когда-либо предназначен для динамического связывания?