Библиотека-оболочка для API Android. Часть комплекта разработки игр для Android .

Библиотека-обёртка — это инструмент командной строки (CLI), который генерирует код-обёртку на языке C для API Android, написанных на Java. Вы можете использовать этот код в нативных приложениях Android для вызова API Java без необходимости вручную создавать интерфейс Java Native Interface (JNI). Этот инструмент упрощает разработку приложений Android, написанных преимущественно на C или C++.

Инструмент генерирует код на языке C для открытых символов, содержащихся в предоставленных вами файлах архива Java (JAR), или для классов, определённых в файле конфигурации инструмента, или для того и другого. Код, сгенерированный инструментом, не заменяет API Java; вместо этого он служит мостом между вашим кодом на языке C и Java. Для вашего приложения по-прежнему требуется включение в проект библиотек Java, которые вы используете.

Скачать

Загрузите архив-оболочку библиотеки и распакуйте его содержимое в каталог по вашему выбору.

Синтаксис

Инструмент-оболочка библиотеки имеет следующий синтаксис командной строки:

java -jar lw.jar \
  [-i jar-file-to-be-wrapped] \
  [-o output-path] \
  [-c config-file] \
  [-fa allow-list-file] \
  [-fb block-list-file] \
  [--skip_deprecated_symbols]
Параметр Описание
-i jar-file-to-be-wrapped JAR-файл для генерации кода-обёртки на языке C. Можно указать несколько JAR-файлов, например:
-i first_library.jar -i second_library.jar...
-o output-path Расположение сгенерированного кода в файловой системе.
-c config-file Путь к файлу конфигурации библиотеки-обёртки в файловой системе. Подробнее см. в разделе «Конфигурация» .
-fa allow-list-file Путь к файлу фильтра, в котором можно указать символы для инструмента, который нужно обёртывать. Подробнее см. в разделе «Фильтр» .
-fb block-list-file Путь к файлу фильтра, содержащему символы, исключенные из переноса. Подробнее см. в разделе «Фильтр» .
--skip_deprecated_symbols Дает указание инструменту-оболочке пропускать символы @Deprecated .

Файл конфигурации оболочки

Конфигурация обёртки библиотеки представляет собой JSON-файл, позволяющий управлять процессом генерации кода. Файл имеет следующую структуру.

{
  // An array of type-specific configs. A type config is useful when a user wants to map
  // a Java type to a manually defined C type without generating the code. For example, when a developer
  // has their own implementation of the "java.lang.String" class, they can tell the generator to use it
  // instead of generating it.
  "type_configs": [
    {
      // [Required] Name of a fully qualified Java type.
      "java_type": "java.lang.String",
      // The C type that the java_type will be mapped to.
      "map_to": "MyOwnStringImplementation",
      // A header file that contains the declaration of the "map_to" type.
      "source_of_definition": "my_wrappers/my_own_string_implementation.h",
      // Controls if a value should be passed by pointer or value.
      "pass_by_value": false
    }
  ],
  // An array of package-specific configs.
  "package_configs": [
    {
      // [Required] A name of a Java package that this section regards. A wildchar * can be used at the
      // end of the package name to apply this config to all packages whose name starts with this value.
      "package_name": "androidx.core.app*",
      // A subdirectory relative to the root directory where the generated code will be located.
      "sub_directory": "androidx_generated/",
      // If true, the generated file structure reflects the package name. For example, files generated
      // for the package com.google.tools will be placed in the directory com/google/tools/.
      "file_location_by_package_name": true,
      // A prefix added to all class names from this package.
      "code_prefix": "Gen",
      // A prefix added to all generated file names from this package.
      "file_prefix": = "gen_"
    }
  ],
  // An array of manually defined classes for wrapping. Defining classes manually is useful when a
  // jar file with desired classes are not available or a user needs to wrap just a small part of an SDK.
  "custom_classes": [
    {
      // [Required] A fully-qualified Java class name. To define inner class, use symbol "$", for example
      // "class com.example.OuterClass$InnerClass".
      "class_name": "class java.util.ArrayList<T>",
      // List of methods.
      "methods": [
        "ArrayList()", // Example of a constructor.
        "boolean add(T e)", // Example of a method that takes a generic parameter.
        "T get(int index)", // Example of a method that returns a generic parameter.
        "int size()" // Example of parameterless method.
      ]
    },
  ]
}

Фильтр файлов

Может быть полезно исключить некоторые символы из JAR-файлов, которые вы планируете переносить. Вы можете указать файл фильтра в настройках, чтобы исключить символы. Файл фильтра — это простой текстовый файл, каждая строка которого определяет символ для переноса. Файлы фильтров используют следующий синтаксис:

