Bibliotheks-Wrapper für Android APIs Teil des Android Game Development Kit.

Der Bibliotheks-Wrapper ist ein Befehlszeilentool, das Wrapper-Code in C-Sprache für in Java geschriebene Android APIs generiert. Sie können diesen Code in nativen Android-Apps verwenden, um Java APIs aufzurufen, ohne manuell ein Java Native Interface (JNI) erstellen zu müssen. Dieses Tool kann die Entwicklung von Android-Apps vereinfachen, die hauptsächlich in C oder C++ geschrieben wurden.

Das Tool generiert C-Code für die öffentlichen Symbole, die in den von Ihnen bereitgestellten Java-Archivdateien (JAR-Dateien) enthalten sind, oder für Klassen, die in der Konfigurationsdatei des Tools definiert sind, oder für beides. Vom Tool generierter Code ersetzt keine Java APIs. Er fungiert als Brücke zwischen Ihrem C-Code und Java. Ihre App erfordert weiterhin, dass die von Ihnen verpackten Java-Bibliotheken in Ihrem Projekt enthalten sind.

Herunterladen

Laden Sie das Bibliotheks-Wrapper-Archiv herunter und entpacken Sie seinen Inhalt in das Verzeichnis Ihrer Wahl.

Syntax

Das Bibliotheks-Wrapper-Tool hat die folgende Befehlszeilensyntax:

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]
Parameter Beschreibung
-i jar-file-to-be-wrapped JAR-Datei zum Generieren von C-Wrapper-Code. Es können mehrere JARs angegeben werden, z. B.:
-i first_library.jar -i second_library.jar...
-o output-path Speicherort des Dateisystems für den generierten Code.
-c config-file Dateisystempfad zur Konfigurationsdatei des Bibliotheks-Wrappers. Weitere Informationen finden Sie im Abschnitt Konfiguration.
-fa allow-list-file Ein Pfad zu einer Filterdatei, in der Sie Symbole für das umbrechende Tool angeben können. Weitere Informationen finden Sie im Abschnitt Filter.
-fb block-list-file Ein Pfad zu einer Filterdatei, die Symbole enthält, die vom Zeilenumbruch ausgeschlossen sind. Weitere Informationen finden Sie im Abschnitt Filter.
--skip_deprecated_symbols Weist das Wrapper-Tool an, @veraltet-Symbole zu überspringen.

Wrapper-Konfigurationsdatei

Die Bibliotheks-Wrapper-Konfiguration ist eine JSON-Datei, mit der Sie den Codegenerierungsprozess steuern können. Die Datei hat die folgende Struktur.

{
  // 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.
      ]
    },
  ]
}

Dateien filtern

Es kann sinnvoll sein, einige Symbole aus den JAR-Dateien auszuschließen, die Sie verpacken möchten. Sie können in Ihrer Konfiguration eine Filterdatei angeben, um Symbole auszuschließen. Eine Filterdatei ist eine einfache Textdatei, in der jede Zeile ein Symbol für die Umbrüche definiert. Filterdateien verwenden die folgende Syntax:

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

Hier ist ein Beispiel für eine Filterdatei:

# 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

Sie stellen eine Filterdatei für die Konfiguration bereit. Sie geben über den Parameter -fa zulässige Symbole an und über den Parameter -fb blockierte Symbole. Beide Parameter können gleichzeitig verwendet werden. Wenn beide Filter angegeben sind, wird ein Symbol umschlossen, wenn es in der Zulassungsfilterdatei definiert und in der Blockfilterdatei nicht vorhanden ist.

Beispielszenario

Angenommen, Sie müssen die JAR-Datei ChatLibrary.jar verpacken, die die folgende Klasse enthält:

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

Für Ihr C-Projekt müssen Sie einen nativen Wrapper für diese JAR generieren, damit Ihre native Android-App sie während der Laufzeit aufrufen kann. Generieren Sie diesen Code mithilfe des Bibliotheks-Wrappers mit dem folgenden Befehl:

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

