Библиотека-оболочка для 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;
Поддерживать
Если вы обнаружите проблему с оболочкой библиотеки, пожалуйста, сообщите нам об этом.
Просмотреть ошибки | Сообщить об ошибке |
---|---|
Инженерное дело | bug_report |
Документация | bug_report |