java-symbol-name java-jni-type-signature

Ниже приведен пример файла фильтра:

# Class filter
java.util.ArrayList Ljava.util.ArrayList;

# Method filter
java.util.ArrayList.lastIndexOf (Ljava.lang.Object;)I

# Field filter
android.view.KeyEvent.KEYCODE_ENTER I

Вы предоставляете конфигурацию в виде файла фильтра, определяющего разрешённые символы с помощью параметра -fa и заблокированные символы с помощью параметра -fb . Оба параметра можно использовать одновременно. Если указаны оба фильтра, символ будет перенесён, если он определён в файле разрешающего фильтра и отсутствует в файле блокирующего фильтра.

Пример сценария

Предположим, что вам нужно обернуть JAR-файл ChatLibrary.jar , содержащий следующий класс:

public class ChatManager {
  public static void sendMessage(int userId, String message) {...}
}

Для вашего проекта на C необходимо сгенерировать нативную оболочку для этого JAR-файла, чтобы ваше нативное приложение Android могло вызывать его во время выполнения. Сгенерируйте этот код, используя оболочку библиотеки, с помощью следующей команды:

java -jar lw.jar -i ChatLibrary.jar -o ./generated_code/

Предыдущая команда генерирует исходный код на языке C в каталоге ./generated_code . Сгенерированный файл chat_manager.h содержит код, аналогичный следующему, что позволяет вызывать библиотеку в вашем проекте:

#include "java/lang/string.h"

typedef struct ChatManager_ ChatManager;
void ChatManager_sendMessage(int32_t user_id, String* message);

Подробный пример сценария см. в руководстве по оболочке библиотеки .

Подробности об инструменте

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

Структура выходного каталога

Все исходные и заголовочные файлы на языке C находятся в подкаталогах, имя которых соответствует имени пакета обёрнутого класса Java. Например, код обёртки для указанного JAR-файла java.lang.Integer генерируется в каталоге ./java/lang/integer.[h/cc] .

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

Жизненный цикл объекта

Объекты Java представлены в коде C как непрозрачные указатели, называемые обёртками. Обёртка управляет ссылкой JNI на соответствующий объект Java. Обёртку можно создать в следующих случаях:

  • Оборачивая существующую ссылку JNI с помощью вызова функции MyClass _wrapJniReference(jobject jobj) . Функция не становится владельцем предоставленной ссылки, а создаёт собственную глобальную ссылку JNI.
  • Создавая новый объект, что эквивалентно вызову конструктора в Java: MyClass _construct()
  • Возвращая новую оболочку из функции, например: Score* Leaderboard_getScore(Leaderboard* instance, String* leaderboard_name)

Вам необходимо уничтожить все обёртки, когда они больше не используются. Для этого вызовите специальную функцию destroy() MyClass _destroy( MyClass * instance) .

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

Например, когда метод Java Singleton.getInstance() всегда возвращает один и тот же экземпляр, эквивалентная функция на стороне C создаст новый экземпляр обертки для того же экземпляра Java:

Singleton* singleton_a = Singleton_getInsance();
Singleton* singleton_b = Singleton_getInsance();

// singleton_a and singleton_b are different pointers, even though they represent the same Java instance.

Обработка неиспользуемых классов

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

  • wrapJniReference()
  • getJniReference()
  • destroy()

Подробности генерации кода

При запуске библиотека-оболочка генерирует код на языке C на основе открытых символов в JAR-файлах, предоставленных вами инструменту. Сгенерированный код на языке C может отличаться от кода Java, используемого в оболочке. Например, язык C не поддерживает такие функции, как ООП, универсальные типы, перегрузка методов и другие функции Java.

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

Классы

Классы представлены в виде непрозрачных указателей в C:

С/С++

typedef struct MyClass_ MyClass;

Ява

public class MyClass { ... }

Экземпляры непрозрачных указателей называются обёртками (wrappers) . Инструмент обёртки генерирует дополнительные вспомогательные функции для каждого класса. Для класса MyClass из предыдущего примера генерируются следующие функции:

// Wraps a JNI reference with MyClass. The 'jobj' must represent MyClass on the Java side.
MyClass* MyClass_wrapJniReference(jobject jobj);

// Return JNI reference associated with the 'MyClass' pointer.
jobject MyClass_getJniReference(const MyClass* object);

// Destroys the object and releases underlying JNI reference.
void MyClass_destroy(const MyClass* object);

Конструкторы

Классы с публичными или стандартными конструкторами представлены с помощью специальных функций:

С/С++

MyClass* MyClass_construct(String* data);

