diff options
Diffstat (limited to 'src/corelib/kernel/qjnienvironment.cpp')
-rw-r--r-- | src/corelib/kernel/qjnienvironment.cpp | 277 |
1 files changed, 184 insertions, 93 deletions
diff --git a/src/corelib/kernel/qjnienvironment.cpp b/src/corelib/kernel/qjnienvironment.cpp index a72078d2cf..b4f8497ddd 100644 --- a/src/corelib/kernel/qjnienvironment.cpp +++ b/src/corelib/kernel/qjnienvironment.cpp @@ -1,44 +1,7 @@ -/**************************************************************************** -** -** Copyright (C) 2021 The Qt Company Ltd. -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the QtCore module of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and The Qt Company. For licensing terms -** and conditions see https://www.qt.io/terms-conditions. For further -** information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ +// Copyright (C) 2021 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qjnienvironment.h" -#include "qjniobject.h" #include "qjnihelpers_p.h" #include <QtCore/QThread> @@ -66,8 +29,6 @@ QT_BEGIN_NAMESPACE It has not been tested for other platforms. */ -static const char qJniThreadName[] = "QtThread"; - class QJniEnvironmentPrivate { public: @@ -83,47 +44,42 @@ public: } }; -struct QJniLocalRefDeleterPrivate -{ - static void cleanup(jobject obj) - { - if (!obj) - return; - - QJniEnvironment env; - env->DeleteLocalRef(obj); - } -}; - -// To simplify this we only define it for jobjects. -typedef QScopedPointer<_jobject, QJniLocalRefDeleterPrivate> QJniScopedLocalRefPrivate; - - Q_GLOBAL_STATIC(QThreadStorage<QJniEnvironmentPrivateTLS *>, jniEnvTLS) - /*! - \fn QJniEnvironment::QJniEnvironment() - Constructs a new JNI Environment object and attaches the current thread to the Java VM. */ QJniEnvironment::QJniEnvironment() : d(new QJniEnvironmentPrivate{}) { + d->jniEnv = getJniEnv(); +} + +/*! + Returns the JNIEnv pointer for the current thread. + + The current thread will be attached to the Java VM. +*/ +JNIEnv *QJniEnvironment::getJniEnv() +{ + JNIEnv *jniEnv = nullptr; + JavaVM *vm = QtAndroidPrivate::javaVM(); - const jint ret = vm->GetEnv((void**)&d->jniEnv, JNI_VERSION_1_6); - if (ret == JNI_OK) // Already attached - return; + const jint ret = vm->GetEnv((void**)&jniEnv, JNI_VERSION_1_6); if (ret == JNI_EDETACHED) { // We need to (re-)attach - JavaVMAttachArgs args = { JNI_VERSION_1_6, qJniThreadName, nullptr }; - if (vm->AttachCurrentThread(&d->jniEnv, &args) != JNI_OK) - return; - - if (!jniEnvTLS->hasLocalData()) // If we attached the thread we own it. - jniEnvTLS->setLocalData(new QJniEnvironmentPrivateTLS); + const QByteArray threadName = QThread::currentThread()->objectName().toUtf8(); + JavaVMAttachArgs args = { JNI_VERSION_1_6, + threadName.isEmpty() ? "QtThread" : threadName.constData(), + nullptr + }; + if (vm->AttachCurrentThread(&jniEnv, &args) == JNI_OK) { + if (!jniEnvTLS->hasLocalData()) // If we attached the thread we own it. + jniEnvTLS->setLocalData(new QJniEnvironmentPrivateTLS); + } } + return jniEnv; } /*! @@ -148,8 +104,6 @@ bool QJniEnvironment::isValid() const } /*! - \fn JNIEnv *QJniEnvironment::operator->() const - Provides access to the JNI Environment's \c JNIEnv pointer. */ JNIEnv *QJniEnvironment::operator->() const @@ -158,8 +112,6 @@ JNIEnv *QJniEnvironment::operator->() const } /*! - \fn JNIEnv &QJniEnvironment::operator*() const - Returns the JNI Environment's \c JNIEnv object. */ JNIEnv &QJniEnvironment::operator*() const @@ -168,8 +120,6 @@ JNIEnv &QJniEnvironment::operator*() const } /*! - \fn JNIEnv *QJniEnvironment::jniEnv() const - Returns the JNI Environment's \c JNIEnv pointer. */ JNIEnv *QJniEnvironment::jniEnv() const @@ -232,6 +182,16 @@ jmethodID QJniEnvironment::findMethod(jclass clazz, const char *methodName, cons } /*! + \fn template<typename ...Args> jmethodId QJniEnvironment::findMethod(jclass clazz, const char *methodName) + \since 6.4 + + Searches for an instance method of a class \a clazz. The method is specified + by its \a methodName, the signature is deduced from the template parameters. + + Returns the method ID or \c nullptr if the method is not found. +*/ + +/*! Searches for a static method of a class \a clazz. The method is specified by its \a methodName and \a signature. @@ -265,9 +225,28 @@ jmethodID QJniEnvironment::findStaticMethod(jclass clazz, const char *methodName return nullptr; } +/*! + \fn template<typename ...Args> jmethodId QJniEnvironment::findStaticMethod(jclass clazz, const char *methodName) + \since 6.4 + + Searches for an instance method of a class \a clazz. The method is specified + by its \a methodName, the signature is deduced from the template parameters. + + Returns the method ID or \c nullptr if the method is not found. + + \code + QJniEnvironment env; + jclass javaClass = env.findClass("org/qtproject/example/android/CustomClass"); + jmethodID methodId = env.findStaticMethod<void, jstring>(javaClass, "staticJavaMethod"); + QJniObject javaMessage = QJniObject::fromString("findStaticMethod example"); + QJniObject::callStaticMethod<void>(javaClass, + methodId, + javaMessage.object<jstring>()); + \endcode +*/ /*! - Searches for an member field of a class \a clazz. The field is specified + Searches for a member field of a class \a clazz. The field is specified by its \a fieldName and \a signature. Returns the field ID or \c nullptr if the field is not found. @@ -289,6 +268,16 @@ jfieldID QJniEnvironment::findField(jclass clazz, const char *fieldName, const c } /*! + \fn template<typename T> jfieldID QJniEnvironment::findField(jclass clazz, const char *fieldName) + \since 6.4 + + Searches for a member field of a class \a clazz. The field is specified + by its \a fieldName. The signature of the field is deduced from the template parameter. + + Returns the field ID or \c nullptr if the field is not found. +*/ + +/*! Searches for a static field of a class \a clazz. The field is specified by its \a fieldName and \a signature. @@ -311,8 +300,16 @@ jfieldID QJniEnvironment::findStaticField(jclass clazz, const char *fieldName, c } /*! - \fn JavaVM *QJniEnvironment::javaVM() + \fn template<typename T> jfieldID QJniEnvironment::findStaticField(jclass clazz, const char *fieldName) + \since 6.4 + + Searches for a static field of a class \a clazz. The field is specified + by its \a fieldName. The signature of the field is deduced from the template parameter. + + Returns the field ID or \c nullptr if the field is not found. +*/ +/*! Returns the Java VM interface for the current process. Although it might be possible to have multiple Java VMs per process, Android allows only one. @@ -323,6 +320,30 @@ JavaVM *QJniEnvironment::javaVM() } /*! + \fn template <typename Class> bool QJniEnvironment::registerNativeMethods(std::initializer_list<JNINativeMethod> methods) + \overload + + Registers the Java methods in \a methods with the Java class represented by + \c Class, and returns whether the registration was successful. + + The \c Class type has to be declared within the QtJniTypes namespace using + the Q_DECLARE_JNI_CLASS macro. Functions that are implemented as free C or + C++ functions have to be declared using one of the + Q_DECLARE_JNI_NATIVE_METHOD macros, and passed into the registration using + the Q_JNI_NATIVE_METHOD macro. + + \include jni.qdoc register-free-function + + For functions that are implemented as static class member functions, use + the \l{Q_DECLARE_JNI_NATIVE_METHOD_IN_CURRENT_SCOPE}{macros for scoped + functions} instead. + + \include jni.qdoc register-scoped-function +*/ + +/*! + \overload + Registers the Java methods in the array \a methods of size \a size, each of which can call native C++ functions from class \a className. These methods must be registered before any attempt to call them. @@ -347,12 +368,22 @@ JavaVM *QJniEnvironment::javaVM() bool QJniEnvironment::registerNativeMethods(const char *className, const JNINativeMethod methods[], int size) { - QJniObject classObject(className); + const jclass clazz = findClass(className); - if (!classObject.isValid()) + if (!clazz) return false; - return registerNativeMethods(classObject.objectClass(), methods, size); + + return registerNativeMethods(clazz, methods, size); } + +/*! + \fn bool QJniEnvironment::registerNativeMethods(const char *className, std::initializer_list<JNINativeMethod> methods) + \overload + + Registers the native functions methods in \a methods for the Java class \a className. + Returns \c true if the registration is successful, otherwise \c false. +*/ + #if QT_DEPRECATED_SINCE(6, 2) /*! \overload @@ -408,6 +439,14 @@ bool QJniEnvironment::registerNativeMethods(jclass clazz, const JNINativeMethod } /*! + \fn bool QJniEnvironment::registerNativeMethods(jclass clazz, std::initializer_list<JNINativeMethod> methods) + \overload + + Registers the native functions methods in \a methods for the Java class \a clazz. + Returns \c true if the registration is successful, otherwise \c false. +*/ + +/*! \enum QJniEnvironment::OutputMode \value Silent The exceptions are cleaned silently @@ -430,20 +469,59 @@ bool QJniEnvironment::registerNativeMethods(jclass clazz, const JNINativeMethod */ bool QJniEnvironment::checkAndClearExceptions(QJniEnvironment::OutputMode outputMode) { - if (Q_UNLIKELY(d->jniEnv->ExceptionCheck())) { - if (outputMode != OutputMode::Silent) - d->jniEnv->ExceptionDescribe(); - d->jniEnv->ExceptionClear(); + return checkAndClearExceptions(d->jniEnv, outputMode); +} - return true; +namespace { + // Any pending exception need to be cleared before calling this + QString exceptionMessage(JNIEnv *env, jthrowable exception) + { + if (!exception) + return {}; + + auto logError = []() { + qWarning() << "QJniEnvironment: a null object returned or an exception occurred while " + "fetching a prior exception message"; + }; + + auto checkAndClear = [env]() { + if (Q_UNLIKELY(env->ExceptionCheck())) { + env->ExceptionClear(); + return true; + } + return false; + }; + + const jclass logClazz = env->FindClass("android/util/Log"); + if (checkAndClear() || !logClazz) { + logError(); + return {}; + } + + const jmethodID methodId = env->GetStaticMethodID(logClazz, "getStackTraceString", + "(Ljava/lang/Throwable;)Ljava/lang/String;"); + if (checkAndClear() || !methodId) { + logError(); + return {}; + } + + jvalue value; + value.l = static_cast<jobject>(exception); + const jobject messageObj = env->CallStaticObjectMethodA(logClazz, methodId, &value); + const jstring jmessage = static_cast<jstring>(messageObj); + if (checkAndClear()) + return {}; + + char const *utfMessage = env->GetStringUTFChars(jmessage, 0); + const QString message = QString::fromUtf8(utfMessage); + + env->ReleaseStringUTFChars(jmessage, utfMessage); + + return message; } - - return false; -} +} // end namespace /*! - \fn QJniEnvironment::checkAndClearExceptions(JNIEnv *env, OutputMode outputMode = OutputMode::Verbose) - Cleans any pending exceptions for \a env, either silently or reporting stack backtrace, depending on the \a outputMode. This is useful when you already have a \c JNIEnv pointer such as in a native function implementation. @@ -458,9 +536,22 @@ bool QJniEnvironment::checkAndClearExceptions(QJniEnvironment::OutputMode output bool QJniEnvironment::checkAndClearExceptions(JNIEnv *env, QJniEnvironment::OutputMode outputMode) { if (Q_UNLIKELY(env->ExceptionCheck())) { - if (outputMode != OutputMode::Silent) - env->ExceptionDescribe(); - env->ExceptionClear(); + if (outputMode == OutputMode::Verbose) { + if (jthrowable exception = env->ExceptionOccurred()) { + env->ExceptionClear(); + const QString message = exceptionMessage(env, exception); + // Print to QWARN since env->ExceptionDescribe() does the same + if (!message.isEmpty()) + qWarning().noquote() << message; + env->DeleteLocalRef(exception); + } else { + // if the exception object is null for some reason just + env->ExceptionDescribe(); + env->ExceptionClear(); + } + } else { + env->ExceptionClear(); + } return true; } |