Мне нужно получить доступ к «родной» библиотеке C из Java через Java Native Access (JNA). Идиоматический способ сделать это — определить интерфейс, который расширяет Library
и объявляет все функции, к которым мне нужен доступ. Затем создайте экземпляр синглтона с помощью метода Native.load()
:
public interface MyLibrary extends Library {
MyLibrary INSTANCE = Native.load("mylib", MyLibrary.class);
int some_function(int param);
}
Это прекрасно работает с функциями. Но как мне получить доступ к глобальным переменным через интерфейс JNA?
В заголовочном файле C он определяется следующим образом:
extern const uint16_t SOME_GLOBAL_VARIABLE;
Информации о том, как получить доступ к глобальным переменным через интерфейс JNA, практически нет. Самое близкое, что я нашел, это метод NativeLibrary.getGlobalVariableAddress()
. Но, к сожалению, этот метод находится в классе NativeLibrary
, а не в интерфейсе Library
!
И, похоже, не существует способа получить экземпляр NativeLibrary
при работе с пользовательским интерфейсом (который является производным от интерфейса Library
) и с методом Native.load()
. Вместо этого я, вероятно, мог бы использовать NativeLibrary.getInstance()
, чтобы получить экземпляр NativeLibrary
и иметь возможность использовать getGlobalVariableAddress()
. Но тогда как мне применить мой Library
-интерфейс к экземпляру NativeLibrary
, чтобы иметь возможность вызывать функции?
Загрузка одной и той же библиотеки дважды: один раз через Native.Load()
, чтобы иметь возможность вызывать функции через мой интерфейс, и один раз через NativeLibrary.getInstance()
, чтобы иметь возможность получить адрес глобальной переменной, кажется очень уродливым обходным путем/хаком, при условии, что это вообще бы работало.
Короче говоря, есть ли в JNA какой-либо способ перейти от интерфейса Library
к базовому экземпляру NativeLibrary
или наоборот?
Или, как вариант, как объявить глобальную переменную прямо в интерфейсе JNA? 🤔
Как вы отметили в своем вопросе, класс NativeLibrary
предоставляет метод getGlobalVariableAddress()
, которому вы можете передать собственное имя переменной в виде строки, чтобы получить к ней Pointer
. (Знание количества байтов, которые необходимо прочитать из этого указателя, вам придется определить вручную.)
Вы можете напрямую назначить NativeLibrary
экземпляру и получить поле следующим образом:
NativeLibrary fooLib = NativeLibrary.getInstance("foo");
Pointer p = fooLib.getGlobalVariableAddress("SOME_GLOBAL_VARIABLE");
short s = p.getShort(0); // signed, convert to uint16
Что касается прямого связывания экземпляра Library
с NativeLibrary
, то оно не поддерживается API. Класс Library
сам по себе является интерфейсом, поэтому не определяет никаких методов, указывающих на собственную библиотеку. У него есть внутренний класс Handler
с методом getNativeLibrary
, который используется как часть метода load()
при настройке класса JDK Proxy
. Похоже, что вы могли бы использовать методы Proxy
или InvocationHandler
, чтобы попытаться восстановить класс посредством отражения, но это оставлено в качестве упражнения для читателя.
@ user25710790 JNA — это проект с открытым исходным кодом, поддерживаемый участниками! Не стесняйтесь предлагать эту функцию там, а еще лучше, отправьте запрос на добавление этой возможности! Я согласен, что это полезно.
Основываясь на предложении Дэниела Виддиса, мне удалось найти решение на основе java.lang.reflect.Proxy
, чтобы получить экземпляр Handler
из интерфейса Library
с помощью метода Proxy.getInvocationHandler(), который затем позволяет мне вызвать getNativeLibrary()
объекта Handler для получения требуемого экземпляра NativeLibrary
:
import java.lang.reflect.Proxy;
import com.sun.jna.Library;
import com.sun.jna.NativeLibrary;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
public interface MyLibrary extends Library {
static final MyLibrary INSTANCE = Native.load("mylib123", MyLibrary.class);
static class NativeLibraryHolder {
public static final NativeLibrary NATIVE_LIBRARY = ((Handler)Proxy.getInvocationHandler(MyLibrary.INSTANCE)).getNativeLibrary();
}
static class SOME_GLOBAL_VARIABLE {
private static final Pointer ADDRESS = NativeLibraryHolder.NATIVE_LIBRARY.getGlobalVariableAddress("SOME_GLOBAL_VARIABLE");
int get() { return ADDRESS.getInt(0L); }
void set(final int newValue) { ADDRESS.setInt(0L, newValue); }
}
int some_function(int param);
}
Огромное спасибо, что указали на
java.lang.reflect.Proxy
. Я никогда не знал о его существовании и о том, что его можно использовать для получения «внутреннего»Handler
класса из интерфейса! Это действительно решает мою проблему. Тем не менее, я думаю, что JNA должна предоставить «более простой» способ решения этой задачи. Например, они могли бы добавить вспомогательный методstatic
в интерфейсLibrary
.