1 /* 2 * Copyright (c) Facebook, Inc. and its affiliates. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #pragma once 18 #include <jni.h> 19 #include <functional> 20 #include <string> 21 22 namespace facebook { 23 namespace jni { 24 25 // Keeps a thread-local reference to the current thread's JNIEnv. 26 struct Environment { 27 // Throws a std::runtime_error if this thread isn't attached to the JVM 28 // TODO(T6594868) Benchmark against raw JNI access 29 static JNIEnv* current(); 30 static void initialize(JavaVM* vm); 31 32 // There are subtle issues with calling the next functions directly. It is 33 // much better to always use a ThreadScope to manage attaching/detaching for 34 // you. 35 static JNIEnv* ensureCurrentThreadIsAttached(); 36 37 // To check if a Java VM is available at all in this environment. 38 // Note that this doesn't check if it is attached to this thread, 39 // it checks whether one is available at all. 40 static bool isGlobalJvmAvailable(); 41 }; 42 43 namespace detail { 44 45 // This will return null the thread isn't attached to the VM, or if 46 // fbjni has never been initialized with a VM at all. You probably 47 // shouldn't be using this. 48 JNIEnv* currentOrNull(); 49 50 // This will return the cached JNIEnv* and the current state of 51 // attaching the thread 52 JNIEnv* cachedWithAttachmentState(bool& isAttaching); 53 54 /** 55 * If there's thread-local data, it's a pointer to one of these. The 56 * instance is a member of JniEnvCacher or ThreadScope, and lives on 57 * the stack. 58 */ 59 struct TLData { 60 // This is modified only by JniEnvCacher, and is guaranteed to be 61 // valid if set, and refer to an env which originated from a JNI 62 // call into C++. 63 JNIEnv* env; 64 // This is modified only by ThreadScope, and is set only if an 65 // instance of ThreadScope which attached is on the stack. 66 bool attached; 67 }; 68 69 /** 70 * RAII object which manages a cached JNIEnv* value. A Value is only 71 * cached if it is guaranteed safe, which means when C++ is called 72 * from a registered fbjni function. 73 */ 74 class JniEnvCacher { 75 public: 76 JniEnvCacher(JNIEnv* env); 77 JniEnvCacher(JniEnvCacher&) = delete; 78 JniEnvCacher(JniEnvCacher&&) = default; 79 JniEnvCacher& operator=(JniEnvCacher&) = delete; 80 JniEnvCacher& operator=(JniEnvCacher&&) = delete; 81 ~JniEnvCacher(); 82 83 private: 84 // If this flag is set, then, this object needs to clear the cache. 85 bool thisCached_; 86 87 // The thread local pointer may point here. 88 detail::TLData data_; 89 }; 90 91 } // namespace detail 92 93 /** 94 * RAII Object that attaches a thread to the JVM. Failing to detach from a 95 * thread before it exits will cause a crash, as will calling Detach an extra 96 * time, and this guard class helps keep that straight. In addition, it 97 * remembers whether it performed the attach or not, so it is safe to nest it 98 * with itself or with non-fbjni code that manages the attachment correctly. 99 * 100 * Potential concerns: 101 * - Attaching to the JVM is fast (~100us on MotoG), but ideally you would 102 * attach while the app is not busy. 103 * - Having a thread detach at arbitrary points is not safe in Dalvik; you need 104 * to be sure that there is no Java code on the current stack or you run the 105 * risk of a crash like: ERROR: detaching thread with interp frames (count=18) 106 * (More detail at 107 * https://groups.google.com/forum/#!topic/android-ndk/2H8z5grNqjo) ThreadScope 108 * won't do a detach if the thread was already attached before the guard is 109 * instantiated, but there's probably some usage that could trip this up. 110 * - Newly attached C++ threads only get the bootstrap class loader -- i.e. 111 * java language classes, not any of our application's classes. This will be 112 * different behavior than threads that were initiated on the Java side. A 113 * workaround is to pass a global reference for a class or instance to the new 114 * thread; this bypasses the need for the class loader. (See 115 * http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html#attach_current_thread) 116 * If you need access to the application's classes, you can use 117 * ThreadScope::WithClassLoader. 118 * - If fbjni has never been initialized, there will be no JavaVM object to 119 * attach with. In that case, a std::runtime_error will be thrown. This is only 120 * likely to happen in a standalone C++ application, or if 121 * Environment::initialize is not used. 122 */ 123 class ThreadScope { 124 public: 125 ThreadScope(); 126 ThreadScope(ThreadScope&) = delete; 127 ThreadScope(ThreadScope&&) = default; 128 ThreadScope& operator=(ThreadScope&) = delete; 129 ThreadScope& operator=(ThreadScope&&) = delete; 130 ~ThreadScope(); 131 132 /** 133 * This runs the closure in a scope with fbjni's classloader. This should be 134 * the same classloader as the rest of the application and thus anything 135 * running in the closure will have access to the same classes as in a normal 136 * java-create thread. 137 */ 138 static void WithClassLoader(std::function<void()>&& runnable); 139 140 static void OnLoad(); 141 142 private: 143 // If this flag is set, then this object needs to detach. 144 bool thisAttached_; 145 146 // The thread local pointer may point here. 147 detail::TLData data_; 148 }; 149 150 } // namespace jni 151 } // namespace facebook 152