Library wrapper for Android APIs Part of Android Game Development Kit.
The library wrapper is a command-line tool (CLI) that generates C-language wrapper code for Android APIs which are written in Java. You can use this code in native Android apps to call Java APIs without needing to manually create a Java Native Interface, or JNI. This tool can simplify development Android apps written primarily in C or C++.
The tool works by generating C code for the public symbols contained in Java Archive (JAR) files you provide, or classes defined in the tool's configuration file, or both. Code generated by the tool does not replace Java APIs; instead, it acts as a bridge between your C code and Java. Your app still requires the Java libraries you wrap to be included in your project.
Download
Download the library wrapper archive and unpack its content to the directory of your choice.
Syntax
The library wrapper tool has the following command line syntax:
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 | Description |
---|---|
-i jar-file-to-be-wrapped |
JAR file to generate C wrapper code. Multiple JARs may be specified,
for example: -i first_library.jar -i second_library.jar...
|
-o output-path |
File system location for the generated code. |
-c config-file |
File system path to the library wrapper configuration file. For details, see the Configuration section. |
-fa allow-list-file |
A path to a filter file where you can specify symbols for the tool to wrap. For details, see the Filter section. |
-fb block-list-file |
A path to a filter file containing symbols excluded from wrapping. For details, see the Filter section. |
--skip_deprecated_symbols |
Instructs the wrapper tool to skip @Deprecated symbols. |
Wrapper configuration file
The library wrapper configuration is a JSON file allowing you to control the code generation process. The file uses the following structure.
{
// 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.
]
},
]
}
Filter files
It may be useful to exclude some symbols from the JAR files you plan to wrap. You can specify a filter file in your configuration to exclude symbols. A filter file is a simple text file where each line defines a symbol to wrap. Filter files use the following syntax:
java-symbol-name java-jni-type-signature
The following is an example filter file:
# 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
You provide the configuration a filter file specifying symbols that
are allowed using the -fa
parameter, and blocked symbols using the -fb
parameter. Both parameters can be used simultaneously. If both filters are
provided, a symbol will be wrapped when it is defined in the allow filter file
and is not present on the block filter file.
Example scenario
Assume that you need to wrap the JAR file ChatLibrary.jar
containing the
following class:
public class ChatManager {
public static void sendMessage(int userId, String message) {...}
}
Your C project requires you to generate a native wrapper for this JAR, enabling your native Android app to call it during runtime. Generate this code using the library wrapper with the following command:
java -jar lw.jar -i ChatLibrary.jar -o ./generated_code/
The preceding command generates C source code to the directory
./generated_code
. The generated file chat_manager.h
contains code similar to
the following, enabling you to call the library in your project:
#include "java/lang/string.h"
typedef struct ChatManager_ ChatManager;
void ChatManager_sendMessage(int32_t user_id, String* message);
For an in-depth example scenario, see the Library wrapper guide.
Tool details
The following sections provide detailed information of the library wrapper's functionality.
Output directory structure
All C source and header files are located in subdirectories that reflect the
package name of the wrapped Java class. For example, wrapper code for the
specified JAR java.lang.Integer
is generated to the directory
./java/lang/integer.[h/cc]
.
You can control this output behavior using the tool's configuration file.
Object lifecycle
Java objects are represented in the C code as opaque pointers, called wrappers. A wrapper manages a JNI reference for a corresponding Java object. A wrapper can be created in the following scenarios:
- By wrapping an existing JNI reference by calling the function
MyClass_wrapJniReference(jobject jobj)
. The function does not take the ownership of the provided reference, but creates its own global JNI reference. - By creating a new object, which is equivalent to calling a constructor in
Java:
MyClass_construct()
- By returning a new wrapper from a function, for example:
Score* Leaderboard_getScore(Leaderboard* instance, String* leaderboard_name)
You need to destroy all wrappers when they're no longer used. To do so, call the dedicated
destroy()
function MyClass_destroy(MyClass* instance)
.
Functions that return wrappers allocate a new memory for them for every call, even if wrappers represent the same Java instance.
For example, when the Java method Singleton.getInstance()
always returns the
same instance, the equivalent function on the C side will create a new instance
of a wrapper for the same Java instance:
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.
Handle unreferenced classes
When a class can't be found in a provided JAR, the libarary wrapper creates a basic implementation consisting of an opaque pointer and the following methods:
wrapJniReference()
getJniReference()
destroy()
Code generation details
When run, the library wrapper generates C code based on the public symbols in the JAR file(s) you provide the tool. Generated C code may exhibit differences from the wrapped Java code. For example, C does not support features like OOP, generic types, method overloading, or other Java features.
The generated C code reflecting these situations may differ from the type of code expected by C developers. The examples in the following sections provide context on how the tool may generate C from Java code. Note: In the code snippets, the following examples include C/C++ and Java code snippets. These snippets are solely intended to demonstrate how the tool generates code for the each given situation.
Classes
Classes are represented as opaque pointers in C:
C/C++
typedef struct MyClass_ MyClass;
Java
public class MyClass { ... }
Instances of opaque pointers are referenced as wrappers. The wrapper tool
generates additional support functions for each class. For the preceding example
class MyClass
, the following functions are generated:
// 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);
Constructors
Classes with public or default constructors are represented using special functions:
C/C++
MyClass* MyClass_construct(String* data);
Java
public class MyClass {
public MyClass(String data) { ... }
}
Methods
Methods are represented as normal functions. The name of a function contains the
original class name. Functions that represent non-static instance methods have
as the first parameter a pointer to a structure representing a Java object, on
behalf of which the function is called. This approach is analogous to the this
pointer.
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) { ... }
}
Inner classes
Inner classes are represented closely to normal classes, except the name of the corresponding C structure contains the chained names of outer classes:
C/C++
typedef struct MyClass_InnerClass_ MyClass_InnerClass;
Java
public class MyClass {
public class InnerClass {...}
}
Inner class methods
Inner class methods are represented as follows:
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) { ... }
}
}
Generic types
The library wrapper does not directly wrap generic types. Instead, the tool only generates wrappers for generic type instantiations.
For example, when a class MyGeneric<T>
exists in an API, and there are two
instantiations of this class, such as MyGeneric<Integer>
and
MyGeneric<String>
, wrappers for those two instantiations are generated. This
means that you cannot create new instantiations of type MyGeneric<T>
using
different type configurations. See the following example:
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();
}
Implement interfaces
Implement a C interface by calling implementInterface()
and providing a
callback function for each interface method. Only interfaces may be implemented
in this manner; classes and abstract classes are not supported. See the
following example:
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);
}
Usage example:
void onAction1() {
// Handle action 1
}
void onAction2(int32_t data) {
// Handle action 2
}
Observer* observer = Observer_implementInterface(onAction1, onAction2);
Subject_registerObserver(subject, observer);
Limitations
The library wrapper tool is in beta. You may encounter the following limitations:
Unsupported Java constructs
The library wrapper beta does not support the following constructs:
Method overloading
The C language doesn’t allow declaring two functions with the same name. If the class uses method overloading, the generated C code won’t compile. The workaround is to use only one method with a sufficient set of parameters. The remaining functions can be filtered out using filters. This also applies to constructors.
Templated methods
Fields other than
static final int
andstatic final String
Arrays
Potential name collisions
Because of how Java classes are represented in C code, there may be name
conflicts in very rare cases. For example, a class Foo<Bar>
and an inner
class Bar
inside a Foo
class are represented by the same symbol in C:
typedef struct Foo_Bar_ Foo_Bar;
Support
If you find an issue with the library wrapper, please let us know.
Browse bugs | File a bug |
---|---|
Engineering | bug_report |
Documentation | bug_report |