// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #pragma once // Note: include appropriate system jni.h as found by CMake, not third_party/jni.h. #include #include #include #include #if _WIN32 #include #else #include #include #endif #if _WIN32 #define FFI_PLUGIN_EXPORT __declspec(dllexport) #else #define FFI_PLUGIN_EXPORT #endif #if defined _WIN32 #define thread_local __declspec(thread) #else #define thread_local __thread #endif #ifdef __ANDROID__ #include #endif #ifdef __ANDROID__ #define __ENVP_CAST (JNIEnv**) #else #define __ENVP_CAST (void**) #endif /// Locking functions for windows and pthread. #if defined _WIN32 #include typedef CRITICAL_SECTION MutexLock; typedef CONDITION_VARIABLE ConditionVariable; static inline void init_lock(MutexLock* lock) { InitializeCriticalSection(lock); } static inline void acquire_lock(MutexLock* lock) { EnterCriticalSection(lock); } static inline void release_lock(MutexLock* lock) { LeaveCriticalSection(lock); } static inline void destroy_lock(MutexLock* lock) { DeleteCriticalSection(lock); } static inline void init_cond(ConditionVariable* cond) { InitializeConditionVariable(cond); } static inline void signal_cond(ConditionVariable* cond) { WakeConditionVariable(cond); } static inline void wait_for(ConditionVariable* cond, MutexLock* lock) { SleepConditionVariableCS(cond, lock, INFINITE); } static inline void destroy_cond(ConditionVariable* cond) { // Not available. } static inline void free_mem(void* mem) { CoTaskMemFree(mem); } #elif defined __APPLE__ || defined __LINUX__ || defined __ANDROID__ || \ defined __GNUC__ #include typedef pthread_mutex_t MutexLock; typedef pthread_cond_t ConditionVariable; static inline void init_lock(MutexLock* lock) { pthread_mutex_init(lock, NULL); } static inline void acquire_lock(MutexLock* lock) { pthread_mutex_lock(lock); } static inline void release_lock(MutexLock* lock) { pthread_mutex_unlock(lock); } static inline void destroy_lock(MutexLock* lock) { pthread_mutex_destroy(lock); } static inline void init_cond(ConditionVariable* cond) { pthread_cond_init(cond, NULL); } static inline void signal_cond(ConditionVariable* cond) { pthread_cond_signal(cond); } static inline void wait_for(ConditionVariable* cond, MutexLock* lock) { pthread_cond_wait(cond, lock); } static inline void destroy_cond(ConditionVariable* cond) { pthread_cond_destroy(cond); } static inline void free_mem(void* mem) { free(mem); } #else #error "No locking/condition variable support; Possibly unsupported platform" #endif typedef struct CallbackResult { MutexLock lock; ConditionVariable cond; int ready; jobject object; } CallbackResult; typedef struct JniLocks { MutexLock classLoadingLock; } JniLocks; /// Represents the error when dart-jni layer has already spawned singleton VM. #define DART_JNI_SINGLETON_EXISTS (-99); /// Stores the global state of the JNI. typedef struct JniContext { JavaVM* jvm; jobject classLoader; jmethodID loadClassMethod; jobject currentActivity; jobject appContext; JniLocks locks; } JniContext; // jniEnv for this thread, used by inline functions in this header, // therefore declared as extern. extern thread_local JNIEnv* jniEnv; extern JniContext* jni; /// Types used by JNI API to distinguish between primitive types. enum JniType { booleanType = 0, byteType = 1, shortType = 2, charType = 3, intType = 4, longType = 5, floatType = 6, doubleType = 7, objectType = 8, voidType = 9, }; /// Result type for use by JNI. /// /// If [exception] is null, it means the result is valid. /// It's assumed that the caller knows the expected type in [result]. typedef struct JniResult { jvalue value; jthrowable exception; } JniResult; /// Similar to [JniResult] but for class lookups. typedef struct JniClassLookupResult { jclass value; jthrowable exception; } JniClassLookupResult; /// Similar to [JniResult] but for method/field ID lookups. typedef struct JniPointerResult { const void* value; jthrowable exception; } JniPointerResult; /// JniExceptionDetails holds 2 jstring objects, one is the result of /// calling `toString` on exception object, other is stack trace; typedef struct JniExceptionDetails { jstring message; jstring stacktrace; } JniExceptionDetails; /// This struct contains functions which wrap method call / field access conveniently along with /// exception checking. /// /// Flutter embedding checks for pending JNI exceptions before an FFI transition, which requires us /// to check for and clear the exception before returning to dart code, which requires these functions /// to return result types. typedef struct JniAccessorsStruct { JniClassLookupResult (*getClass)(char* internalName); JniPointerResult (*getFieldID)(jclass cls, char* fieldName, char* signature); JniPointerResult (*getStaticFieldID)(jclass cls, char* fieldName, char* signature); JniPointerResult (*getMethodID)(jclass cls, char* methodName, char* signature); JniPointerResult (*getStaticMethodID)(jclass cls, char* methodName, char* signature); JniResult (*newObject)(jclass cls, jmethodID ctor, jvalue* args); JniResult (*newPrimitiveArray)(jsize length, int type); JniResult (*newObjectArray)(jsize length, jclass elementClass, jobject initialElement); JniResult (*getArrayElement)(jarray array, int index, int type); JniResult (*callMethod)(jobject obj, jmethodID methodID, int callType, jvalue* args); JniResult (*callStaticMethod)(jclass cls, jmethodID methodID, int callType, jvalue* args); JniResult (*getField)(jobject obj, jfieldID fieldID, int callType); JniResult (*getStaticField)(jclass cls, jfieldID fieldID, int callType); JniExceptionDetails (*getExceptionDetails)(jthrowable exception); } JniAccessorsStruct; FFI_PLUGIN_EXPORT JniAccessorsStruct* GetAccessors(); FFI_PLUGIN_EXPORT JavaVM* GetJavaVM(void); FFI_PLUGIN_EXPORT JNIEnv* GetJniEnv(void); /// Spawn a JVM with given arguments. /// /// Returns JNI_OK on success, and one of the documented JNI error codes on /// failure. It returns DART_JNI_SINGLETON_EXISTS if an attempt to spawn multiple /// JVMs is made, even if the underlying API potentially supports multiple VMs. FFI_PLUGIN_EXPORT int SpawnJvm(JavaVMInitArgs* args); /// Returns Application classLoader (on Android), /// which can be used to load application and platform classes. /// /// On other platforms, NULL is returned. FFI_PLUGIN_EXPORT jobject GetClassLoader(void); /// Returns application context on Android. /// /// On other platforms, NULL is returned. FFI_PLUGIN_EXPORT jobject GetApplicationContext(void); /// Returns current activity of the app on Android. FFI_PLUGIN_EXPORT jobject GetCurrentActivity(void); static inline void attach_thread() { if (jniEnv == NULL) { (*jni->jvm)->AttachCurrentThread(jni->jvm, __ENVP_CAST & jniEnv, NULL); } } /// Load class into [cls] using platform specific mechanism static inline void load_class_platform(jclass* cls, const char* name) { #ifdef __ANDROID__ jstring className = (*jniEnv)->NewStringUTF(jniEnv, name); *cls = (*jniEnv)->CallObjectMethod(jniEnv, jni->classLoader, jni->loadClassMethod, className); (*jniEnv)->DeleteLocalRef(jniEnv, className); #else *cls = (*jniEnv)->FindClass(jniEnv, name); #endif } static inline void load_class_global_ref(jclass* cls, const char* name) { if (*cls == NULL) { jclass tmp = NULL; acquire_lock(&jni->locks.classLoadingLock); if (*cls == NULL) { load_class_platform(&tmp, name); if (!(*jniEnv)->ExceptionCheck(jniEnv)) { *cls = (*jniEnv)->NewGlobalRef(jniEnv, tmp); (*jniEnv)->DeleteLocalRef(jniEnv, tmp); } } release_lock(&jni->locks.classLoadingLock); } } static inline void load_method(jclass cls, jmethodID* res, const char* name, const char* sig) { if (*res == NULL) { *res = (*jniEnv)->GetMethodID(jniEnv, cls, name, sig); } } static inline void load_static_method(jclass cls, jmethodID* res, const char* name, const char* sig) { if (*res == NULL) { *res = (*jniEnv)->GetStaticMethodID(jniEnv, cls, name, sig); } } static inline void load_field(jclass cls, jfieldID* res, const char* name, const char* sig) { if (*res == NULL) { *res = (*jniEnv)->GetFieldID(jniEnv, cls, name, sig); } } static inline void load_static_field(jclass cls, jfieldID* res, const char* name, const char* sig) { if (*res == NULL) { *res = (*jniEnv)->GetStaticFieldID(jniEnv, cls, name, sig); } } static inline jobject to_global_ref(jobject ref) { jobject g = (*jniEnv)->NewGlobalRef(jniEnv, ref); (*jniEnv)->DeleteLocalRef(jniEnv, ref); return g; } // These functions are useful for C+Dart bindings, and not required for pure dart bindings. FFI_PLUGIN_EXPORT JniContext* GetJniContextPtr(); /// For use by jni_gen's generated code /// don't use these. // these 2 fn ptr vars will be defined by generated code library extern JniContext* (*context_getter)(void); extern JNIEnv* (*env_getter)(void); // this function will be exported by generated code library // it will set above 2 variables. FFI_PLUGIN_EXPORT void setJniGetters(struct JniContext* (*cg)(void), JNIEnv* (*eg)(void)); static inline void load_env() { if (jniEnv == NULL) { jni = context_getter(); jniEnv = env_getter(); } } static inline jthrowable check_exception() { jthrowable exception = (*jniEnv)->ExceptionOccurred(jniEnv); if (exception != NULL) (*jniEnv)->ExceptionClear(jniEnv); if (exception == NULL) return NULL; return to_global_ref(exception); } static inline JniResult to_global_ref_result(jobject ref) { JniResult result; result.exception = check_exception(); if (result.exception == NULL) { result.value.l = to_global_ref(ref); } return result; }