Wrapper de biblioteca para APIs do Android Parte do Android Game Development Kit.

O wrapper de biblioteca é uma interface de linha de comando (CLI) que gera código de wrapper da linguagem C para APIs do Android que são programadas em Java. É possível usar esse código em apps Android nativos para chamar APIs Java sem precisar criar manualmente uma interface nativa do Java (link em inglês), ou JNI. Essa ferramenta pode simplificar o desenvolvimento de apps Android escritos principalmente em C ou C++.

Ela gera um código C para os símbolos públicos contidos nos arquivos Java Archive (JAR) que você fornece, para as classes definidas no arquivo de configuração da ferramenta ou para ambos. O código gerado pela ferramenta não substitui APIs Java. Ele funciona como uma ponte entre o código C e o Java. Seu app ainda precisa que as bibliotecas Java unidas sejam incluídas no projeto.

Fazer o download

Faça o download do arquivo do wrapper de biblioteca e descompacte o conteúdo no diretório que preferir.

Sintaxe

A ferramenta wrapper de biblioteca tem a seguinte sintaxe de linha de comando:

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]
Parâmetro Descrição
-i jar-file-to-be-wrapped Arquivo JAR para gerar o código wrapper C. Vários JARs podem ser especificados, por exemplo:
-i first_library.jar -i second_library.jar...
-o output-path Local onde o código gerado fica armazenado no sistema de arquivos.
-c config-file Caminho do sistema que mostra onde o arquivo de configuração do wrapper de biblioteca está. Para saber mais detalhes, consulte a seção de configuração.
-fa allow-list-file Um caminho para um arquivo de filtro em que é possível especificar símbolos para a ferramenta unir. Para mais detalhes, consulte a seção sobre filtros.
-fb block-list-file Um caminho para um arquivo de filtro com símbolos excluídos do wrapper. Para mais detalhes, consulte a seção de filtro.
--skip_deprecated_symbols Instrui a ferramenta de wrapper a pular símbolos @Deprecated.

Arquivo de configuração do wrapper

A configuração do wrapper de biblioteca é um arquivo JSON que permite controlar o processo de geração de códigos. O arquivo usa a estrutura a seguir.

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

Arquivos de filtro

Pode ser útil excluir alguns símbolos dos arquivos JAR que você planeja unir. Você pode especificar um arquivo de filtro na sua configuração para excluir símbolos. Um arquivo de filtro é um arquivo de texto simples em que cada linha define um símbolo a ser unido. Os arquivos de filtro usam a seguinte sintaxe:

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

Confira abaixo um exemplo de arquivo de filtro:

# 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

Você fornece à configuração um arquivo de filtro que especifica os símbolos permitidos usando o parâmetro -fa e os bloqueados usando o parâmetro -fb. Os dois parâmetros podem ser usados simultaneamente. Se ambos os filtros forem fornecidos, um símbolo será unido quando for definido no arquivo de filtro de permissão e não estará presente no arquivo de filtro de bloqueio.

Exemplo

Imagine que você precisa unir o arquivo JAR ChatLibrary.jar contendo a seguinte classe:

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

O projeto C requer que você gere um wrapper nativo para esse JAR, permitindo que o app Android nativo o chame no momento da execução. Gere esse código usando o wrapper de biblioteca com o seguinte comando:

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

O comando anterior gera o código-fonte C para o diretório ./generated_code. O arquivo chat_manager.h gerado contém um código semelhante ao seguinte, permitindo que você chame a biblioteca no projeto:

#include "java/lang/string.h"

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

Para conferir um exemplo detalhado, consulte o Guia do wrapper de biblioteca.

Detalhes da ferramenta

As seções abaixo oferecem informações detalhadas sobre a funcionalidade do wrapper de biblioteca.

Estrutura de diretórios de saída

Todos os arquivos principais e de origem C estão localizados em subdiretórios que refletem o nome do pacote da classe Java unida. Por exemplo, o código wrapper para o JAR java.lang.Integer especificado é gerado para o diretório ./java/lang/integer.[h/cc].

É possível controlar esse comportamento de saída usando o arquivo de configuração da ferramenta.

Ciclo de vida de objetos

Os objetos Java são representados no código C como ponteiros opacos, chamados "wrappers". Um wrapper gerencia uma referência JNI para um objeto Java correspondente. Ele pode ser criado nos seguintes cenários:

  • Ao unir uma referência JNI já existente chamando a função MyClass_wrapJniReference(jobject jobj). Essa função não assume a propriedade da referência fornecida, mas cria a própria referência global de JNI.
  • Ao criar um novo objeto, que é equivalente a chamar um construtor em Java: MyClass_construct().
  • Ao retornar um novo wrapper de uma função, por exemplo: Score* Leaderboard_getScore(Leaderboard* instance, String* leaderboard_name).