Ява

public class MyClass {
  public MyClass(String data) { ... }
}

Методы

Методы представлены как обычные функции. Имя функции содержит имя исходного класса. Функции, представляющие нестатические методы экземпляра, имеют в качестве первого параметра указатель на структуру, представляющую объект Java, от имени которого вызывается функция. Этот подход аналогичен использованию указателя this .

С/С++

Result* MyClass_doAction(const MyClass* my_class_instance, int32_t action_id, String* data);
int32_t MyClass_doAction(int32_t a, int32_t b);

Ява

public class MyClass {
  public Result doAction(int actionId, String data) { ... }
  public static int doCalculations(int a, int b) { ... }
}

Внутренние классы

Внутренние классы представлены близко к обычным классам, за исключением того, что имя соответствующей структуры C содержит цепочку имен внешних классов:

С/С++

typedef struct MyClass_InnerClass_ MyClass_InnerClass;

Ява

public class MyClass {
  public class InnerClass {...}
}

Методы внутреннего класса

Методы внутреннего класса представлены следующим образом:

С/С++

bool MyClass_InnerClass_setValue(MyClass_InnerClass* my_class_inner_class_instance, int32_t value);

Ява

public class MyClass {
  public class InnerClass {
    public boolean setValue(int value) { ... }
  }
}

Общие типы

Библиотечная обёртка не обёртывает обобщённые типы напрямую. Вместо этого инструмент генерирует обёртки только для экземпляров обобщённых типов.

Например, если в API существует класс MyGeneric<T> и существуют два экземпляра этого класса, например, MyGeneric<Integer> и MyGeneric<String> , генерируются обёртки для этих двух экземпляров. Это означает, что невозможно создать новые экземпляры типа MyGeneric<T> , используя другие конфигурации типов. См. следующий пример:

С/С++

// result.h

typedef struct Result_Integer_ Result_Integer;
typedef struct Result_Float_ Result_Float;

Integer* Result_Integer_getResult(const Result_Integer* instance);
Float* Result_Float_getResult(const Result_Float* instance);

// data_processor.h

typedef struct DataProcessor_ DataProcessor;

Result_Integer* DataProcessor_processIntegerData(const DataProcessor* instance);
Result_Float* DataProcessor_processFloatData(constDataProcessor* instance);

Ява

public class Result<T> {
  public T getResult();
}

public class DataProcessor {
  public Result<Integer> processIntegerData();
  public Result<Float> processFloatData();
}

Реализовать интерфейсы

Реализуйте интерфейс C, вызвав implementInterface() и предоставив функцию обратного вызова для каждого метода интерфейса. Таким способом можно реализовать только интерфейсы; классы и абстрактные классы не поддерживаются. См. следующий пример:

С/С++

// observer.h

typedef struct Observer_ Observer;
typedef void (*Observer_onAction1Callback)();
typedef void (*Observer_onAction2Callback)(int32_t data);

Observer* Observer_implementInterface(
Observer_onAction1Callback observer_on_action1_callback,
Observer_onAction2Callback observer_on_action2_callback);

Ява

public interface Observer {
  void onAction1();
  void onAction2(int data);
}

public class Subject {
  public void registerObserver(Observer observer);
}

Пример использования:

void onAction1() {
  // Handle action 1
}

void onAction2(int32_t data) {
  // Handle action 2
}

Observer* observer = Observer_implementInterface(onAction1, onAction2);
Subject_registerObserver(subject, observer);

Ограничения

Инструмент для создания библиотек находится в стадии бета-тестирования. Вы можете столкнуться со следующими ограничениями:

Неподдерживаемые конструкции Java

Бета-версия библиотечной оболочки не поддерживает следующие конструкции:

  • Перегрузка методов

    Язык C не позволяет объявлять две функции с одинаковым именем. Если класс использует перегрузку методов, сгенерированный код C не скомпилируется. Обходной путь — использовать только один метод с достаточным набором параметров. Оставшиеся функции можно отфильтровать с помощью фильтров . Это также относится к конструкторам.

  • Шаблонные методы

  • Поля, отличные от static final int и static final String

  • Массивы

Возможные конфликты имен

Из-за особенностей представления классов Java в коде C в очень редких случаях могут возникать конфликты имён. Например, класс Foo<Bar> и внутренний класс Bar внутри класса Foo представлены в C одним и тем же символом: typedef struct Foo_Bar_ Foo_Bar;

Поддерживать

Если вы обнаружите проблему с оболочкой библиотеки, пожалуйста, сообщите нам об этом.

Просмотреть ошибки Сообщить об ошибке
Инженерное дело
Документация