| // 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 | |
| #include "include/dart_api_dl.h" | |
| #include "jni_constants.h" | |
| // Note: include appropriate system jni.h as found by CMake, not third_party/jni.h. | |
| #include <jni.h> | |
| #include <stdint.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #if defined(__ANDROID__) | |
| #include <android/log.h> | |
| #define LOGF(...) __android_log_print(ANDROID_LOG_FATAL, "DartJNI", __VA_ARGS__) | |
| #else | |
| #define LOGF(...) \ | |
| do { \ | |
| fprintf(stderr, "DartJNI [FATAL] "); \ | |
| fprintf(stderr, __VA_ARGS__); \ | |
| fprintf(stderr, "\n"); \ | |
| } while (0) | |
| #endif | |
| /// Locking functions for windows and pthread. | |
| #if defined(_WIN32) | |
| #include <windows.h> | |
| #define FFI_PLUGIN_EXPORT __declspec(dllexport) | |
| #define THREAD_LOCAL __declspec(thread) | |
| typedef SRWLOCK MutexLock; | |
| typedef CONDITION_VARIABLE ConditionVariable; | |
| static inline void init_lock(MutexLock* lock) { | |
| InitializeSRWLock(lock); | |
| } | |
| static inline void acquire_lock(MutexLock* lock) { | |
| AcquireSRWLockExclusive(lock); | |
| } | |
| static inline void release_lock(MutexLock* lock) { | |
| ReleaseSRWLockExclusive(lock); | |
| } | |
| static inline void destroy_lock(MutexLock* lock) { | |
| // Not available on Windows. | |
| } | |
| 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) { | |
| SleepConditionVariableSRW(cond, lock, INFINITE, 0); | |
| } | |
| static inline void destroy_cond(ConditionVariable* cond) { | |
| // Not available on Windows. | |
| } | |
| static inline void free_mem(void* mem) { | |
| CoTaskMemFree(mem); | |
| } | |
| #else | |
| #include <pthread.h> | |
| #include <unistd.h> | |
| #define FFI_PLUGIN_EXPORT | |
| #define THREAD_LOCAL __thread | |
| 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); | |
| } | |
| #endif | |
| typedef struct CallbackResult { | |
| MutexLock lock; | |
| ConditionVariable cond; | |
| int ready; | |
| jobject object; | |
| } CallbackResult; | |
| typedef struct JniLocks { | |
| MutexLock classLoadingLock; | |
| } JniLocks; | |
| /// Stores the global state of the JNI. | |
| typedef struct JniContext { | |
| JavaVM* jvm; | |
| jobject classLoader; | |
| jmethodID loadClassMethod; | |
| 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; | |
| /// Handling the lifetime of thread-local jniEnv. | |
| #if !defined(_WIN32) | |
| extern pthread_key_t tlsKey; | |
| #endif | |
| static inline void detach_thread(void* data) { | |
| jniEnv = NULL; | |
| if (*jni->jvm) { | |
| (*jni->jvm)->DetachCurrentThread(jni->jvm); | |
| } | |
| } | |
| static inline void attach_thread() { | |
| if (jniEnv == NULL) { | |
| if (jni->jvm == NULL) { | |
| LOGF( | |
| "JNI is not initialized. Are you trying to invoke a Java API from" | |
| " Dart code too early, before 'main()' (such as during Dart plugin" | |
| " class registration)?"); | |
| } | |
| (*jni->jvm)->AttachCurrentThread(jni->jvm, &jniEnv, NULL); | |
| #if !defined(_WIN32) | |
| pthread_setspecific(tlsKey, &jniEnv); | |
| #endif | |
| } | |
| } | |
| /// 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; | |
| FFI_PLUGIN_EXPORT | |
| JniClassLookupResult FindClass(const char* name); | |
| FFI_PLUGIN_EXPORT | |
| JniExceptionDetails GetExceptionDetails(jthrowable exception); | |
| 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 JniErrorCode 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); | |
| /// Load class into [cls] using platform specific mechanism | |
| static inline void load_class_platform(jclass* cls, const char* name) { | |
| #if defined(__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; | |
| } | |
| 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; | |
| } | |
| FFI_PLUGIN_EXPORT intptr_t InitDartApiDL(void* data); | |
| FFI_PLUGIN_EXPORT int64_t GetMainPortId(); | |
| FFI_PLUGIN_EXPORT int64_t GetCurrentIsolateId(); | |
| FFI_PLUGIN_EXPORT | |
| JniResult DartException__ctor(jstring message, jthrowable cause); | |
| FFI_PLUGIN_EXPORT | |
| JniResult PortContinuation__ctor(int64_t j); | |
| FFI_PLUGIN_EXPORT void resultFor(CallbackResult* result, jobject object); | |
| FFI_PLUGIN_EXPORT | |
| Dart_FinalizableHandle newJObjectFinalizableHandle(Dart_Handle object, | |
| jobject reference, | |
| jobjectRefType refType); | |
| FFI_PLUGIN_EXPORT | |
| Dart_FinalizableHandle newBooleanFinalizableHandle(Dart_Handle object, | |
| bool* reference); | |
| FFI_PLUGIN_EXPORT | |
| void deleteFinalizableHandle(Dart_FinalizableHandle finalizableHandle, | |
| Dart_Handle object); | |
| FFI_PLUGIN_EXPORT | |
| void setCaptureStackTraceOnRelease(int8_t value); | |
| FFI_PLUGIN_EXPORT | |
| int8_t getCaptureStackTraceOnRelease(); | |
| FFI_PLUGIN_EXPORT | |
| Dart_FinalizableHandle newStackTraceFinalizableHandle(Dart_Handle object, | |
| char* reference); |