Оболочка библиотеки для API-интерфейсов Android . Часть пакета разработки игр для Android .

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

Инструмент работает путем создания кода C для общедоступных символов, содержащихся в предоставленных вами файлах Java Archive (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 { ... }

Экземпляры непрозрачных указателей называются оболочками . Инструмент-оболочка генерирует дополнительные функции поддержки для каждого класса. Для предыдущего примера класса 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) { ... }
  }
}

Общие типы

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

Например, если класс MyGeneric<T> существует в API и существует два экземпляра этого класса, например 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;

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

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

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