Skip to content

wang-bin/JMI

Repository files navigation

JMI

JNI Modern Interface in C++

中文

Some Java Classes Written in JMI

Build status github

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 or stores it as a member, or use CRTP JObject. 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