Использование системного кода в AnyLogic с помощью JNI и JNA

Поскольку модели AnyLogic фактически являются Java-приложениями, вы можете расширять их различными функциями и возможностями путем подключения системных библиотек. Такие библиотеки, например, могут входить в состав операционной системы, на которой работает ваша версия AnyLogic. С их помощью вы можете усовершенствовать свои модели функциями и возможностями, предоставляемыми языками C и C++.

Данный материал описывает, как вызывать методы системных библиотек с использованием механизма Java Native Integration (JNI), встроенного в Java. Кроме того, вы познакомитесь с библиотекой Java Native Access (JNA). Существуют и другие способы вызывать системные методы из кода Java — вы можете использовать наиболее удобный вам подход.

Примечание: Использование системных приложений и библиотек в Java — это сложная тема, подразумевающая, что вы знакомы с основами Java и стандартами именования классов и методов. Рекомендуем предварительно ознакомиться с материалами, посвященным базовым концепциям работы с Java в AnyLogic.
Если вы хотите узнать больше о подключении Java-библиотек, ознакомьтесь со статьей: Библиотеки.

Механизм JNI в моделях AnyLogic

Механизм JNI позволяет вызывать системные функции библиотек DLL, SO и DYLIB/JNILIB. Для этого используется специальное ключевое слово: native. Данный прием может быть полезен в различных ситуациях:

Механизм JNI предлагает решение этих проблем, представляя собой «мостик» между Java-кодом и системным кодом. Вы можете использовать это преимущество в контексте моделей AnyLogic.

 Чтобы использовать методы JNI внутри модели

  1. Убедитесь, что учли все предварительные требования.
  2. Подготовьте необходимые системные методы.
  3. Загрузите системную библиотеку в код Java.
  4. Вызовите системные методы изнутри модели.

JNI: Предварительные требования

Если вы работали с JNI ранее, вы знаете, что предназначенные для работы по этому механизму библиотечные файлы создаются с учетом строгих стандартов именования: названия всех классов и методов внутри этих библиотек соответствуют определенным правилам, заданным внутри специальных «заголовочных» файлов.

Таким образом, чтобы использовать JNI для вызова некоторой функции из DLL-файла, необходимо удостовериться, что имена Java-пакета модели, нужных интерфейсов, классов и методов идентичны именам Java-пакета, интерфейсов, классов и методов, указанным при компиляции DLL-файла.

Примечание: Чтобы не учитывать эти ограничения, попробуйте использовать библиотеку JNA.

Чтобы использовать системные методы в моделях AnyLogic, подготовьте соответствующие библиотеки.

 Чтобы создать методы на основе системного кода

  1. Подготовьте исходный код будущей системной библиотеки C/C++, из которого впоследствии скомпилируете DLL-, SO- или DYLIB/JNILIB-файл (в зависимости от используемой платформы).
  2. Удостоверьтесь, что у компилятора есть доступ к служебным файлам библиотеки JNI, указав в переменных среды путь до директории Java.
  3. Подпишите методы Java, соответствующие системным методам, которые планируете использовать.
  4. Запустите утилиту javac с флагом -h, чтобы скомпилировать заголовочные файлы, или
    Запустите утилиту javah, используемую в старых версиях Java для генерации заголовочных файлов.
  5. Удостоверьтесь, что компилятор C/C++ имеет доступ ко вновь созданным заголовочным файлам.
  6. Реализуйте интересующие вас методы на C/C++ в исходном коде будущей системной библиотеки.
  7. Скомпилируйте файлы C/C++ в DLL-файл, SO-файл или DYLIB/JNIB-файл.

После выполнения этих шагов у вас появится библиотечный файл, готовый к использованию в контексте моделей AnyLogic.

