blob: 5a9bde92f8bd085f1fa51714b9625a11fbc75f74 [file] [log] [blame]
// Copyright 2017 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef STARBOARD_ANDROID_SHARED_JNI_ENV_EXT_H_
#define STARBOARD_ANDROID_SHARED_JNI_ENV_EXT_H_
#include <android/native_activity.h>
#include <jni.h>
#include <cstdarg>
#include <cstring>
#include <string>
#include "starboard/common/log.h"
namespace starboard {
namespace android {
namespace shared {
// An extension to JNIEnv to simplify making JNI calls.
//
// Call the static Get() method to get an instance that is already attached to
// the JVM in the current thread.
//
// This extends the JNIEnv structure, which already has a C++ interface for
// calling JNI methods, so any JNIEnv method can be called directly on this.
//
// There are convenience methods to lookup and call Java methods on object
// instances in a single step, with even simpler methods to call Java methods on
// the StarboardBridge.
struct JniEnvExt : public JNIEnv {
// Warning: use __android_log_write for logging in this file to avoid infinite
// recursion.
// One-time initialization to be called before starting the application.
static void Initialize(JniEnvExt* jni_env, jobject starboard_bridge);
// Called right before each native thread is about to be shutdown.
static void OnThreadShutdown();
// Returns the thread-specific instance of JniEnvExt.
static JniEnvExt* Get();
// Returns the StarboardBridge object.
jobject GetStarboardBridge();
// Lookup the class of an object and find a field in it.
jfieldID GetStaticFieldIDOrAbort(jclass clazz,
const char* name,
const char* sig) {
jfieldID field = GetStaticFieldID(clazz, name, sig);
AbortOnException();
return field;
}
jfieldID GetFieldIDOrAbort(jobject obj, const char* name, const char* sig) {
jclass clazz = GetObjectClass(obj);
AbortOnException();
jfieldID field = GetFieldID(clazz, name, sig);
AbortOnException();
DeleteLocalRef(clazz);
return field;
}
jint GetEnumValueOrAbort(jclass clazz, const char* name) {
jfieldID field = GetStaticFieldIDOrAbort(clazz, name, "I");
jint enum_value = GetStaticIntField(clazz, field);
AbortOnException();
return enum_value;
}
// Lookup the class of an object and find a method in it.
jmethodID GetObjectMethodIDOrAbort(jobject obj,
const char* name,
const char* sig) {
jclass clazz = GetObjectClass(obj);
AbortOnException();
jmethodID method_id = GetMethodID(clazz, name, sig);
AbortOnException();
DeleteLocalRef(clazz);
return method_id;
}
jmethodID GetStaticMethodIDOrAbort(jclass clazz,
const char* name,
const char* sig) {
jmethodID method = GetStaticMethodID(clazz, name, sig);
AbortOnException();
return method;
}
jobject GetObjectArrayElementOrAbort(jobjectArray array, jsize index) {
jobject result = GetObjectArrayElement(array, index);
AbortOnException();
return result;
}
// Find a class by name using the application's class loader. This can load
// both system classes and application classes, even when not in a JNI
// stack frame (e.g. in a native thread that was attached the the JVM).
// https://developer.android.com/training/articles/perf-jni.html#faq_FindClass
jclass FindClassExtOrAbort(const char* name);
jclass FindClassOrAbort(const char* name) {
jclass result = FindClass(name);
AbortOnException();
return result;
}
// Convenience method to lookup and call a constructor.
jobject NewObjectOrAbort(const char* class_name, const char* sig, ...) {
va_list argp;
va_start(argp, sig);
jclass clazz = FindClassExtOrAbort(class_name);
jmethodID methodID = GetMethodID(clazz, "<init>", sig);
AbortOnException();
jobject result = NewObjectV(clazz, methodID, argp);
AbortOnException();
DeleteLocalRef(clazz);
va_end(argp);
return result;
}
// Constructs a new java.lang.String object from an array of characters in
// standard UTF-8 encoding. This differs from JNIEnv::NewStringUTF() which
// takes JNI modified UTF-8.
jstring NewStringStandardUTFOrAbort(const char* bytes) {
const jstring charset = NewStringUTF("UTF-8");
AbortOnException();
const jbyteArray byte_array = NewByteArrayFromRaw(
reinterpret_cast<const jbyte*>(bytes), strlen(bytes));
AbortOnException();
jstring result = static_cast<jstring>(NewObjectOrAbort(
"java/lang/String", "([BLjava/lang/String;)V", byte_array, charset));
DeleteLocalRef(byte_array);
DeleteLocalRef(charset);
return result;
}
// Returns a std::string representing the jstring in standard UTF-8 encoding.
// This differs from JNIEnv::GetStringUTFChars() which returns modified UTF-8.
// Also, the buffer of the returned bytes is managed by the std::string object
// so it is not necessary to release it with JNIEnv::ReleaseStringUTFChars().
std::string GetStringStandardUTFOrAbort(jstring str) {
if (str == NULL) {
return std::string();
}
const jstring charset = NewStringUTF("UTF-8");
AbortOnException();
const jbyteArray byte_array =
static_cast<jbyteArray>(CallObjectMethodOrAbort(
str, "getBytes", "(Ljava/lang/String;)[B", charset));
jsize array_length = GetArrayLength(byte_array);
AbortOnException();
void* bytes = GetPrimitiveArrayCritical(byte_array, NULL);
AbortOnException();
std::string result(static_cast<const char*>(bytes), array_length);
ReleasePrimitiveArrayCritical(byte_array, bytes, JNI_ABORT);
AbortOnException();
DeleteLocalRef(byte_array);
DeleteLocalRef(charset);
return result;
}
// Convenience methods to lookup and read a field or call a method all at once:
// Get[Type]FieldOrAbort() takes a jobject of an instance.
// Call[Type]MethodOrAbort() takes a jobject of an instance.
// CallStarboard[Type]MethodOrAbort() to call methods on the StarboardBridge.
#define X(_jtype, _jname) \
_jtype Get##_jname##FieldOrAbort(jobject obj, const char* name, \
const char* sig) { \
_jtype result = \
Get##_jname##Field(obj, GetFieldIDOrAbort(obj, name, sig)); \
AbortOnException(); \
return result; \
} \
\
_jtype GetStatic##_jname##FieldOrAbort(const char* class_name, \
const char* name, const char* sig) { \
jclass clazz = FindClassExtOrAbort(class_name); \
_jtype result = GetStatic##_jname##FieldOrAbort(clazz, name, sig); \
DeleteLocalRef(clazz); \
return result; \
} \
\
_jtype GetStatic##_jname##FieldOrAbort(jclass clazz, const char* name, \
const char* sig) { \
_jtype result = GetStatic##_jname##Field( \
clazz, GetStaticFieldIDOrAbort(clazz, name, sig)); \
AbortOnException(); \
return result; \
} \
\
_jtype Call##_jname##MethodOrAbort(jobject obj, const char* name, \
const char* sig, ...) { \
va_list argp; \
va_start(argp, sig); \
_jtype result = Call##_jname##MethodVOrAbort( \
obj, GetObjectMethodIDOrAbort(obj, name, sig), argp); \
va_end(argp); \
return result; \
} \
\
_jtype CallStarboard##_jname##MethodOrAbort(const char* name, \
const char* sig, ...) { \
va_list argp; \
va_start(argp, sig); \
jobject obj = GetStarboardBridge(); \
_jtype result = Call##_jname##MethodVOrAbort( \
obj, GetObjectMethodIDOrAbort(obj, name, sig), argp); \
va_end(argp); \
return result; \
} \
\
_jtype CallStatic##_jname##MethodOrAbort( \
const char* class_name, const char* method_name, const char* sig, ...) { \
va_list argp; \
va_start(argp, sig); \
jclass clazz = FindClassExtOrAbort(class_name); \
_jtype result = CallStatic##_jname##MethodVOrAbort( \
clazz, GetStaticMethodIDOrAbort(clazz, method_name, sig), argp); \
DeleteLocalRef(clazz); \
va_end(argp); \
return result; \
} \
\
_jtype Call##_jname##MethodVOrAbort(jobject obj, jmethodID methodID, \
va_list args) { \
_jtype result = Call##_jname##MethodV(obj, methodID, args); \
AbortOnException(); \
return result; \
} \
\
_jtype CallStatic##_jname##MethodVOrAbort(jclass clazz, jmethodID methodID, \
va_list args) { \
_jtype result = CallStatic##_jname##MethodV(clazz, methodID, args); \
AbortOnException(); \
return result; \
}
X(jobject, Object)
X(jboolean, Boolean)
X(jbyte, Byte)
X(jchar, Char)
X(jshort, Short)
X(jint, Int)
X(jlong, Long)
X(jfloat, Float)
X(jdouble, Double)
#undef X
void CallVoidMethod(jobject obj, const char* name, const char* sig, ...) {
va_list argp;
va_start(argp, sig);
CallVoidMethodV(obj, GetObjectMethodIDOrAbort(obj, name, sig), argp);
va_end(argp);
}
void CallVoidMethodOrAbort(jobject obj,
const char* name,
const char* sig,
...) {
va_list argp;
va_start(argp, sig);
CallVoidMethodVOrAbort(obj, GetObjectMethodIDOrAbort(obj, name, sig), argp);
va_end(argp);
}
void CallVoidMethodVOrAbort(jobject obj, jmethodID methodID, va_list args) {
CallVoidMethodV(obj, methodID, args);
AbortOnException();
}
void CallStarboardVoidMethod(const char* name, const char* sig, ...) {
va_list argp;
va_start(argp, sig);
jobject obj = GetStarboardBridge();
CallVoidMethodV(obj, GetObjectMethodIDOrAbort(obj, name, sig), argp);
va_end(argp);
}
void CallStarboardVoidMethodOrAbort(const char* name, const char* sig, ...) {
va_list argp;
va_start(argp, sig);
jobject obj = GetStarboardBridge();
CallVoidMethodVOrAbort(obj, GetObjectMethodIDOrAbort(obj, name, sig), argp);
va_end(argp);
}
void CallStaticVoidMethod(const char* class_name,
const char* method_name,
const char* sig,
...) {
va_list argp;
va_start(argp, sig);
jclass clazz = FindClassExtOrAbort(class_name);
CallStaticVoidMethodV(
clazz, GetStaticMethodIDOrAbort(clazz, method_name, sig), argp);
DeleteLocalRef(clazz);
va_end(argp);
}
void CallStaticVoidMethodOrAbort(const char* class_name,
const char* method_name,
const char* sig,
...) {
va_list argp;
va_start(argp, sig);
jclass clazz = FindClassExtOrAbort(class_name);
CallStaticVoidMethodV(
clazz, GetStaticMethodIDOrAbort(clazz, method_name, sig), argp);
AbortOnException();
DeleteLocalRef(clazz);
va_end(argp);
}
jstring GetStringFieldOrAbort(jobject obj, const char* name) {
return static_cast<jstring>(
GetObjectFieldOrAbort(obj, name, "Ljava/lang/String;"));
}
jstring GetStaticStringFieldOrAbort(const char* class_name,
const char* name) {
return static_cast<jstring>(
GetStaticObjectFieldOrAbort(class_name, name, "Ljava/lang/String;"));
}
jstring GetStaticStringFieldOrAbort(jclass clazz, const char* name) {
return static_cast<jstring>(
GetStaticObjectFieldOrAbort(clazz, name, "Ljava/lang/String;"));
}
// Convenience method to create a j[Type]Array from raw, native data. It is
// the responsibility of clients to free the returned array when done with it
// by manually calling |DeleteLocalRef| on it.
#define X(_jtype, _jname) \
_jtype##Array New##_jname##ArrayFromRaw(const _jtype* data, jsize size) { \
SB_DCHECK(data); \
SB_DCHECK(size >= 0); \
_jtype##Array j_array = New##_jname##Array(size); \
SB_CHECK(j_array) << "Out of memory making new array"; \
Set##_jname##ArrayRegion(j_array, 0, size, data); \
return j_array; \
}
X(jboolean, Boolean)
X(jbyte, Byte)
X(jchar, Char)
X(jshort, Short)
X(jint, Int)
X(jlong, Long)
X(jfloat, Float)
X(jdouble, Double)
#undef X
jobject ConvertLocalRefToGlobalRef(jobject local) {
jobject global = NewGlobalRef(local);
DeleteLocalRef(local);
return global;
}
void AbortOnException() {
if (!ExceptionCheck()) {
return;
}
ExceptionDescribe();
SbSystemBreakIntoDebugger();
}
};
SB_COMPILE_ASSERT(sizeof(JNIEnv) == sizeof(JniEnvExt),
JniEnvExt_must_not_add_fields);
} // namespace shared
} // namespace android
} // namespace starboard
#endif // STARBOARD_ANDROID_SHARED_JNI_ENV_EXT_H_