É necessário destruir todos os wrappers quando eles não são mais usados. Para fazer isso, chame MyClass_destroy(MyClass* instance) da função destroy().

As funções que retornam wrappers alocam uma nova memória para cada chamada, mesmo que os wrappers representem a mesma instância Java.

Por exemplo, quando o método Java Singleton.getInstance() sempre retorna a mesma instância, a função equivalente no lado C cria uma nova instância de um wrapper para a mesma instância 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.

Processar classes sem referências

Quando uma classe não é encontrada em um JAR fornecido, o wrapper de biblioteca cria uma implementação básica que consiste em um ponteiro opaco e nos seguintes métodos:

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

Detalhes da geração de código

Quando executado, o wrapper de biblioteca gera o código C com base nos símbolos públicos nos arquivos JAR que você fornece para a ferramenta. O código C gerado pode apresentar diferenças em relação ao Java unido. Por exemplo, o C não tem suporte a recursos como OOP, tipos genéricos, sobrecarga de método ou outros recursos do Java.

O código C gerado que reflete essas situações pode ser diferente do tipo de código esperado pelos desenvolvedores. Os exemplos nas seções a seguir fornecem contexto sobre como a ferramenta pode gerar código C com o Java. Observação: os exemplos a seguir incluem snippets de código C/C++ e Java. Esses snippets servem apenas para demonstrar como a ferramenta gera códigos para cada situação específica.

Classes

As classes são representadas como ponteiros opacos (link em inglês) em C:

C/C++

typedef struct MyClass_ MyClass;

Java

public class MyClass { ... }

As instâncias de ponteiros opacos são chamadas de wrappers. A ferramenta wrapper gera outras funções de suporte para cada classe. Para a classe MyClass do exemplo anterior, são geradas as seguintes funçõ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);

Construtores

As classes com construtores públicos ou padrão são representadas por funções especiais:

C/C++

MyClass* MyClass_construct(String* data);

Java

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

Métodos

Os métodos são representados como funções normais. O nome de uma função contém o nome da classe original. As funções que representam métodos de instâncias não estáticos têm como o primeiro parâmetro um ponteiro para uma estrutura que representa um objeto Java, em nome do qual a função é chamada. Essa abordagem é análoga ao ponteiro 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 internas

As classes internas são representadas de maneira similar às normais, exceto pelo nome da estrutura C correspondente, que contém os nomes encadeados de classes externas:

C/C++

typedef struct MyClass_InnerClass_ MyClass_InnerClass;

Java

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

Métodos de classes internas

Os métodos de classes internas são representados da seguinte maneira:

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

Tipos genéricos

O wrapper de biblioteca não une tipos genéricos de forma direta. Em vez disso, a ferramenta só gera wrappers para instanciamentos de tipo genérico.

Por exemplo, quando há uma classe MyGeneric<T> em uma API e há dois instanciamentos dessa classe, como MyGeneric<Integer> e MyGeneric<String>, wrappers são gerados para eles. Isso significa que não é possível criar novos instanciamentos do tipo MyGeneric<T> usando diferentes configurações de tipo. Confira este exemplo:

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

Implementar interfaces

Implemente uma interface C chamando implementInterface() e fornecendo uma função de callback para cada método de interface. Somente interfaces podem ser implementadas dessa forma. Classes abstratas não são aceitas. Confira este exemplo:

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

Exemplo de uso:

void onAction1() {
  // Handle action 1
}

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

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

Limitações

A ferramenta wrapper de biblioteca está na versão Beta. Talvez você encontre estas limitações:

Construções Java sem suporte

O wrapper de biblioteca Beta não tem suporte às seguintes construções:

  • Sobrecarga de método

    A linguagem C não permite declarar duas funções com o mesmo nome. Se a classe usar sobrecarga de método, o código C gerado não será compilado. A solução alternativa é usar apenas um método com um conjunto suficiente de parâmetros. As demais funções podem passar por filtros. Isso também se aplica a construtores.

  • Métodos de modelo

  • Campos que não sejam static final int e static final String

  • Matrizes

Possíveis conflitos de nome

Devido à forma como as classes Java são representadas no código C, pode haver conflitos de nome em casos muito raros. Por exemplo, uma classe Foo<Bar> e uma classe interna Bar dentro de uma Foo são representadas pelo mesmo símbolo em C: typedef struct Foo_Bar_ Foo_Bar;

Suporte

Se você encontrar um problema com o wrapper de biblioteca, entre em contato.

Procurar bugs Informar um bug
Engenharia
Documentação