Wrapper de bibliothèque pour les API Android Composant d'Android Game Development Kit.
Le wrapper de bibliothèque est un outil de ligne de commande (CLI) qui génère du code wrapper en langage C pour les API Android écrites en Java. Vous pouvez utiliser ce code dans les applications Android natives pour appeler les API Java sans avoir à créer manuellement une interface Java native (JNI). Cet outil peut simplifier le développement des applications Android principalement écrites en C ou C++.
L'outil génère du code C pour les symboles publics contenus dans les fichiers Java Archive (JAR) que vous fournissez et/ou pour les classes définies dans le fichier de configuration de l'outil. Le code généré par l'outil ne remplace pas les API Java. Au lieu de cela, il sert de pont entre le code C et Java. Votre application nécessite toujours que les bibliothèques Java encapsulées soient incluses dans le projet.
Télécharger
Téléchargez l'archive du wrapper de bibliothèque et décompressez son contenu dans le répertoire de votre choix.
Syntaxe
La syntaxe de la ligne de commande de l'outil de wrapper de bibliothèque est la suivante :
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]
Paramètre | Description |
---|---|
-i jar-file-to-be-wrapped |
Fichier JAR permettant de générer le code de wrapper C. Vous pouvez spécifier plusieurs fichiers JAR, par exemple : -i first_library.jar -i second_library.jar...
|
-o output-path |
Emplacement du système de fichiers pour le code généré. |
-c config-file |
Chemin d'accès du système de fichiers vers le fichier de configuration du wrapper de bibliothèque. Pour en savoir plus, consultez la section Configuration. |
-fa allow-list-file |
Chemin d'accès à un fichier de filtre dans lequel vous pouvez spécifier des symboles que l'outil doit encapsuler. Pour en savoir plus, consultez la section Filtre. |
-fb block-list-file |
Chemin d'accès à un fichier de filtre contenant les symboles exclus de l'encapsulation. Pour en savoir plus, consultez la section Filtre. |
--skip_deprecated_symbols |
Ordonne à l'outil wrapper d'ignorer les symboles @Deprecated. |
Fichier de configuration du wrapper
La configuration du wrapper de bibliothèque est un fichier JSON vous permettant de contrôler le processus de génération du code. Ce fichier utilise la structure suivante.
{
// 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.
]
},
]
}
Fichiers de filtre
Il peut être utile d'exclure des fichiers JAR certains symboles que vous prévoyez d'encapsuler. Pour ce faire, vous pouvez spécifier un fichier de filtre dans votre configuration. Un fichier de filtre est un fichier texte simple dans lequel chaque ligne définit un symbole à encapsuler. Les fichiers de filtre utilisent la syntaxe suivante :
java-symbol-name java-jni-type-signature
Voici un exemple de fichier de filtre :
# 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
Vous devez fournir à la configuration un fichier de filtre spécifiant les symboles autorisés à l'aide du paramètre -fa
, et les symboles bloqués à l'aide du paramètre -fb
. Vous pouvez utiliser les deux paramètres simultanément. Si les deux filtres sont fournis, un symbole est encapsulé lorsqu'il est défini dans le fichier de filtre d'autorisation et absent dans le fichier de filtre de blocage.
Exemple de scénario
Supposons que vous deviez encapsuler le fichier JAR ChatLibrary.jar
contenant la classe suivante :
public class ChatManager {
public static void sendMessage(int userId, String message) {...}
}
Votre projet C devra générer un wrapper natif pour ce fichier JAR, ce qui permet à votre application Android native de l'appeler pendant l'exécution. Générez ce code avec le wrapper de bibliothèque à l'aide de la commande suivante :
java -jar lw.jar -i ChatLibrary.jar -o ./generated_code/
La commande précédente génère du code source C dans le répertoire ./generated_code
. Le fichier généré chat_manager.h
contient un code semblable à ce qui suit, qui vous permet d'appeler la bibliothèque dans votre projet :
#include "java/lang/string.h"
typedef struct ChatManager_ ChatManager;
void ChatManager_sendMessage(int32_t user_id, String* message);
Pour obtenir un exemple détaillé de scénario, consultez le guide du wrapper de bibliothèque.
Détails de l'outil
Les sections suivantes fournissent des informations détaillées sur les fonctionnalités du wrapper de bibliothèque.
Structure du répertoire de sortie
Tous les fichiers source et d'en-tête C se trouvent dans des sous-répertoires qui reflètent le nom du package de la classe Java encapsulée. Par exemple, le code du wrapper pour le fichier JAR spécifié java.lang.Integer
est généré dans le répertoire ./java/lang/integer.[h/cc]
.
Vous pouvez contrôler ce comportement de sortie à l'aide du fichier de configuration de l'outil.
Cycle de vie des objets
Les objets Java sont représentés dans le code C par des pointeurs opaques, appelés wrappers. Un wrapper gère une référence JNI pour un objet Java correspondant. Un wrapper peut être créé dans les scénarios suivants :
- En encapsulant une référence JNI existante via l'appel de la fonction
MyClass_wrapJniReference(jobject jobj)
. La fonction n'est pas propriétaire de la référence fournie, mais crée sa propre référence JNI globale. - En créant un objet équivalent à l'appel d'un constructeur en Java :
MyClass_construct()
- En renvoyant un nouveau wrapper à partir d'une fonction, par exemple :
Score* Leaderboard_getScore(Leaderboard* instance, String* leaderboard_name)
Vous devez détruire tous les wrappers lorsqu'ils ne sont plus utilisés. Pour ce faire, appelez la fonction dédiée destroy()
MyClass_destroy(MyClass* instance)
.
Les fonctions qui renvoient des wrappers leur allouent une nouvelle mémoire à chaque appel, même si les wrappers représentent la même instance Java.
Par exemple, lorsque la méthode Java Singleton.getInstance()
renvoie toujours la même instance, la fonction équivalente côté C crée une autre instance de wrapper pour la même instance 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.
Gérer les classes non référencées
Lorsqu'une classe est introuvable dans un fichier JAR fourni, le wrapper de bibliothèque crée une implémentation de base composée d'un pointeur opaque et des méthodes suivantes :
wrapJniReference()
getJniReference()
destroy()
Détails sur la génération de code
Lors de son exécution, le wrapper de bibliothèque génère du code C en fonction des symboles publics qui se trouvent dans le ou les fichiers JAR que vous fournissez à l'outil. Le code C généré peut présenter des différences par rapport au code Java encapsulé. Par exemple, C n'est pas compatible avec certaines fonctionnalités telles qu'OOP, les types génériques, la surcharge de méthodes ou d'autres fonctionnalités Java.
Le code C généré reflétant ces situations peut être différent du type de code attendu par les développeurs en langage C. Les exemples des sections suivantes fournissent du contexte sur la manière dont l'outil peut générer du code C à partir du code Java. Remarque : dans les extraits de code, les exemples suivants incluent des extraits de code C/C++ et Java. Ces extraits visent uniquement à montrer comment l'outil génère du code pour chaque situation.
Classes
Les classes sont représentées par des pointeurs opaques en C :
C/C++
typedef struct MyClass_ MyClass;
Java
public class MyClass { ... }
Les instances de pointeurs opaques sont appelées wrappers. L'outil de wrapper génère des fonctions d'assistance supplémentaires pour chaque classe. Pour l'exemple de classe MyClass
précédent, les fonctions suivantes sont générées :
// 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);
Constructeurs
Les classes comportant des constructeurs publics ou par défaut sont représentées à l'aide de fonctions spéciales :
C/C++
MyClass* MyClass_construct(String* data);
Java
public class MyClass {
public MyClass(String data) { ... }
}
Méthodes
Les méthodes sont représentées par des fonctions normales. Le nom d'une fonction contient le nom de la classe d'origine. Les fonctions qui représentent des méthodes d'instance non statiques ont comme premier paramètre un pointeur vers une structure représentant un objet Java, au nom duquel la fonction est appelée. Cette approche est analogue au pointeur this
.
C/C++
Result* MyClass_doAction(const MyClass* my_class_instance, int32_t action_id, String* data);
int32_t MyClass_doAction(int32_t a, int32_t b);
Java
public class MyClass {
public Result doAction(int actionId, String data) { ... }
public static int doCalculations(int a, int b) { ... }
}
Classes internes
Les classes internes sont représentées étroitement par les classes normales, sauf que le nom de la structure C correspondante contient les noms enchaînés des classes externes :
C/C++
typedef struct MyClass_InnerClass_ MyClass_InnerClass;
Java
public class MyClass {
public class InnerClass {...}
}
Méthodes des classes internes
Les méthodes des classes internes sont représentées comme suit :
C/C++
bool MyClass_InnerClass_setValue(MyClass_InnerClass* my_class_inner_class_instance, int32_t value);
Java
public class MyClass {
public class InnerClass {
public boolean setValue(int value) { ... }
}
}
Types génériques
Le wrapper de bibliothèque n'encapsule pas directement les types génériques. Au lieu de cela, l'outil ne génère des wrappers que pour les instanciations de types génériques.
Par exemple, lorsqu'une classe MyGeneric<T>
est présente dans une API et qu'il existe deux instanciations de cette classe, telles que MyGeneric<Integer>
et MyGeneric<String>
, des wrappers pour ces deux instanciations sont générés. Autrement dit, vous ne pouvez pas créer d'instanciations de type MyGeneric<T>
à l'aide de configurations de type différentes. Consultez l'exemple suivant :
C/C++
// 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);
Java
public class Result<T> {
public T getResult();
}
public class DataProcessor {
public Result<Integer> processIntegerData();
public Result<Float> processFloatData();
}
Implémenter des interfaces
Implémentez une interface C en appelant implementInterface()
et en fournissant une fonction de rappel pour chaque méthode d'interface. Seules les interfaces peuvent être implémentées de cette manière, et les classes abstraites ne sont pas acceptées. Consultez l'exemple suivant :
C/C++
// 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);
Java
public interface Observer {
void onAction1();
void onAction2(int data);
}
public class Subject {
public void registerObserver(Observer observer);
}
Exemple d'utilisation :
void onAction1() {
// Handle action 1
}
void onAction2(int32_t data) {
// Handle action 2
}
Observer* observer = Observer_implementInterface(onAction1, onAction2);
Subject_registerObserver(subject, observer);
Limites
L'outil de wrapper de bibliothèque est en version bêta. Les limites suivantes peuvent s'appliquer :
Constructions Java non prises en charge
La version bêta du wrapper de bibliothèque n'est pas compatible avec les constructions suivantes :
Surcharge des méthodes
Le langage C ne permet pas de déclarer deux fonctions portant le même nom. Si la classe utilise la surcharge des méthodes, le code C généré n'est pas compilé. La solution consiste à n'utiliser qu'une seule méthode avec un ensemble de paramètres suffisant . Les fonctions restantes peuvent être exclues à l'aide de filtres. Cela s'applique également aux constructeurs.
Méthodes modélisées
Champs autres que
static final int
etstatic final String
Tableaux
Conflits de noms potentiels
En raison de la représentation des classes Java dans le code C, des conflits de noms peuvent survenir dans de très rares cas. Par exemple, une classe Foo<Bar>
et une classe interne Bar
dans une classe Foo
sont représentées par le même symbole dans C :
typedef struct Foo_Bar_ Foo_Bar;
Assistance
Si vous rencontrez un problème avec le wrapper de bibliothèque, veuillez nous en informer.
Parcourir les bugs | Signaler un bug |
---|---|
Ingénierie | bug_report |
Documentation | bug_report |