uc-jni is a Java Native Interface (JNI) wrapper library created C++14 single-header.
- Copy
uc-jni.hpp
to your project. - Include it in your c++ source.
You must call uc::jni::java_vm()
only once at the beginning.
jint JNI_OnLoad(JavaVM * vm, void * __unused reserved)
{
uc::jni::java_vm(vm);
return JNI_VERSION_1_6;
}
If you call uc::jni::find_class()
/uc::jni::get_class()
in native thread ( pthread_create(), std::thread, std::async...), also call uc::jni::replace_with_class_loader_find_class()
.
UC_JNI_DEFINE_JCLASS_ALIAS(YourOwnClass, com/example/YourOwnClass);
jint JNI_OnLoad(JavaVM * vm, void * __unused reserved)
{
uc::jni::java_vm(vm);
// call before find_class() / get_class()
uc::jni::replace_with_class_loader_find_class<YourOwnClass>();
return JNI_VERSION_1_6;
}
see also FindClass in native thread.
You can use helper macros from Ver.1.4.
JNI resources are properly cached and overhead is minimized.
package com.example.uc.ucjnitest;
import java.util.Objects;
public class Point {
public static final Point ZERO = new Point(0, 0);
public static Point add(Point a, Point b)
{
return new Point(a.x + b.x, a.y + b.y);
}
private double x, y;
Point(double x, double y)
{
this.x = x;
this.y = y;
}
Point(Point p)
{
this.x = p.x;
this.y = p.y;
}
double norm()
{
return Math.sqrt(x * x + y * y);
}
void multiply(double v)
{
x *= v;
y *= v;
}
@Override public String toString() {...}
@Override public int hashCode() {...}
@Override public boolean equals(Object o) {...}
}
For the above Java code, in C ++, write as follows.
// Define "jPoint_" class as a wrapper for Java "com/example/uc/ucjnitest/Point" class.
// "jPoint" is defined as an alias for jPoint_*.
UC_JNI_DEFINE_JCLASS(jPoint, com/example/uc/ucjnitest/Point)
{
UC_JNI_DEFINE_JCLASS_STATIC_FINAL_FIELD(jPoint, ZERO)
UC_JNI_DEFINE_JCLASS_STATIC_METHOD(jPoint, add, jPoint, jPoint)
UC_JNI_DEFINE_JCLASS_CONSTRUCTOR(double, double)
UC_JNI_DEFINE_JCLASS_CONSTRUCTOR(jPoint)
UC_JNI_DEFINE_JCLASS_FIELD(double, x)
UC_JNI_DEFINE_JCLASS_FIELD(double, y)
UC_JNI_DEFINE_JCLASS_METHOD(double, norm)
UC_JNI_DEFINE_JCLASS_METHOD(void, multiply, double)
};
It is not necessary to write everything.
As a result, the C ++ wrapper class jPoint
of the com.example.uc.ucjnitest.Point
class was defined.
This can be used as follows.
// If a C++ exception occurs in this, it replaces it with java.lang.Throwable.
uc::jni::exception_guard([&] {
// Constructor Method
// p0,p1,p2 are smart pointer. release automatically.
auto p0 = jPoint_::new_(3, 4); // p0 = new Point(12, 34);
auto p1 = jPoint_::new_(p0); // p1 = new Point(p0);
// get field
TEST_ASSERT_EQUALS(p1->x(), 3); // p1.x == 3
TEST_ASSERT_EQUALS(p1->y(), 4); // p1.y == 4
// call method
TEST_ASSERT_EQUALS(p1->norm(), 5); // p1.norm() == 5
// java.lang.Object methods are already defined.
TEST_ASSERT(p0->equals(p1)); // p0.equals(p1)
// set field
p1->x(30); // p1.x = 30;
p1->y(40); // p1.y = 40;
TEST_ASSERT(!p0->equals(p1)); // !p0.equals(p1)
// get static final field
TEST_ASSERT_EQUALS(jPoint_::ZERO()->x(), 0);
TEST_ASSERT_EQUALS(jPoint_::ZERO()->y(), 0);
// call static method
auto p2 = jPoint_::add(p0, p1);
TEST_ASSERT_EQUALS(p2->x(), 60);
TEST_ASSERT_EQUALS(p2->y(), 80);
// Object.toString() returns std::string.
LOGD << p0->toString() << ", " << p1->toString() << ", " << p2->toString();
});
In addition to the method / field API as in the above sample, various JNI functions such as array operations and string operations are wrapped and provided.
JNIEnv* uc::jni::env()
works correctly from any thread call.
AttachCurrentThread()
, DetachCurrentThread()
are unnecessary because they are called at an appropriate timing.
std::thread t([thiz]{
// This is a safe call.
jclass cls = uc::jni::env()->GetObjectClass(thiz);
:
uc::jni::env()->DeleteLocalRef(cls);
});
uc::jni::local_ref<T>
is an alias for std::unique_ptr<T,>
.
DeleteLocalRef()
is called automatically.
uc-jni functions normally returns a "JNI object" in this form.
// clazz, superClazz will be released automatically.
auto clazz = uc::jni::get_object_class(thiz);
auto superClazz = uc::jni::get_super_class(clazz);
Also, using the helper function uc::jni::make_local()
explicitly calls NewLocalRef()
to create a local reference.
global_ref<T>
is similar to std::shared_ptr<T>
.
A global reference is created by NewGlobalRef()
at the time of assignment, and DeleteGlobalRef()
is called when the reference count reaches 0.
uc::jni::make_global()
is provided as a helper function.
// global_ref<jclass>
auto clazz = uc::jni::make_global(uc::jni::get_object_class(thiz));
// It is also possible to substitute local_ref.
// Ownership does not shift and a new Global Reference can be created.
uc::jni::global_ref<jclass> superClazz = uc::jni::get_super_class(clazz);
superClazz = env->GetSuperclass(clazz.get());
uc::jni::weak_ref<T>
can be handled like std::weak_ptr<T>
.
DeleteWeakGlobalRef()
is called automatically.
uc::jni::weak_ref<jobject> wref = jobj;
uc::jni::local_ref<jobject> tmp = wref.lock();
if (tmp) {
:
:
}
There is also a wrapper function of FindClass()
, but you should not normally use this if you use uc-jni.
// I do not recommend
auto cls = uc::jni::find_class("java/lang/RuntimeException");
uc::jni::get_class()
allocates the cache very efficiently.
Provide very fast access with minimal cost.
// You need to define wrapper classes in macros in advance.
UC_JNI_DEFINE_JCLASS_ALIAS(RuntimeException, java/lang/RuntimeException);
// jclass instance is cached. Must not release it.
jclass cls = uc::jni::get_class<RuntimeException>();
FAQ: Why didn't FindClass find my class?
You can get into trouble if you create a thread yourself (perhaps by calling pthread_create and then attaching it with AttachCurrentThread). Now there are no stack frames from your application. If you call FindClass from this thread, the JavaVM will start in the "system" class loader instead of the one associated with your application, so attempts to find app-specific classes will fail.
// java.lang.ClassNotFoundException is thrown.
std::async(std::launch::async, [] {
auto cls = uc::jni::find_class("com/example/YourOwnClass");
});
There are a few ways to work around this:
-
Do your FindClass lookups once, in JNI_OnLoad, and cache the class references for later use.
UC_JNI_DEFINE_JCLASS_ALIAS(YourOwnClass1, com/example/YourOwnClass1); UC_JNI_DEFINE_JCLASS_ALIAS(YourOwnClass2, com/example/YourOwnClass2); UC_JNI_DEFINE_JCLASS_ALIAS(YourOwnClass3, com/example/YourOwnClass3); : jint JNI_OnLoad(JavaVM * vm, void * __unused reserved) { uc::jni::java_vm(vm); // create class caches uc::jni::get_class<YourOwnClass1>(); uc::jni::get_class<YourOwnClass2>(); uc::jni::get_class<YourOwnClass3>(); : return JNI_VERSION_1_6; }
Class caches created by these calls are only valid with
uc::jni::get_class()
. -
Cache a reference to the ClassLoader object somewhere handy, and issue loadClass calls directly.
UC_JNI_DEFINE_JCLASS_ALIAS(YourOwnClass1, com/example/YourOwnClass1); UC_JNI_DEFINE_JCLASS_ALIAS(YourOwnClass2, com/example/YourOwnClass2); UC_JNI_DEFINE_JCLASS_ALIAS(YourOwnClass3, com/example/YourOwnClass3); jint JNI_OnLoad(JavaVM * vm, void * __unused reserved) { uc::jni::java_vm(vm); // create ClassLoader cache uc::jni::replace_with_class_loader_find_class<YourOwnClass1>(); return JNI_VERSION_1_6; }
uc::jni::replace_with_class_loader_find_class()
replaces the implementation ofuc::jni::find_class()
API withjava.lang.ClassLoader#findClass()
. This is implemented with reference to the code written here.
jstring
and std::basic_string
can convert to each other.
// decltype(jstr) == uc::jni::local_ref<jstring>
auto jstr = uc::jni::to_jstring("Hello World!");
std::string str = uc::jni::to_string(jstr);
Supports C++11 UTF-16 string.
auto jstr = uc::jni::to_jstring(u"こんにちは、世界!"); // japanese
std::u16string str = uc::jni::to_u16string(jstr);
uc::jni::join
is more convenient to create jstring
.
jstring returnString(JNIEnv* env, jobject obj, jstring str)
{
// Arguments can include jstring, std::string, std::u16string, and so on.
return uc::jni::join("[", str, "] received.").release();
}
You have been freed from tedious "type signatures".
UC_JNI_DEFINE_JCLASS_ALIAS(Point, android/graphics/Point);
jfieldID fieldId = uc::jni::get_field_id<Point, int>("x");
UC_JNI_DEFINE_JCLASS_ALIAS(System, java/lang/System);
jmethodID methodId = uc::jni::get_static_method_id<System, void()>("gc");
The following "function object" which is more convenient is recommended.
UC_JNI_DEFINE_JCLASS_ALIAS(System, java/lang/System);
UC_JNI_DEFINE_JCLASS_ALIAS(Point, android/graphics/Point);
void func(Point point)
{
// Point#Point(int, int)
auto newPoint = uc::jni::make_constructor<Point(int,int)>();
auto point = newPoint(10, 20);
// Point#x
auto x = uc::jni::make_field<Point, int>("x");
// valueX = point.x;
int valueX = x.get(point);
// point.x = 100;
x.set(point, 100);
auto gc = uc::jni::make_static_method<System, void()>("gc");
// System.gc();
gc();
}
The above x
and gc
can be treated like jfieldID
, jmethodID
. (copy, hold etc)
Other examples.
// boolean Object.equals(Object)
auto equals = jni::make_method<jobject, bool(jobject)>("equals");
// String Object.toString()
auto toString = jni::make_method<jobject, jstring()>("toString");
auto toString2 = jni::make_method<jobject, std::string()>("toString"); // Convert to std::string and return it.
UC_JNI_DEFINE_JCLASS_ALIAS(UcJniTest, com/example/uc/ucjnitest/UcJniTest);
// String[] UcJniTest.getFieldStringArray()
auto getFieldStringArray = uc::jni::make_method<UcJniTest, uc::jni::array<jstring>()>("getFieldStringArray");
auto getFieldStringArray2 = uc::jni::make_method<UcJniTest, std::vector<std::string>()>("getFieldStringArray"); // Convert to std::vector<std::string>.
jarray
and std::vector
can convert to each other.
// jintArray to std::vector<jint>.
auto intValues = uc::jni::to_vector(iArray);
// std::vector<jint> to local_ref<jintArray>.
auto jintValues = uc::jni::to_jarray(intValues);
// uc::jni::array<jstring> (inherited from jobjectArray) to std::vector<std::string>.
auto stringValues = uc::jni::to_vector<std::string>(sArray);
// std::vector<std::string> to local_ref<uc::jni::array<jstring>>.
auto jstringValues = uc::jni::to_jarray(stringValues);
// create local_ref<jintArray>.
auto array = uc::jni::new_array<jint>(10);
TEST_ASSERT_EQUALS(10, uc::jni::length(array));
// set values.
const int v0[] {10, 11, 12, 13, 14, 15, 16, 17, 18, 19};
uc::jni::set_region(array, 0, 10, v0);
// get values.
int v1[10]{};
uc::jni::get_region(array, 0, 10, v1);
// ReleaseArrayElements( mode = 0 ) is called automatically.
auto elems = uc::jni::get_elements(array);
// iterator access OK
std::copy(std::begin(values), std::end(values), uc::jni::begin(elems));
// Apply `JNI_COMMIT`
uc::jni::commit(elems);
// range based for
for (auto&& e : elems) {
e = 100;
}
// Apply `JNI_ABORT`
uc::jni::set_abort(elems);
The following are read only access.
// ReleaseArrayElements( mode = JNI_ABORT ) is called automatically.
auto elems = uc::jni::get_const_elements(array);
std::equal(std::begin(values), std::end(values), uc::jni::begin(elems));
// It means String [].
auto array = uc::jni::new_array<jstring>(2);
TEST_ASSERT_EQUALS(2, uc::jni::length(array));
// Can be used like unique_ptr<jobjectArray>.
TEST_ASSERT_EQUALS(2, env->GetArrayLength(array.get()));
// set values.
uc::jni::set(array, 0, uc::jni::to_jstring("Hello"));
uc::jni::set(array, 1, uc::jni::to_jstring("World"));
// get values.
TEST_ASSERT_EQUALS("Hello", uc::jni::to_string(uc::jni::get(array, 0)));
TEST_ASSERT_EQUALS("World", uc::jni::to_string(uc::jni::get(array, 1)));
Use uc::jni::array<T>
instead of jobjectArray
.
// String[] UcJniTest.getFieldStringArray()
auto get = uc::jni::make_method<UcJniTest, uc::jni::array<jstring>()>("getFieldStringArray");
// decltype(array) == local_ref<array<jstring>>
auto array = get(thiz);
void func(JNIEnv *env, jobject thiz, jobject monitor)
{
auto s = uc::jni::synchronized(monitor);
:
:
}