Mit dem vorherigen Befehl wird C-Quellcode für das Verzeichnis ./generated_code generiert. Die generierte Datei chat_manager.h enthält Code ähnlich dem folgenden, mit dem Sie die Bibliothek in Ihrem Projekt aufrufen können:

#include "java/lang/string.h"

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

Ein detailliertes Beispielszenario finden Sie im Leitfaden zu Bibliotheks-Wrappern.

Tool details

In den folgenden Abschnitten finden Sie detaillierte Informationen zur Funktion des Bibliothek-Wrappers.

Struktur des Ausgabeverzeichnisses

Alle C-Quell- und Header-Dateien befinden sich in Unterverzeichnissen, die den Paketnamen der zusammengefassten Java-Klasse widerspiegeln. Beispielsweise wird der Wrapper-Code für die angegebene JAR-java.lang.Integer im Verzeichnis ./java/lang/integer.[h/cc] generiert.

Dieses Ausgabeverhalten können Sie mithilfe der Konfigurationsdatei des Tools steuern.

Objektlebenszyklus

Java-Objekte werden im C-Code als intransparente Pointer dargestellt, die als Wrapper bezeichnet werden. Ein Wrapper verwaltet eine JNI-Referenz für ein entsprechendes Java-Objekt. Ein Wrapper kann in den folgenden Szenarien erstellt werden:

  • Durch Wrapping einer vorhandenen JNI-Referenz durch Aufrufen der Funktion MyClass_wrapJniReference(jobject jobj). Die Funktion übernimmt nicht die Inhaberschaft der angegebenen Referenz, erstellt aber eine eigene globale JNI-Referenz.
  • Durch Erstellen eines neuen Objekts, was dem Aufrufen eines Konstruktors in Java entspricht: MyClass_construct()
  • Durch Rückgabe eines neuen Wrappers von einer Funktion, z. B.: Score* Leaderboard_getScore(Leaderboard* instance, String* leaderboard_name)

Sie müssen alle Wrapper löschen, wenn sie nicht mehr verwendet werden. Dazu rufen Sie die dedizierte destroy()-Funktion MyClass_destroy(MyClass* instance) auf.

Funktionen, die Wrapper zurückgeben, weisen ihnen bei jedem Aufruf einen neuen Arbeitsspeicher zu, auch wenn Wrapper dieselbe Java-Instanz darstellen.

Wenn beispielsweise die Java-Methode Singleton.getInstance() immer dieselbe Instanz zurückgibt, erstellt die entsprechende Funktion auf der C-Seite eine neue Instanz eines Wrappers für dieselbe Java-Instanz:

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.

Nicht referenzierte Klassen verarbeiten

Wenn eine Klasse in einer bereitgestellten JAR-Datei nicht gefunden werden kann, erstellt der Libarary-Wrapper eine grundlegende Implementierung, die aus einem intransparenten Pointer und den folgenden Methoden besteht:

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

Details zur Codegenerierung

Bei der Ausführung generiert der Bibliothek-Wrapper C-Code anhand der öffentlichen Symbole in den JAR-Dateien, die Sie für das Tool bereitstellen. Der generierte C-Code kann sich vom verpackten Java-Code unterscheiden. C unterstützt beispielsweise keine Features wie OOP, generische Typen, Methodenüberlastung oder andere Java-Funktionen.

Der generierte C-Code, der diese Situationen widerspiegelt, kann sich von dem von C-Entwicklern erwarteten Code unterscheiden. Die Beispiele in den folgenden Abschnitten zeigen, wie das Tool C aus Java-Code generieren kann. Hinweis: In den Code-Snippets enthalten die folgenden Beispiele C/C++- und Java-Code-Snippets. Diese Snippets sollen lediglich zeigen, wie das Tool Code für die jeweilige Situation generiert.

Kurse

Klassen werden in C als opake Pointer dargestellt:

C/C++

typedef struct MyClass_ MyClass;

Java

public class MyClass { ... }

