Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qjnienvironment.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
6
7#include <QtCore/QThread>
8#include <QtCore/QThreadStorage>
9
11
12/*!
13 \class QJniEnvironment
14 \inmodule QtCore
15 \since 6.1
16 \brief The QJniEnvironment class provides access to the JNI Environment (JNIEnv).
17
18 When using JNI, the \l {JNI tips: JavaVM and JNIEnv}{JNIEnv} class is a pointer to a function
19 table and a member function for each JNI function that indirects through the table. \c JNIEnv
20 provides most of the JNI functions. Every C++ native function receives a \c JNIEnv as the first
21 argument. The JNI environment cannot be shared between threads.
22
23 Since \c JNIEnv doesn't do much error checking, such as exception checking and clearing,
24 QJniEnvironment allows you to do that easily.
25
26 For more information about JNIEnv, see \l {Java: Interface Function Table}.
27
28 \note This API has been designed and tested for use with Android.
29 It has not been tested for other platforms.
30*/
31
33{
34public:
35 JNIEnv *jniEnv = nullptr;
36};
37
39{
40public:
42 {
43 QtAndroidPrivate::javaVM()->DetachCurrentThread();
44 }
45};
46
47Q_GLOBAL_STATIC(QThreadStorage<QJniEnvironmentPrivateTLS *>, jniEnvTLS)
48
49
50/*!
51 Constructs a new JNI Environment object and attaches the current thread to the Java VM.
52*/
53QJniEnvironment::QJniEnvironment()
54 : d(new QJniEnvironmentPrivate{})
55{
56 d->jniEnv = getJniEnv();
57}
58
59/*!
60 Returns the JNIEnv pointer for the current thread.
61
62 The current thread will be attached to the Java VM.
63*/
64JNIEnv *QJniEnvironment::getJniEnv()
65{
66 JNIEnv *jniEnv = nullptr;
67
68 JavaVM *vm = QtAndroidPrivate::javaVM();
69 const jint ret = vm->GetEnv((void**)&jniEnv, JNI_VERSION_1_6);
70
71 if (ret == JNI_EDETACHED) { // We need to (re-)attach
72 const QByteArray threadName = QThread::currentThread()->objectName().toUtf8();
73 JavaVMAttachArgs args = { JNI_VERSION_1_6,
74 threadName.isEmpty() ? "QtThread" : threadName.constData(),
75 nullptr
76 };
77 if (vm->AttachCurrentThread(&jniEnv, &args) == JNI_OK) {
78 if (!jniEnvTLS->hasLocalData()) // If we attached the thread we own it.
79 jniEnvTLS->setLocalData(new QJniEnvironmentPrivateTLS);
80 }
81 }
82 return jniEnv;
83}
84
85/*!
86 \fn QJniEnvironment::~QJniEnvironment()
87
88 Detaches the current thread from the Java VM and destroys the QJniEnvironment object.
89 This will clear any pending exception by calling checkAndClearExceptions().
90*/
91QJniEnvironment::~QJniEnvironment()
92{
93 checkAndClearExceptions();
94}
95
96/*!
97 Returns \c true if this instance holds a valid JNIEnv object.
98
99 \since 6.2
100*/
101bool QJniEnvironment::isValid() const
102{
103 return d->jniEnv;
104}
105
106/*!
107 Provides access to the JNI Environment's \c JNIEnv pointer.
108*/
109JNIEnv *QJniEnvironment::operator->() const
110{
111 return d->jniEnv;
112}
113
114/*!
115 Returns the JNI Environment's \c JNIEnv object.
116*/
117JNIEnv &QJniEnvironment::operator*() const
118{
119 return *d->jniEnv;
120}
121
122/*!
123 Returns the JNI Environment's \c JNIEnv pointer.
124*/
125JNIEnv *QJniEnvironment::jniEnv() const
126{
127 return d->jniEnv;
128}
129
130/*!
131 Searches for \a className using all available class loaders. Qt on Android
132 uses a custom class loader to load all the .jar files and it must be used
133 to find any classes that are created by that class loader because these
134 classes are not visible when using the default class loader.
135
136 Returns the class pointer or null if \a className is not found.
137
138 A use case for this function is searching for a class to call a JNI method
139 that takes a \c jclass. This can be useful when doing multiple JNI calls on
140 the same class object which can a bit faster than using a class name in each
141 call. Additionally, this call looks for internally cached classes first before
142 doing a JNI call, and returns such a class if found. The following code snippet
143 creates an instance of the class \c CustomClass and then calls the
144 \c printFromJava() method:
145
146 \code
147 QJniEnvironment env;
148 jclass javaClass = env.findClass("org/qtproject/example/android/CustomClass");
149 QJniObject javaMessage = QJniObject::fromString("findClass example");
150 QJniObject::callStaticMethod<void>(javaClass, "printFromJava",
151 "(Ljava/lang/String;)V", javaMessage.object<jstring>());
152 \endcode
153
154 \note This call returns a global reference to the class object from the
155 internally cached classes.
156*/
157jclass QJniEnvironment::findClass(const char *className)
158{
159 return QtAndroidPrivate::findClass(className, d->jniEnv);
160}
161
162/*!
163 Searches for an instance method of a class \a clazz. The method is specified
164 by its \a methodName and \a signature.
165
166 Returns the method ID or \c nullptr if the method is not found.
167
168 A usecase for this method is searching for class methods and caching their
169 IDs, so that they could later be used for calling the methods.
170
171 \since 6.2
172*/
173jmethodID QJniEnvironment::findMethod(jclass clazz, const char *methodName, const char *signature)
174{
175 if (clazz) {
176 jmethodID id = d->jniEnv->GetMethodID(clazz, methodName, signature);
177 if (!checkAndClearExceptions(d->jniEnv))
178 return id;
179 }
180
181 return nullptr;
182}
183
184/*!
185 \fn template<typename ...Args> jmethodId QJniEnvironment::findMethod(jclass clazz, const char *methodName)
186 \since 6.4
187
188 Searches for an instance method of a class \a clazz. The method is specified
189 by its \a methodName, the signature is deduced from the template parameters.
190
191 Returns the method ID or \c nullptr if the method is not found.
192*/
193
194/*!
195 Searches for a static method of a class \a clazz. The method is specified
196 by its \a methodName and \a signature.
197
198 Returns the method ID or \c nullptr if the method is not found.
199
200 A usecase for this method is searching for class methods and caching their
201 IDs, so that they could later be used for calling the methods.
202
203 \code
204 QJniEnvironment env;
205 jclass javaClass = env.findClass("org/qtproject/example/android/CustomClass");
206 jmethodID methodId = env.findStaticMethod(javaClass,
207 "staticJavaMethod",
208 "(Ljava/lang/String;)V");
209 QJniObject javaMessage = QJniObject::fromString("findStaticMethod example");
210 QJniObject::callStaticMethod<void>(javaClass,
211 methodId,
212 javaMessage.object<jstring>());
213 \endcode
214
215 \since 6.2
216*/
217jmethodID QJniEnvironment::findStaticMethod(jclass clazz, const char *methodName, const char *signature)
218{
219 if (clazz) {
220 jmethodID id = d->jniEnv->GetStaticMethodID(clazz, methodName, signature);
221 if (!checkAndClearExceptions(d->jniEnv))
222 return id;
223 }
224
225 return nullptr;
226}
227
228/*!
229 \fn template<typename ...Args> jmethodId QJniEnvironment::findStaticMethod(jclass clazz, const char *methodName)
230 \since 6.4
231
232 Searches for an instance method of a class \a clazz. The method is specified
233 by its \a methodName, the signature is deduced from the template parameters.
234
235 Returns the method ID or \c nullptr if the method is not found.
236
237 \code
238 QJniEnvironment env;
239 jclass javaClass = env.findClass("org/qtproject/example/android/CustomClass");
240 jmethodID methodId = env.findStaticMethod<void, jstring>(javaClass, "staticJavaMethod");
241 QJniObject javaMessage = QJniObject::fromString("findStaticMethod example");
242 QJniObject::callStaticMethod<void>(javaClass,
243 methodId,
244 javaMessage.object<jstring>());
245 \endcode
246*/
247
248/*!
249 Searches for a member field of a class \a clazz. The field is specified
250 by its \a fieldName and \a signature.
251
252 Returns the field ID or \c nullptr if the field is not found.
253
254 A usecase for this method is searching for class fields and caching their
255 IDs, so that they could later be used for getting/setting the fields.
256
257 \since 6.2
258*/
259jfieldID QJniEnvironment::findField(jclass clazz, const char *fieldName, const char *signature)
260{
261 if (clazz) {
262 jfieldID id = d->jniEnv->GetFieldID(clazz, fieldName, signature);
263 if (!checkAndClearExceptions())
264 return id;
265 }
266
267 return nullptr;
268}
269
270/*!
271 \fn template<typename T> jfieldID QJniEnvironment::findField(jclass clazz, const char *fieldName)
272 \since 6.4
273
274 Searches for a member field of a class \a clazz. The field is specified
275 by its \a fieldName. The signature of the field is deduced from the template parameter.
276
277 Returns the field ID or \c nullptr if the field is not found.
278*/
279
280/*!
281 Searches for a static field of a class \a clazz. The field is specified
282 by its \a fieldName and \a signature.
283
284 Returns the field ID or \c nullptr if the field is not found.
285
286 A usecase for this method is searching for class fields and caching their
287 IDs, so that they could later be used for getting/setting the fields.
288
289 \since 6.2
290*/
291jfieldID QJniEnvironment::findStaticField(jclass clazz, const char *fieldName, const char *signature)
292{
293 if (clazz) {
294 jfieldID id = d->jniEnv->GetStaticFieldID(clazz, fieldName, signature);
295 if (!checkAndClearExceptions())
296 return id;
297 }
298
299 return nullptr;
300}
301
302/*!
303 \fn template<typename T> jfieldID QJniEnvironment::findStaticField(jclass clazz, const char *fieldName)
304 \since 6.4
305
306 Searches for a static field of a class \a clazz. The field is specified
307 by its \a fieldName. The signature of the field is deduced from the template parameter.
308
309 Returns the field ID or \c nullptr if the field is not found.
310*/
311
312/*!
313 Returns the Java VM interface for the current process. Although it might
314 be possible to have multiple Java VMs per process, Android allows only one.
315
316*/
317JavaVM *QJniEnvironment::javaVM()
318{
319 return QtAndroidPrivate::javaVM();
320}
321
322/*!
323 \fn template <typename Class> bool QJniEnvironment::registerNativeMethods(std::initializer_list<JNINativeMethod> methods)
324 \overload
325
326 Registers the Java methods in \a methods with the Java class represented by
327 \c Class, and returns whether the registration was successful.
328
329 The \c Class type has to be declared within the QtJniTypes namespace using
330 the Q_DECLARE_JNI_CLASS macro. Functions that are implemented as free C or
331 C++ functions have to be declared using one of the
332 Q_DECLARE_JNI_NATIVE_METHOD macros, and passed into the registration using
333 the Q_JNI_NATIVE_METHOD macro.
334
335 \include jni.qdoc register-free-function
336
337 For functions that are implemented as static class member functions, use
338 the \l{Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE}{macros for scoped
339 functions} instead.
340
341 \include jni.qdoc register-scoped-function
342*/
343
344/*!
345 \overload
346
347 Registers the Java methods in the array \a methods of size \a size, each of
348 which can call native C++ functions from class \a className. These methods
349 must be registered before any attempt to call them.
350
351 Returns \c true if the registration is successful, otherwise \c false.
352
353 Each element in the methods array consists of:
354 \list
355 \li The Java method name
356 \li Method signature
357 \li The C++ functions that will be executed
358 \endlist
359
360 \code
361 const JNINativeMethod methods[] =
362 {{"callNativeOne", "(I)V", reinterpret_cast<void *>(fromJavaOne)},
363 {"callNativeTwo", "(I)V", reinterpret_cast<void *>(fromJavaTwo)}};
364 QJniEnvironment env;
365 env.registerNativeMethods("org/qtproject/android/TestJavaClass", methods, 2);
366 \endcode
367*/
368bool QJniEnvironment::registerNativeMethods(const char *className, const JNINativeMethod methods[],
369 int size)
370{
371 const jclass clazz = findClass(className);
372
373 if (!clazz)
374 return false;
375
376 return registerNativeMethods(clazz, methods, size);
377}
378
379/*!
380 \fn bool QJniEnvironment::registerNativeMethods(const char *className, std::initializer_list<JNINativeMethod> methods)
381 \overload
382
383 Registers the native functions methods in \a methods for the Java class \a className.
384 Returns \c true if the registration is successful, otherwise \c false.
385*/
386
387#if QT_DEPRECATED_SINCE(6, 2)
388/*!
389 \overload
390 \deprecated [6.2] Use the overload with a const JNINativeMethod[] instead.
391
392 Registers the Java methods in the array \a methods of size \a size, each of
393 which can call native C++ functions from class \a className. These methods
394 must be registered before any attempt to call them.
395
396 Returns \c true if the registration is successful, otherwise \c false.
397
398 Each element in the methods array consists of:
399 \list
400 \li The Java method name
401 \li Method signature
402 \li The C++ functions that will be executed
403 \endlist
404
405 \code
406 JNINativeMethod methods[] = {{"callNativeOne", "(I)V", reinterpret_cast<void *>(fromJavaOne)},
407 {"callNativeTwo", "(I)V", reinterpret_cast<void *>(fromJavaTwo)}};
408 QJniEnvironment env;
409 env.registerNativeMethods("org/qtproject/android/TestJavaClass", methods, 2);
410 \endcode
411*/
412bool QJniEnvironment::registerNativeMethods(const char *className, JNINativeMethod methods[],
413 int size)
414{
415 return registerNativeMethods(className, const_cast<const JNINativeMethod*>(methods), size);
416}
417#endif
418/*!
419 \overload
420
421 This overload uses a previously cached jclass instance \a clazz.
422
423 \code
424 JNINativeMethod methods[] {{"callNativeOne", "(I)V", reinterpret_cast<void *>(fromJavaOne)},
425 {"callNativeTwo", "(I)V", reinterpret_cast<void *>(fromJavaTwo)}};
426 QJniEnvironment env;
427 jclass clazz = env.findClass("org/qtproject/android/TestJavaClass");
428 env.registerNativeMethods(clazz, methods, 2);
429 \endcode
430*/
431bool QJniEnvironment::registerNativeMethods(jclass clazz, const JNINativeMethod methods[],
432 int size)
433{
434 if (d->jniEnv->RegisterNatives(clazz, methods, size) < 0) {
435 checkAndClearExceptions();
436 return false;
437 }
438 return true;
439}
440
441/*!
442 \fn bool QJniEnvironment::registerNativeMethods(jclass clazz, std::initializer_list<JNINativeMethod> methods)
443 \overload
444
445 Registers the native functions methods in \a methods for the Java class \a clazz.
446 Returns \c true if the registration is successful, otherwise \c false.
447*/
448
449/*!
450 \enum QJniEnvironment::OutputMode
451
452 \value Silent The exceptions are cleaned silently
453 \value Verbose Prints the exceptions and their stack backtrace as an error
454 to \c stderr stream.
455*/
456
457/*!
458 \fn QJniEnvironment::checkAndClearExceptions(OutputMode outputMode = OutputMode::Verbose)
459
460 Cleans any pending exceptions either silently or reporting stack backtrace,
461 depending on the \a outputMode.
462
463 In contrast to \l QJniObject, which handles exceptions internally, if you
464 make JNI calls directly via \c JNIEnv, you need to clear any potential
465 exceptions after the call using this function. For more information about
466 \c JNIEnv calls that can throw an exception, see \l {Java: JNI Functions}{JNI Functions}.
467
468 \return \c true when a pending exception was cleared.
469*/
470bool QJniEnvironment::checkAndClearExceptions(QJniEnvironment::OutputMode outputMode)
471{
472 return checkAndClearExceptions(d->jniEnv, outputMode);
473}
474
475namespace {
476 // Any pending exception need to be cleared before calling this
477 QString exceptionMessage(JNIEnv *env, jthrowable exception)
478 {
479 if (!exception)
480 return {};
481
482 auto logError = []() {
483 qWarning() << "QJniEnvironment: a null object returned or an exception occurred while "
484 "fetching a prior exception message";
485 };
486
487 auto checkAndClear = [env]() {
488 if (Q_UNLIKELY(env->ExceptionCheck())) {
489 env->ExceptionClear();
490 return true;
491 }
492 return false;
493 };
494
495 const jclass logClazz = env->FindClass("android/util/Log");
496 if (checkAndClear() || !logClazz) {
497 logError();
498 return {};
499 }
500
501 const jmethodID methodId = env->GetStaticMethodID(logClazz, "getStackTraceString",
502 "(Ljava/lang/Throwable;)Ljava/lang/String;");
503 if (checkAndClear() || !methodId) {
504 logError();
505 return {};
506 }
507
508 jvalue value;
509 value.l = static_cast<jobject>(exception);
510 const jobject messageObj = env->CallStaticObjectMethodA(logClazz, methodId, &value);
511 const jstring jmessage = static_cast<jstring>(messageObj);
512 if (checkAndClear())
513 return {};
514
515 char const *utfMessage = env->GetStringUTFChars(jmessage, 0);
516 const QString message = QString::fromUtf8(utfMessage);
517
518 env->ReleaseStringUTFChars(jmessage, utfMessage);
519
520 return message;
521 }
522} // end namespace
523
524/*!
525 Cleans any pending exceptions for \a env, either silently or reporting
526 stack backtrace, depending on the \a outputMode. This is useful when you
527 already have a \c JNIEnv pointer such as in a native function implementation.
528
529 In contrast to \l QJniObject, which handles exceptions internally, if you
530 make JNI calls directly via \c JNIEnv, you need to clear any potential
531 exceptions after the call using this function. For more information about
532 \c JNIEnv calls that can throw an exception, see \l {Java: JNI Functions}{JNI Functions}.
533
534 \return \c true when a pending exception was cleared.
535*/
536bool QJniEnvironment::checkAndClearExceptions(JNIEnv *env, QJniEnvironment::OutputMode outputMode)
537{
538 if (Q_UNLIKELY(env->ExceptionCheck())) {
539 if (outputMode == OutputMode::Verbose) {
540 if (jthrowable exception = env->ExceptionOccurred()) {
541 env->ExceptionClear();
542 const QString message = exceptionMessage(env, exception);
543 // Print to QWARN since env->ExceptionDescribe() does the same
544 if (!message.isEmpty())
545 qWarning().noquote() << message;
546 env->DeleteLocalRef(exception);
547 } else {
548 // if the exception object is null for some reason just
549 env->ExceptionDescribe();
550 env->ExceptionClear();
551 }
552 } else {
553 env->ExceptionClear();
554 }
555
556 return true;
557 }
558
559 return false;
560}
561
562QT_END_NAMESPACE