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 et static 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
Documentation