Instanzen von intransparenten Zeigern werden als Wrapper bezeichnet. Das Wrapper-Tool generiert zusätzliche Supportfunktionen für jede Klasse. Für die vorherige Beispielklasse MyClass werden die folgenden Funktionen generiert:

// 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);

Konstruktoren

Klassen mit öffentlichen oder Standardkonstruktoren werden durch spezielle Funktionen dargestellt:

C/C++

MyClass* MyClass_construct(String* data);

Java

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

Methoden

Methoden werden als normale Funktionen dargestellt. Der Name einer Funktion enthält den ursprünglichen Klassennamen. Funktionen, die nicht statische Instanzmethoden darstellen, haben als ersten Parameter einen Zeiger auf eine Struktur, die ein Java-Objekt darstellt, in dessen Namen die Funktion aufgerufen wird. Dieser Ansatz entspricht dem this-Zeiger.

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) { ... }
}

Innere Klassen

Innere Klassen werden ähnlich wie normale Klassen dargestellt, mit dem Unterschied, dass der Name der entsprechenden C-Struktur die verketteten Namen der äußeren Klassen enthält:

C/C++

typedef struct MyClass_InnerClass_ MyClass_InnerClass;

Java

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

Methoden der inneren Klasse

Methoden der inneren Klasse werden so dargestellt:

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) { ... }
  }
}

Allgemeine Typen

Der Bibliothek-Wrapper umschließt generische Typen nicht direkt. Stattdessen generiert das Tool nur Wrapper für generische Typinstanziierungen.

Wenn beispielsweise die Klasse MyGeneric<T> in einer API vorhanden ist und es zwei Instanzen dieser Klasse gibt, z. B. MyGeneric<Integer> und MyGeneric<String>, werden Wrapper für diese beiden Instanziierungen generiert. Das bedeutet, dass Sie keine neuen Instanziierungen des Typs MyGeneric<T> mit anderen Typkonfigurationen erstellen können. Hier ein Beispiel:

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();
}

Schnittstellen implementieren

Implementieren Sie eine C-Schnittstelle, indem Sie implementInterface() aufrufen und eine Callback-Funktion für jede Schnittstellenmethode bereitstellen. Nur Schnittstellen dürfen auf diese Weise implementiert werden. Klassen und abstrakte Klassen werden nicht unterstützt. Hier ein Beispiel:

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);
}

Verwendungsbeispiel:

void onAction1() {
  // Handle action 1
}

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

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

Einschränkungen

Das Bibliotheks-Wrapper-Tool befindet sich in der Betaphase. Es können folgende Einschränkungen gelten:

Nicht unterstützte Java-Konstrukte

Die folgenden Konstrukte werden von der Betaversion des Bibliotheks-Wrappers nicht unterstützt:

  • Überlastung der Methode

    Die Programmiersprache C erlaubt es nicht, zwei Funktionen mit demselben Namen zu deklarieren. Wenn die Klasse eine Überlastung der Methode verwendet, wird der generierte C-Code nicht kompiliert. Sie können das Problem umgehen, indem Sie nur eine Methode mit einer ausreichenden Anzahl von Parametern verwenden. Die übrigen Funktionen können mithilfe von Filtern herausgefiltert werden. Dies gilt auch für Konstruktoren.

  • Auf Vorlagen basierende Methoden

  • Andere Felder als static final int und static final String

  • Arrays

Mögliche Namenskonflikte

Aufgrund der Darstellung von Java-Klassen im C-Code kann es in sehr seltenen Fällen zu Namenskonflikten kommen. Beispielsweise werden eine Klasse Foo<Bar> und eine innere Klasse Bar innerhalb einer Klasse Foo durch dasselbe Symbol in C dargestellt: typedef struct Foo_Bar_ Foo_Bar;

Support

Wenn Sie ein Problem mit dem Bibliotheks-Wrapper feststellen, teilen Sie uns dies bitte mit.

Programmfehler durchsuchen Programmfehler melden
Technik
Dokumentation