Примечание: Если вы хотите узнать больше о создании библиотек, предназначенных для работы с JNI, ознакомьтесь со следующими материалами:Baeldung; Nanyang Technological University of Singapore (на английском языке). В них подробно рассматривается разработка таких библиотек. Вы также можете изучить техническую спецификацию (англ.)(механизма JNI, опубликованную на сайте Oracle.

 Чтобы загрузить библиотеку в модель

Самый простой способ обратиться к библиотечному файлу — это создание специального класса. Вы можете сделать это вручную (из кода) или с помощью графического интерфейса AnyLogic.

Создав класс, укажите в нем библиотеку, которую хотите использовать в модели.

Предположим, у нас имеется библиотека под названием MultiplierLibrary, внутри которой имеется метод multiply(). Этот метод возвращает целое число и требует двух параметров того же типа int. Чтобы обратиться к этой библиотеке, следует использовать такой код:

public class Multiplier {
    static {
        System.loadLibrary("MultiplierLibrary");
    }
}

Метод System.loadLibrary загрузит системную библиотеку во время «прогона» модели — MultiplierLibrary.dll в Windows / libMultiplierLibrary.so в *nix-системе / MultiplierLibrary.jnilib (в Mac OS X). Чтобы данный пример работал корректно, скомпилированную библиотеку следует поместить внутрь директории с моделью — в противном случае методу в качестве параметра следует передать строку, содержащую абсолютный путь до библиотеки.

После загрузки библиотеки необходимо объявить, какой системный метод будет использоваться в модели. Это нужно сделать внутри контекста того же класса:

native int multiply(int a, int b);

Теперь вы можете вызывать метод multiply() класса Multiplier в любом месте модели.

На изображении ниже — нестандартный класс, созданный с помощью графического интерфейса AnyLogic. Он реализует описанный выше подход с использованием механизма JNI:

JNI: Нестандартный класс в графическом интерфейсе AnyLogic

Если вы хотите использовать другие методы из системной библиотеки, вы можете также объявить их в контексте этого класса. В рамках примера мы будем использовать метод, описанный выше.

Создав класс с системными методами, вы можете использовать их во время «прогона» модели.

 Чтобы использовать системные методы в коде модели

  1. Объявите экземпляр своего класса, например:
    Multiplier myMultiplier = new Multiplier();
  2. Вызовите метод системного класса и передайте параметры, если это необходимо. AnyLogic осуществит вызов метода и вернет результат. Полученное значение можно, к примеру, присвоить какой-либо переменной:
    int a = 0;
    a = myMultiplier.multiply(3, 4);

С помощью предложенной процедуры вы можете обращаться к системным методам из любого места модели, фиксировать результаты, а затем использовать их при вычислениях.

JNI: Использование метода JNI из события

На изображении выше показано событие, которое использует системный метод для умножения значений переменных a и b.

Использование библиотеки JNA

JNA (расшифровывается как Java Native Access) — это библиотека, разработанная совместными усилиями сообщества Java. Ее задача — предоставить упрощенный доступ к системным библиотекам, избавившись от необходимости повторять код на нескольких языках и использовать структуры, единственное назначение которых — обеспечение корректной работы интерфейса (так называемых блоков «связующего кода»).

JNA поддерживает класс Native. С помощью этого класса вы можете обращаться к библиотечным файлам. Такие файлы могут храниться в директории, указанной в свойстве jna.boot.library.path, или внутри системных хранилищ библиотек.

Чтобы скачать JNA, посетите репозиторий проекта в GitHub (англ.).

Чтобы узнать больше об особенностях разработки с использованием JNA, ознакомьтесь с официальной документацией (англ.).

 Чтобы использовать методы JNA внутри модели

  1. Добавьте библиотеку JNA в список зависимостей модели.
  2. Создайте Java-интерфейс.
  3. Вызовите системные методы изнутри модели.

 Чтобы добавить библиотеку в список зависимостей

  1. Выберите модель в панели Проекты.
  2. Разверните секцию свойств Зависимости.
  3. В секции Зависимости щелкните по кнопке Добавить справа от таблицы Jar файлы и папки классов, требуемые для построения модели.
  4. В появившемся диалоговом окне выберите опцию Файл архива Java (*.jar, *.zip) в разделе Тип, а затем укажите местоположение файла-архива jna.jar в поле Файл. Вы можете указать абсолютный или относительный путь до файла.
    Кроме того, вы можете щелкнуть по кнопке Открыть, перейти к директории и указать нужный файл в открывшемся окне проводника.
  5. Чтобы импортировать файл jna.jar в папку модели, выберите опцию Импортировать файл в папку модели. Импортировав Java-архив в папку модели, вы упростите процедуру переноса модели на другое устройство — достаточно будет скопировать папку целиком, и модель будет полностью работоспособна.
    Если у вас есть несколько моделей, использующих библиотеку jna.jar, вы можете выбрать опцию Ссылаться на текущее местоположение файла. В этом случае JAR-файл не будет скопирован в папку модели, и AnyLogic всегда будет искать файл по указанному пути.
  6. Щелкните по кнопке Готово. После окончания операции запись о файле jna.jar появится на панели Проекты — раскройте папку Ресурсы в дереве проекта, чтобы увидеть ее.

JNA: Список зависимостей модели

Нужный архив Java прикреплен к модели — давайте посмотрим, как можно сослаться на него, чтобы использовать содержащиеся в нем методы.

 Чтобы создать интерфейс для библиотеки JNA

  1. Щелкните правой кнопкой мыши по элементу текущей модели в панели Проекты (Mac OS: зажмите кнопку Ctrl и сделайте щелчок). В контекстном меню выберите опцию Новый ’ Java интерфейс.
  2. Откроется диалоговое окно Новый Java интерфейс.
  3. Укажите имя для нового интерфейса в поле Имя и щелкните по кнопке Готово.
  4. Откроется редактор Java-кода, в котором вы можете написать код для вновь созданного интерфейса.
  5. Перед тем как вводить код интерфейса, добавьте следующие строки:
    import com.sun.jna.Library;
    import com.sun.jna.Native;
    С помощью этого кода осуществляется импортирование нужных пакетов из файла jna.jar. После этого вы сможете указывать нужные классы и методы из системных библиотек, просто вводя их имена.
  6. Новый интерфейс будет расширять предоставленный библиотекой JNA класс Library и устанавливать соответствие между методами Java и C/C++. Для этого введите следующий код:
    public interface MyInterface extends Library {
      MyInterface INSTANCE = (MyInterface) Native.loadLibrary("%library%", MyInterface.class);
    }
    В примере выше MyInterface — это заданное вручную имя интерфейса. Вы можете задать название самостоятельно.

    Примечание: С помощью метода Native.loadLibrary() мы обращаемся к Java-пакету Native, входящему в состав библиотеки JNA, и вызываем содержащийся в нем метод loadLibrary(). Этот метод указывает, какой именно библиотечный файл C/C++ мы хотим вызвать. Строка, которая передается этому методу в виде параметра — это название библиотечного файла С/C++.
  7. В коде интерфейса следует явно указать следующую информацию:

Предположим, у нас имеется написанная на C++ библиотека randomint. Эта библиотека содержит имплементацию метода randomNumber(int bound). В качестве параметра этому методу нужно передать целое число; при вызове метода будет сгенерировано случайное целое число между 0 и значением, переданным в параметре.

Исходный код такого метода на C++ будет выглядеть так:

int randomNumber(int bound) {
  return rand() % bound;
}

В зависимости от операционной системы скомпилированная библиотека должна иметь одно из перечисленных названий:

Вне зависимости от того, на какой операционной системе вы выполняете код, интерфейс взаимодействия с данной библиотекой будет выглядеть следующим образом:

public interface MyInterface extends Library {
  MyInterface INSTANCE = (MyInterface) Native.loadLibrary("randomint", MyInterface.class);
  int randomNumber(int bound);
}

Ваш интерфейс готов.

JNA: Команды импорта и реализация интерфейса

Теперь вы можете вызывать методы, объявленные внутри интерфейса, и использовать их из кода модели.

 Чтобы использовать системные методы из кода модели

  1. Объявим переменную для примера:
    int a = 0;
  2. Чтобы использовать метод из библиотечного файла, сначала объявите экземпляр интерфейса, созданного на предыдущем этапе. После этого вы можете вызвать метод, указав переменную (целое число).
    Метод также возвращает целое число, которое можно сделать значением переменной:
    a = MyInterface.INSTANCE.randomNumber(5);
  3. Вы можете указать, что переменную нужно вывести в панели Консоль:
    traceln(a);

    JNA: Calling a native method with JNA

  4. Запустите модель. Когда введенный код будет обработан в ходе «прогона», в панели Консоль появится случайное число:

    JNA: The method call result in the Java console

Экспорт моделей с системными вызовами в AnyLogic Cloud

Поскольку платформа AnyLogic Cloud работает на операционной системе Linux, модели, задействующие вызовы библиотек DLL или DYLIB/JNILIB, не смогут использовать эти функции. Тем не менее, вы можете использовать всю функциональность системных методов при условии, что вместе с моделью в Cloud будет загружена поддерживаемая *nix-библиотека в формате SO.

Обратите внимание, что эта функциональность доступна только для частных облаков Private Cloud.

Чтобы использовать системные методы в моделях AnyLogic Cloud:

Убедившись, что модель отвечает перечисленным требованиям, экспортируйте ее в частное облако Private Cloud как обычно. Такая модель будет работать в Private Cloud без проблем.

Примечание: Если в число зависимостей входит библиотека JNA, убедитесь, что прикрепили ее к модели.