Awesome
JMI
JNI Modern Interface in C++
Some Java Classes Written in JMI
Features
- Compile time computed signature constant(C++17)
- Support both In & Out parameters for Java methods
- Per class jclass cache, per method jmethodID cache, per field jfieldID cache
- The same C++/Java storage duration: a static java member maps to a static member in C++
- Get rid of local reference leak
- getEnv() at any thread without caring about when to detach
- Signature is generated by compiler only once
- Supports JNI primitive types(jint, jlong etc. but not int, long), JMI's JObject, C/C++ string and array of these types as method parameter type, return type and field type.
- Provide frequently used functions for convenience:
to_string(jstring, JNIEnv*)
,from_string(std::string, JNIEnv*)
,android::application()
- Easy to use. Minimize user code
- Exception handling in every call
Example:
-
Setup java vm in
JNI_OnLoad
:jmi::javaVM(vm);
-
Create a SurfaceTexture:
// define SurfaceTexture tag class in any scope visibile by jmi::JObject<SurfaceTexture>
struct SurfaceTexture : jmi::ClassTag { static constexpr auto name() {return JMISTR("android/graphics/SurfaceTexture");}}; // or JMISTR("android.graphics.SurfaceTexture")
...
GLuint tex = ...
...
jmi::JObject<SurfaceTexture> texture;
if (!texture.create(tex)) {
// texture.error() ...
}
- Create Surface from SurfaceTexture:
struct Surface : jmi::ClassTag { static constexpr auto name() {return JMISTR("android.view.Surface");}}; // '.' or '/'
...
jmi::JObject<Surface> surface;
surface.create(texture);
- Call void method:
texture.call("updateTexImage");
or
texture.call<void>("updateTexImage");
- Call method with output parameters:
float mat4[16]; // or std::array<float, 16>, valarray<float>
texture.call("getTransformMatrix", std::ref(mat4)); // use std::ref() if parameter should be modified by jni method
If out parameter is of type JObject<...>
or it's subclass, std::ref()
is not required because the object does not change, only some fields may be changed. For example:
MediaCodec::BufferInfo bi;
bi.create();
codec.dequeueOutputBuffer(bi, timeout); // bi is of type MediaCodec::BufferInfo&
- Call method with a return type:
auto t = texture.call<jlong>("getTimestamp");
jmethodID Cache
GetMethodID/GetStaticMethodID()
is always called in call/callStatic("methodName", ....)
every time, while it's called only once in overload one call/callStatic<...MTag>(...)
, where MTag
is a subclass of jmi:MethodTag
implementing static const char* name() { return "methodName";}
.
// GetMethodID() will be invoked only once for each method in the scope of MethodTag subclass
struct UpdateTexImage : jmi::MethodTag { static const char* name() {return "updateTexImage";}};
struct GetTimestamp : jmi::MethodTag { static const char* name() {return "getTimestamp";}};
struct GetTransformMatrix : jmi::MethodTag { static const char* name() {return "getTransformMatrix";}};
...
texture.call<UpdateTexImage>(); // or texture.call<void,UpdateTexImage>();
auto t = texture.call<jlong, GetTimestamp>();
texture.call<GetTransformMatrix>(std::ref(mat4)); // use std::ref() if parameter should be modified by jni method
Field API
Field api supports cacheable and uncacheable jfieldID. Field object can be JNI basic types, string, JObject and array of these types.
Cacheable jfieldID through FieldTag
JObject<MyClassTag> obj;
...
struct MyIntField : FieldTag { static const char* name() {return "myIntFieldName";} };
auto ifield = obj.field<jint, MyIntField>();
jfieldID ifid = ifield; // or ifield.id()
ifield.set(1234);
jint ivalue = ifield; // or ifield.get();
// static field is the same except using the static function JObject::staticField
struct MyStrFieldS : FieldTag { static const char* name() {return "myStaticStrFieldName";} };
auto& ifields = JObject<MyClassTag>::staticField<std::string, MyIntFieldS>(); // it can be an ref
jfieldID ifids = ifields; // or ifield.id()
ifields.set("JMI static field test");
ifields = "assign";
std::string ivalues = ifields; // or ifield.get();
Uncacheable jfieldID using field name string directly
auto ifield = obj.field<jint>("myIntFieldName");
...
Writting a C++ Class for a Java Class
Create a class inherits JObject<YouClassTag> or stores it as a member, or use CRTP JObject<YouClass>. Each method implementation is usually less then 2 lines of code. See JMITest and Project AND
Using Signatures Generated by Compiler
The function template auto signature_of<T>()
returns the signature of type T. T can be JMI supported types (except jobject types because the class is determined at runtime), reference_wrapper, void, and function types whose return type and parameter types are of above types.
example:
void native_test_impl(JNIEnv *env , jobject thiz, ...) {}
staitc const JNINativeMethod gMethods[] = {
{"native_test", signature_of(native_test_impl).data(), native_test_impl},
...
};
You may find that a macro can simplify above example:
#define DEFINE_METHOD(M) {#M, signature_of(M##_impl).data(), M##_impl}
staitc const JNINativeMethod gMethods[] = {
DEFINE_METHOD(native_test),
...
}
Known Issues
- If return type and first n arguments of call/call_static are the same, explicitly specifying return type and n arguments type is required
Why JObject is a Template?
- To support per class jclass, per method jmethodID, per field jfieldID cache
Compilers
c++14/17 is required
- g++ >= 4.9.0(except 8.0~8.3)
- clang >= 3.5
- msvc>= 19.0
- icc >= 17.0
TODO
- modern C++ class generator script
MIT License
Copyright (c) 2016-2021 WangBin