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