A modern, type-safe, header-only, C++14 wrapper for JNI
OTHER License
jni.hpp is a modern, type-safe, header-only, C++14 wrapper for JNI (Java Native Interface). Its aim is to make calling Java from C++, or C++ from Java, convenient and safe, without sacrificing low-level control, as you commonly do with code generation approaches.
Two levels of wrappers are provided.
The low-level wrappers match JNI, type-for-type and function-for-function, with modern C++ equivalents in a jni
namespace. The matching uniformly follows a set of rules.
Rules for Names
jni
namespace.jni::jni_ok
for JNI_OK
.Rules for Types
jni::jboolean
, jni::jint
, jni::jdouble
, etc.) are matched unchanged.jni::jobject
, jni::jclass
, jni::jarray
, etc.) are non-copyable and non-constructible. Their references and pointers automatically convert to base types as expected. (These types do not hide pointer-ness behind a confusing typedef, like the underlying JNI headers do.)jni::jsize
is std::size_t
, not a signed integer. jni.hpp checks for overflow in the necessary places.unique_ptr
, and using
declarations are provided for each specific instantiation, e.g. jni::UniqueGlobalRef
.Call*Method
and CallStatic*Method
, are matched with a single template function, such as jni::CallMethod<ResultType>
and jni::CallStaticMethod<ResultType>
.jni::jvalue
and the Call*MethodV
and Call*MethodA
function families are made redundant by type safety, and omitted.Rules for Parameters and Results
jni::jobject
parameters and results, which are pointers.std::tuple
.T
and length" parameters, an overload is provided that accepts a statically-sized array, std::array<T>
, std::vector<T>
, or (if T
is a character type) std::basic_string<T>
argument. These overloads compute the length automatically.char16_t
replaces jchar
.Rules for Error Handling
jni::PendingJavaException
.std::system_error
s containing std::error_code
s with a custom category.The high-level wrappers provide additional syntactic convenience and type safety when working with non-primitive Java types. They are built around the concept of class tags. A class tag is a C++ type that provides a static Name
method returning a fully-qualified Java class name, thus associating a unique C++ type with the corresponding Java class. For example:
struct Math { static constexpr auto Name() { return "java/lang/Math"; } };
The high-level wrappers consist of a set of classes templated on class tag:
jni::Class<Tag>
, a wrapper for a reference to the Java class associated with the tag.jni::Object<Tag>
, a wrapper for a (possibly-null) reference to an instance of the Java class associated with the tag.jni::Array<E>
, a wrapper for a (possibly-null) reference to a Java array. The element type E
is a jni.hpp primitive type or Object<Tag>
.jni::Constructor<Tag, Args...>
, a wrapper for a constructor for the the Java class associated with the tag. The result type R
and each argument type in Args
is a jni.hpp primitive type or Object<Tag>
.jni::Method<Tag, R (Args...)>
, a wrapper for an instance method of the Java class associated with the tag. The result type R
and each argument type in Args
is a jni.hpp primitive type or Object<Tag>
.jni::StaticMethod<Tag, R (Args...)>
, a wrapper for a static method of the Java class associated with the tag. The result type R
and each argument type in Args
is a jni.hpp primitive type or Object<Tag>
.jni::Field<Tag, T>
, a wrapper for an instance field of the Java class associated with the tag, and providing Get
and Set
methods. The field type T
is a jni.hpp primitive type or Object<Tag>
.jni::StaticField<Tag, T>
, a wrapper for a static field of the Java class associated with the tag, and providing Get
and Set
methods. The field type T
is a jni.hpp primitive type or Object<Tag>
.Registering native methods is a central part of JNI, and jni.hpp provides several features that make this task safer and more convenient. The jni.hpp wrapper method jni::RegisterNatives
has the following signature:
template < class... Methods >
void RegisterNatives(jni::JNIEnv& env, jni::jclass&, const Methods&... methods);
In other words, rather than receiving a length and pointer to an array of JNIMethod
s, it takes a variable number of variable types. This allows jni::RegisterNatives
to type check the methods, ensuring that their type signatures are valid for JNI.
Use the helper function jni::MakeNativeMethod
to construct method arguments for jni::RegisterNatives
. jni::MakeNativeMethod
wraps your method in a try
/ catch
block that translates C++ exceptions to Java exceptions. It is overloaded on the following combinations of arguments:
const char *
name, const char *
signature, and lambda. The type of the first parameter of the lambda must be jni::JNIEnv&
. The type of the second parameter must be either jni::jclass*
(for a native static method) or jni::jobject*
(for a native instance method). The result type must be a JNI primitive type or type convertable to jni::jobject*
. (These requirements are type checked.)const char *
name, const char *
signature, and function pointer. The function has the same parameter and return type requirements as the lambda. In order to guarantee a unique exception-handling wrapper for each unique function pointer, the function pointer must be provided as a template parameter rather than method parameter:jni::MakeNativeMethod<decltype(myFunction), myFunction>(name, signature)
The repetition of myFunction
with decltype
is necessary because it is not possible to infer the function type from the non-type template parameter. You may wish to define and use a macro to avoid the repetition.
const char *
name and lamba whose parameter and return types use high-level jni.hpp wrapper types. In this case, jni.hpp will compute the signature automatically.const char *
name and function pointer whose parameter and return types use high-level jni.hpp wrapper types. Again, jni.hpp will compute the signature automatically, and again, the function pointer must be provided as a template parameter rather than method parameter.Finally, jni.hpp provides a mechanism for registering a "native peer": a long-lived native object corresponding to a Java object, usually created when the Java object is created and destroyed when the Java object's finalizer runs. Between creation and finalization, a pointer to the native peer is stored in a long
field on the Java object. jni.hpp will take care of wrapping lambdas, function pointers, or member function pointers with code that automatically gets the value of this field, casts it to a pointer to the peer, and calls the member function (or passes a reference to the peer as an argument to the lambda or function pointer). See the example code for details.
Example code for both the low-level and high-level wrappers is provided in the examples
subdirectory. This code shows the use of jni.hpp for:
unique_ptr
and variadic templates, introduces no preprocessor macros, and is header-only -- no build step required.