diff options
Diffstat (limited to 'src/corelib/kernel/qjniobject.cpp')
-rw-r--r-- | src/corelib/kernel/qjniobject.cpp | 1432 |
1 files changed, 1432 insertions, 0 deletions
diff --git a/src/corelib/kernel/qjniobject.cpp b/src/corelib/kernel/qjniobject.cpp new file mode 100644 index 0000000000..892f02e7a4 --- /dev/null +++ b/src/corelib/kernel/qjniobject.cpp @@ -0,0 +1,1432 @@ +// 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 "qjniobject.h" + +#include "qjnihelpers_p.h" + +#include <QtCore/qbytearray.h> +#include <QtCore/qhash.h> +#include <QtCore/qreadwritelock.h> +#include <QtCore/qloggingcategory.h> + + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcJniThreadCheck, "qt.core.jni.threadcheck") + +using namespace Qt::StringLiterals; + +/*! + \class QJniObject + \inmodule QtCore + \since 6.1 + \brief A convenience wrapper around the Java Native Interface (JNI). + + The QJniObject class wraps a reference to a Java object, ensuring it isn't + garbage-collected and providing access to most \c JNIEnv method calls + (member, static) and fields (setter, getter). It eliminates much + boiler-plate that would normally be needed, with direct JNI access, for + every operation, including exception-handling. + + \note This API has been designed and tested for use with Android. + It has not been tested for other platforms. + + \sa QJniEnvironment + + \section1 General Notes + + \list + \li Class names need to be fully-qualified, for example: \c "java/lang/String". + \li Method signatures are written as \c "(ArgumentsTypes)ReturnType", see \l {JNI Types}. + \li All object types are returned as a QJniObject. + \endlist + + \section1 Method Signatures + + QJniObject provides convenience functions that will use the correct signature based on the + provided template types. For functions that only return and take \l {JNI types}, the + signature can be generate at compile time: + + \code + jint x = QJniObject::callMethod<jint>("getSize"); + QJniObject::callMethod<void>("touch"); + jint ret = jString1.callMethod<jint>("compareToIgnoreCase", jString2.object<jstring>()); + \endcode + + These functions are variadic templates, and the compiler will deduce the template arguments + from the actual argument types. In many situations, only the return type needs to be provided + explicitly. + + For functions that take other argument types, you need to supply the signature yourself. It is + important that the signature matches the function you want to call. The example below + demonstrates how to call different static functions: + + \code + // Java class + package org.qtproject.qt; + class TestClass + { + static TestClass create() { ... } + static String fromNumber(int x) { ... } + static String[] stringArray(String s1, String s2) { ... } + } + \endcode + + The signature structure is \c "(ArgumentsTypes)ReturnType". Array types in the signature + must have the \c {[} prefix, and the fully-qualified \c Object type names must have the + \c L prefix and the \c ; suffix. The signature for the \c create function is + \c {"()Lorg/qtproject/qt/TestClass;}. The signatures for the second and third functions + are \c {"(I)Ljava/lang/String;"} and + \c {"(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;"}, respectively. + + We can call the \c create() function like this: + + \code + // C++ code + QJniObject testClass = QJniObject::callStaticObjectMethod("org/qtproject/qt/TestClass", + "create", + "()Lorg/qtproject/qt/TestClass;"); + \endcode + + For the second and third function we can rely on QJniObject's template methods to create + the implicit signature string, but we can also pass the signature string explicitly: + + \code + // C++ code + QJniObject stringNumber = QJniObject::callStaticObjectMethod("org/qtproject/qt/TestClass", + "fromNumber", + "(I)Ljava/lang/String;", 10); + \endcode + + For the implicit signature creation to work we need to specify the return type explicitly: + + \code + // C++ code + QJniObject string1 = QJniObject::fromString("String1"); + QJniObject string2 = QJniObject::fromString("String2"); + QJniObject stringArray = QJniObject::callStaticObjectMethod<jobjectArray>( + "org/qtproject/qt/TestClass", + "stringArray", + string1.object<jstring>(), + string2.object<jstring>()); + \endcode + + Note that while the first template parameter specifies the return type of the Java + function, the method will still return a QJniObject. + + \section1 Handling Java Exception + + After calling Java functions that might throw exceptions, it is important + to check for, handle and clear out any exception before continuing. All + QJniObject functions handle exceptions internally by reporting and clearing them, + saving client code the need to handle exceptions. + + \note The user must handle exceptions manually when doing JNI calls using \c JNIEnv directly. + It is unsafe to make other JNI calls when exceptions are pending. For more information, see + QJniEnvironment::checkAndClearExceptions(). + + \section1 Java Native Methods + + Java native methods makes it possible to call native code from Java, this is done by creating a + function declaration in Java and prefixing it with the \c native keyword. + Before a native function can be called from Java, you need to map the Java native function to a + native function in your code. Mapping functions can be done by calling + QJniEnvironment::registerNativeMethods(). + + The example below demonstrates how this could be done. + + Java implementation: + \snippet jni/src_qjniobject.cpp Java native methods + + C++ Implementation: + \snippet jni/src_qjniobject.cpp C++ native methods + + \section1 The Lifetime of a Java Object + + Most \l{Object types}{objects} received from Java will be local references + and will only stay valid until you return from the native method. After that, + the object becomes eligible for garbage collection. If your code creates + many local references in a loop you should delete them manually with each + iteration, otherwise you might run out of memory. For more information, see + \l {JNI Design Overview: Global and Local References}. Local references + created outside a native method scope must be deleted manually, since + the garbage collector will not free them automatically because we are using + \l {Java: AttachCurrentThread}{AttachCurrentThread}. For more information, see + \l {JNI tips: Local and global references}. + + If you want to keep a Java object alive you need to either create a new global + reference to the object and release it when you are done, or construct a new + QJniObject and let it manage the lifetime of the Java object. + + \sa object() + + \note The QJniObject only manages its own references, if you construct a QJniObject from a + global or local reference that reference will not be released by the QJniObject. + + \section1 JNI Types + + \section2 Object Types + \table + \header + \li Type + \li Signature + \row + \li jobject + \li Ljava/lang/Object; + \row + \li jclass + \li Ljava/lang/Class; + \row + \li jstring + \li Ljava/lang/String; + \row + \li jthrowable + \li Ljava/lang/Throwable; + \row + \li jobjectArray + \li [Ljava/lang/Object; + \row + \li jarray + \li [\e<type> + \row + \li jbooleanArray + \li [Z + \row + \li jbyteArray + \li [B + \row + \li jcharArray + \li [C + \row + \li jshortArray + \li [S + \row + \li jintArray + \li [I + \row + \li jlongArray + \li [J + \row + \li jfloatArray + \li [F + \row + \li jdoubleArray + \li [D + \endtable + + \section2 Primitive Types + \table + \header + \li Type + \li Signature + \row + \li jboolean + \li Z + \row + \li jbyte + \li B + \row + \li jchar + \li C + \row + \li jshort + \li S + \row + \li jint + \li I + \row + \li jlong + \li J + \row + \li jfloat + \li F + \row + \li jdouble + \li D + \endtable + + \section2 Other + \table + \header + \li Type + \li Signature + \row + \li void + \li V + \row + \li \e{Custom type} + \li L\e<fully-qualified-name>; + \endtable + + For more information about JNI, see \l {Java Native Interface Specification}. +*/ + +/*! + \fn bool operator==(const QJniObject &o1, const QJniObject &o2) + + \relates QJniObject + + Returns true if both objects, \a o1 and \a o2, are referencing the same Java object, or if both + are NULL. In any other cases false will be returned. +*/ + +/*! + \fn bool operator!=(const QJniObject &o1, const QJniObject &o2) + \relates QJniObject + + Returns true if \a o1 holds a reference to a different object than \a o2. +*/ + +class QJniObjectPrivate +{ +public: + QJniObjectPrivate() + { + } + ~QJniObjectPrivate() { + JNIEnv *env = QJniEnvironment::getJniEnv(); + if (m_jobject) + env->DeleteGlobalRef(m_jobject); + if (m_jclass && m_own_jclass) + env->DeleteGlobalRef(m_jclass); + } + + template <typename ...Args> + void construct(const char *signature = nullptr, Args &&...args) + { + if (m_jclass) { + JNIEnv *env = QJniEnvironment::getJniEnv(); + // get default constructor + jmethodID constructorId = QJniObject::getCachedMethodID(env, m_jclass, m_className, "<init>", + signature ? signature : "()V"); + if (constructorId) { + jobject obj = nullptr; + if constexpr (sizeof...(Args) == 0) + obj = env->NewObject(m_jclass, constructorId); + else + obj = env->NewObjectV(m_jclass, constructorId, std::forward<Args>(args)...); + if (obj) { + m_jobject = env->NewGlobalRef(obj); + env->DeleteLocalRef(obj); + } + } + } + } + + QByteArray m_className; + jobject m_jobject = nullptr; + jclass m_jclass = nullptr; + bool m_own_jclass = true; +}; + +template <typename ...Args> +static inline QByteArray cacheKey(Args &&...args) +{ + return (QByteArrayView(":") + ... + QByteArrayView(args)); +} + +typedef QHash<QByteArray, jclass> JClassHash; +Q_GLOBAL_STATIC(JClassHash, cachedClasses) +Q_GLOBAL_STATIC(QReadWriteLock, cachedClassesLock) + +static jclass getCachedClass(const QByteArray &className) +{ + QReadLocker locker(cachedClassesLock); + const auto &it = cachedClasses->constFind(className); + return it != cachedClasses->constEnd() ? it.value() : nullptr; +} + +/*! + \internal + + Get a JNI object from a jobject variant and do the necessary + exception clearing and delete the local reference before returning. + The JNI object can be null if there was an exception. +*/ +static QJniObject getCleanJniObject(jobject object, JNIEnv *env) +{ + if (QJniEnvironment::checkAndClearExceptions(env) || !object) { + if (object) + env->DeleteLocalRef(object); + return QJniObject(); + } + + QJniObject res(object); + env->DeleteLocalRef(object); + return res; +} + +/*! + \internal + \a className must be slash-encoded +*/ +jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env) +{ + Q_ASSERT(env); + QByteArray classNameArray(className); +#ifdef QT_DEBUG + if (classNameArray.contains('.')) { + qWarning("QtAndroidPrivate::findClass: className '%s' should use slash separators!", + className); + } +#endif + classNameArray.replace('.', '/'); + jclass clazz = getCachedClass(classNameArray); + if (clazz) + return clazz; + + QWriteLocker locker(cachedClassesLock); + // Check again; another thread might have added the class to the cache after + // our call to getCachedClass and before we acquired the lock. + const auto &it = cachedClasses->constFind(classNameArray); + if (it != cachedClasses->constEnd()) + return it.value(); + + // JNIEnv::FindClass wants "a fully-qualified class name or an array type signature" + // which is a slash-separated class name. + jclass localClazz = env->FindClass(classNameArray.constData()); + if (localClazz) { + clazz = static_cast<jclass>(env->NewGlobalRef(localClazz)); + env->DeleteLocalRef(localClazz); + } else { + // Clear the exception silently; we are going to try the ClassLoader next, + // so no need for warning unless that fails as well. + env->ExceptionClear(); + } + + if (!clazz) { + // Wrong class loader, try our own + QJniObject classLoader(QtAndroidPrivate::classLoader()); + if (!classLoader.isValid()) + return nullptr; + + // ClassLoader::loadClass on the other hand wants the binary name of the class, + // e.g. dot-separated. In testing it works also with /, but better to stick to + // the specification. + const QString binaryClassName = QString::fromLatin1(className).replace(u'/', u'.'); + jstring classNameObject = env->NewString(reinterpret_cast<const jchar*>(binaryClassName.constData()), + binaryClassName.length()); + QJniObject classObject = classLoader.callMethod<jclass>("loadClass", classNameObject); + env->DeleteLocalRef(classNameObject); + + if (!QJniEnvironment::checkAndClearExceptions(env) && classObject.isValid()) + clazz = static_cast<jclass>(env->NewGlobalRef(classObject.object())); + } + + if (clazz) + cachedClasses->insert(classNameArray, clazz); + + return clazz; +} + +jclass QJniObject::loadClass(const QByteArray &className, JNIEnv *env) +{ + return QtAndroidPrivate::findClass(className, env); +} + +typedef QHash<QByteArray, jmethodID> JMethodIDHash; +Q_GLOBAL_STATIC(JMethodIDHash, cachedMethodID) +Q_GLOBAL_STATIC(QReadWriteLock, cachedMethodIDLock) + +jmethodID QJniObject::getMethodID(JNIEnv *env, + jclass clazz, + const char *name, + const char *signature, + bool isStatic) +{ + jmethodID id = isStatic ? env->GetStaticMethodID(clazz, name, signature) + : env->GetMethodID(clazz, name, signature); + + if (QJniEnvironment::checkAndClearExceptions(env)) + return nullptr; + + return id; +} + +void QJniObject::callVoidMethodV(JNIEnv *env, jmethodID id, ...) const +{ + va_list args; + va_start(args, id); + env->CallVoidMethodV(d->m_jobject, id, args); + va_end(args); +} + +jmethodID QJniObject::getCachedMethodID(JNIEnv *env, + jclass clazz, + const QByteArray &className, + const char *name, + const char *signature, + bool isStatic) +{ + if (className.isEmpty()) + return getMethodID(env, clazz, name, signature, isStatic); + + const QByteArray key = cacheKey(className, name, signature); + QHash<QByteArray, jmethodID>::const_iterator it; + + { + QReadLocker locker(cachedMethodIDLock); + it = cachedMethodID->constFind(key); + if (it != cachedMethodID->constEnd()) + return it.value(); + } + + { + QWriteLocker locker(cachedMethodIDLock); + it = cachedMethodID->constFind(key); + if (it != cachedMethodID->constEnd()) + return it.value(); + + jmethodID id = getMethodID(env, clazz, name, signature, isStatic); + + cachedMethodID->insert(key, id); + return id; + } +} + +jmethodID QJniObject::getCachedMethodID(JNIEnv *env, const char *name, + const char *signature, bool isStatic) const +{ + return QJniObject::getCachedMethodID(env, d->m_jclass, d->m_className, name, signature, isStatic); +} + +typedef QHash<QByteArray, jfieldID> JFieldIDHash; +Q_GLOBAL_STATIC(JFieldIDHash, cachedFieldID) +Q_GLOBAL_STATIC(QReadWriteLock, cachedFieldIDLock) + +jfieldID QJniObject::getFieldID(JNIEnv *env, + jclass clazz, + const char *name, + const char *signature, + bool isStatic) +{ + jfieldID id = isStatic ? env->GetStaticFieldID(clazz, name, signature) + : env->GetFieldID(clazz, name, signature); + + if (QJniEnvironment::checkAndClearExceptions(env)) + return nullptr; + + return id; +} + +jfieldID QJniObject::getCachedFieldID(JNIEnv *env, + jclass clazz, + const QByteArray &className, + const char *name, + const char *signature, + bool isStatic) +{ + if (className.isNull()) + return getFieldID(env, clazz, name, signature, isStatic); + + const QByteArray key = cacheKey(className, name, signature); + QHash<QByteArray, jfieldID>::const_iterator it; + + { + QReadLocker locker(cachedFieldIDLock); + it = cachedFieldID->constFind(key); + if (it != cachedFieldID->constEnd()) + return it.value(); + } + + { + QWriteLocker locker(cachedFieldIDLock); + it = cachedFieldID->constFind(key); + if (it != cachedFieldID->constEnd()) + return it.value(); + + jfieldID id = getFieldID(env, clazz, name, signature, isStatic); + + cachedFieldID->insert(key, id); + return id; + } +} + +jfieldID QJniObject::getCachedFieldID(JNIEnv *env, + const char *name, + const char *signature, + bool isStatic) const +{ + return QJniObject::getCachedFieldID(env, d->m_jclass, d->m_className, name, signature, isStatic); +} + +/*! + \fn QJniObject::QJniObject() + + Constructs an invalid JNI object. + + \sa isValid() +*/ +QJniObject::QJniObject() + : d(new QJniObjectPrivate()) +{ +} + +/*! + \fn QJniObject::QJniObject(const char *className) + + Constructs a new JNI object by calling the default constructor of \a className. + + \code + QJniObject myJavaString("java/lang/String"); + \endcode +*/ +QJniObject::QJniObject(const char *className) + : d(new QJniObjectPrivate()) +{ + d->m_className = className; + d->m_jclass = loadClass(d->m_className, jniEnv()); + d->m_own_jclass = false; + + d->construct(); +} + +/*! + \fn QJniObject::QJniObject(const char *className, const char *signature, ...) + + Constructs a new JNI object by calling the constructor of \a className with + \a signature specifying the types of any subsequent arguments. + + \code + QJniEnvironment env; + char* str = "Hello"; + jstring myJStringArg = env->NewStringUTF(str); + QJniObject myNewJavaString("java/lang/String", "(Ljava/lang/String;)V", myJStringArg); + \endcode +*/ +QJniObject::QJniObject(const char *className, const char *signature, ...) + : d(new QJniObjectPrivate()) +{ + d->m_className = className; + d->m_jclass = loadClass(d->m_className, jniEnv()); + d->m_own_jclass = false; + + va_list args; + va_start(args, signature); + d->construct(signature, args); + va_end(args); +} + +/*! + \fn template<typename ...Args> QJniObject::QJniObject(const char *className, Args &&...args) + \since 6.4 + + Constructs a new JNI object by calling the constructor of \a className with + the arguments \a args. This constructor is only available if all \a args are + known \l {JNI Types}. + + \code + QJniEnvironment env; + char* str = "Hello"; + jstring myJStringArg = env->NewStringUTF(str); + QJniObject myNewJavaString("java/lang/String", myJStringArg); + \endcode +*/ + +/*! + Constructs a new JNI object from \a clazz by calling the constructor with + \a signature specifying the types of any subsequent arguments. + + \code + QJniEnvironment env; + jclass myClazz = env.findClass("org/qtproject/qt/TestClass"); + QJniObject(myClazz, "(I)V", 3); + \endcode +*/ +QJniObject::QJniObject(jclass clazz, const char *signature, ...) + : d(new QJniObjectPrivate()) +{ + if (clazz) { + d->m_jclass = static_cast<jclass>(jniEnv()->NewGlobalRef(clazz)); + va_list args; + va_start(args, signature); + d->construct(signature, args); + va_end(args); + } +} + +/*! + \fn template<typename ...Args> QJniObject::QJniObject(jclass clazz, Args &&...args) + \since 6.4 + + Constructs a new JNI object from \a clazz by calling the constructor with + the arguments \a args. This constructor is only available if all \a args are + known \l {JNI Types}. + + \code + QJniEnvironment env; + jclass myClazz = env.findClass("org/qtproject/qt/TestClass"); + QJniObject(myClazz, 3); + \endcode +*/ + +/*! + Constructs a new JNI object by calling the default constructor of \a clazz. + + \note The QJniObject will create a new reference to the class \a clazz + and releases it again when it is destroyed. References to the class created + outside the QJniObject need to be managed by the caller. +*/ + +QJniObject::QJniObject(jclass clazz) + : QJniObject(clazz, "()V") +{ +} + +/*! + Constructs a new JNI object around the Java object \a object. + + \note The QJniObject will hold a reference to the Java object \a object + and release it when destroyed. Any references to the Java object \a object + outside QJniObject needs to be managed by the caller. In most cases you + should never call this function with a local reference unless you intend + to manage the local reference yourself. See QJniObject::fromLocalRef() + for converting a local reference to a QJniObject. + + \sa fromLocalRef() +*/ +QJniObject::QJniObject(jobject object) + : d(new QJniObjectPrivate()) +{ + if (!object) + return; + + JNIEnv *env = QJniEnvironment::getJniEnv(); + d->m_jobject = env->NewGlobalRef(object); + jclass cls = env->GetObjectClass(object); + d->m_jclass = static_cast<jclass>(env->NewGlobalRef(cls)); + env->DeleteLocalRef(cls); +} + +/*! + \fn template<typename Class, typename ...Args> static inline QJniObject QJniObject::construct(Args &&...args) + \since 6.4 + + Constructs an instance of the Java class that is the equivalent of \c Class and + returns a QJniObject containing the JNI object. The arguments in \a args are + passed to the Java constructor. + + \code + QJniObject javaString = QJniObject::construct<jstring>(); + \endcode + + This function is only available if all \a args are known \l {JNI Types}. +*/ + +/*! + \fn QJniObject::~QJniObject() + + Destroys the JNI object and releases any references held by the JNI object. +*/ +QJniObject::~QJniObject() +{} + +namespace { +QByteArray getClassNameHelper(JNIEnv *env, const QJniObjectPrivate *d) +{ + if (env->PushLocalFrame(3) != JNI_OK) // JVM out of memory + return QByteArray(); + + jmethodID mid = env->GetMethodID(d->m_jclass, "getClass", "()Ljava/lang/Class;"); + jobject classObject = env->CallObjectMethod(d->m_jobject, mid); + jclass classObjectClass = env->GetObjectClass(classObject); + mid = env->GetMethodID(classObjectClass, "getName", "()Ljava/lang/String;"); + jstring stringObject = static_cast<jstring>(env->CallObjectMethod(classObject, mid)); + const jsize length = env->GetStringUTFLength(stringObject); + const char* nameString = env->GetStringUTFChars(stringObject, NULL); + const QByteArray result = QByteArray::fromRawData(nameString, length).replace('.', '/'); + env->ReleaseStringUTFChars(stringObject, nameString); + env->PopLocalFrame(nullptr); + return result; +} +} + +/*! \internal + + Returns the JNIEnv of the calling thread. +*/ +JNIEnv *QJniObject::jniEnv() const noexcept +{ + return QJniEnvironment::getJniEnv(); +} + +/*! + \fn jobject QJniObject::object() const + \fn template <typename T> T QJniObject::object() const + + Returns the object held by the QJniObject either as jobject or as type T. + T can be one of \l {Object Types}{JNI Object Types}. + + \code + QJniObject string = QJniObject::fromString("Hello, JNI"); + jstring jstring = string.object<jstring>(); + \endcode + + \note The returned object is still kept alive by this QJniObject. To keep the + object alive beyond the lifetime of this QJniObject, for example to record it + for later use, the easiest approach is to store it in another QJniObject with + a suitable lifetime. Alternatively, you may create a new global reference to the + object and store it, taking care to free it when you are done with it. + + \snippet jni/src_qjniobject.cpp QJniObject scope +*/ +jobject QJniObject::object() const +{ + return javaObject(); +} + +/*! + \fn jclass QJniObject::objectClass() const + + Returns the class object held by the QJniObject as a \c jclass. + + \note The returned object is still kept alive by this QJniObject. To keep the + object alive beyond the lifetime of this QJniObject, for example to record it + for later use, the easiest approach is to store it in another QJniObject with + a suitable lifetime. Alternatively, you may create a new global reference to the + object and store it, taking care to free it when you are done with it. + + \since 6.2 +*/ +jclass QJniObject::objectClass() const +{ + return d->m_jclass; +} + +/*! + \fn QByteArray QJniObject::className() const + + Returns the name of the class object held by the QJniObject as a \c QByteArray. + + \since 6.2 +*/ +QByteArray QJniObject::className() const +{ + if (d->m_className.isEmpty() && d->m_jclass && d->m_jobject) { + JNIEnv *env = jniEnv(); + d->m_className = getClassNameHelper(env, d.get()); + } + return d->m_className; +} + +/*! + \fn template <typename Ret, typename ...Args> auto QJniObject::callMethod(const char *methodName, const char *signature, Args &&...args) const + \since 6.4 + + Calls the object's method \a methodName with \a signature specifying the types of any + subsequent arguments \a args, and returns the value (unless \c Ret is \c void). If \c Ret + is a jobject type, then the returned value will be a QJniObject. + + \code + QJniObject myJavaStrin("org/qtproject/qt/TestClass"); + jint index = myJavaString.callMethod<jint>("indexOf", "(I)I", 0x0051); + \endcode +*/ + +/*! + \fn template <typename Ret, typename ...Args> auto QJniObject::callMethod(const char *methodName, Args &&...args) const + \since 6.4 + + Calls the method \a methodName with arguments \a args and returns the value + (unless \c Ret is \c void). If \c Ret is a jobject type, then the returned value + will be a QJniObject. + + \code + QJniObject myJavaStrin("org/qtproject/qt/TestClass"); + jint size = myJavaString.callMethod<jint>("length"); + \endcode + + The method signature is deduced at compile time from \c Ret and the types of \a args. +*/ + +/*! + \fn template <typename Ret, typename ...Args> auto QJniObject::callStaticMethod(const char *className, const char *methodName, const char *signature, Args &&...args) + \since 6.4 + + Calls the static method \a methodName from class \a className with \a signature + specifying the types of any subsequent arguments \a args. Returns the result of + the method (unless \c Ret is \c void). If \c Ret is a jobject type, then the + returned value will be a QJniObject. + + \code + jint a = 2; + jint b = 4; + jint max = QJniObject::callStaticMethod<jint>("java/lang/Math", "max", "(II)I", a, b); + \endcode +*/ + +/*! + \fn template <typename Ret, typename ...Args> auto QJniObject::callStaticMethod(const char *className, const char *methodName, Args &&...args) + \since 6.4 + + Calls the static method \a methodName on class \a className with arguments \a args, + and returns the value of type \c Ret (unless \c Ret is \c void). If \c Ret + is a jobject type, then the returned value will be a QJniObject. + + \code + jint value = QJniObject::callStaticMethod<jint>("MyClass", "staticMethod"); + \endcode + + The method signature is deduced at compile time from \c Ret and the types of \a args. +*/ + +/*! + \fn template <typename Ret, typename ...Args> auto QJniObject::callStaticMethod(jclass clazz, const char *methodName, const char *signature, Args &&...args) + + Calls the static method \a methodName from \a clazz with \a signature + specifying the types of any subsequent arguments. Returns the result of + the method (unless \c Ret is \c void). If \c Ret is a jobject type, then the + returned value will be a QJniObject. + + \code + QJniEnvironment env; + jclass javaMathClass = env.findClass("java/lang/Math"); + jint a = 2; + jint b = 4; + jint max = QJniObject::callStaticMethod<jint>(javaMathClass, "max", "(II)I", a, b); + \endcode +*/ + +/*! + \fn template <typename Ret, typename ...Args> auto QJniObject::callStaticMethod(jclass clazz, jmethodID methodId, Args &&...args) + \since 6.4 + + Calls the static method identified by \a methodId from the class \a clazz + with any subsequent arguments, and returns the value of type \c Ret (unless + \c Ret is \c void). If \c Ret is a jobject type, then the returned value will + be a QJniObject. + + Useful when \a clazz and \a methodId are already cached from previous operations. + + \code + QJniEnvironment env; + jclass javaMathClass = env.findClass("java/lang/Math"); + jmethodID methodId = env.findStaticMethod(javaMathClass, "max", "(II)I"); + if (methodId != 0) { + jint a = 2; + jint b = 4; + jint max = QJniObject::callStaticMethod<jint>(javaMathClass, methodId, a, b); + } + \endcode +*/ + +/*! + \fn template <typename Ret, typename ...Args> auto QJniObject::callStaticMethod(jclass clazz, const char *methodName, Args &&...args) + \since 6.4 + + Calls the static method \a methodName on \a clazz and returns the value of type \c Ret + (unless \c Ret is \c void). If \c Ret is a jobject type, then the returned value will + be a QJniObject. + + \code + QJniEnvironment env; + jclass javaMathClass = env.findClass("java/lang/Math"); + jdouble randNr = QJniObject::callStaticMethod<jdouble>(javaMathClass, "random"); + \endcode + + The method signature is deduced at compile time from \c Ret and the types of \a args. +*/ + +/*! + \fn template <typename Klass, typename Ret, typename ...Args> auto QJniObject::callStaticMethod(const char *methodName, Args &&...args) + \since 6.7 + + Calls the static method \a methodName on the class \c Klass and returns the value of type + \c Ret (unless \c Ret is \c void). If \c Ret is a jobject type, then the returned value will + be a QJniObject. + + The method signature is deduced at compile time from \c Ret and the types of \a args. + \c Klass needs to be a C++ type with a registered type mapping to a Java type. +*/ + +/*! + \fn QJniObject QJniObject::callObjectMethod(const char *methodName, const char *signature, ...) const + + Calls the Java object's method \a methodName with \a signature specifying + the types of any subsequent arguments. + + \code + QJniObject myJavaString = QJniObject::fromString("Hello, Java"); + QJniObject mySubstring = myJavaString.callObjectMethod("substring", + "(II)Ljava/lang/String;", 7, 11); + \endcode +*/ +QJniObject QJniObject::callObjectMethod(const char *methodName, const char *signature, ...) const +{ + jmethodID id = getCachedMethodID(jniEnv(), methodName, signature); + if (id) { + va_list args; + va_start(args, signature); + QJniObject res = getCleanJniObject(jniEnv()->CallObjectMethodV(d->m_jobject, id, args), jniEnv()); + va_end(args); + return res; + } + + return QJniObject(); +} + +/*! + \fn QJniObject QJniObject::callStaticObjectMethod(const char *className, const char *methodName, const char *signature, ...) + + Calls the static method \a methodName from the class \a className with \a signature + specifying the types of any subsequent arguments. + + \code + QJniObject thread = QJniObject::callStaticObjectMethod("java/lang/Thread", "currentThread", + "()Ljava/lang/Thread;"); + QJniObject string = QJniObject::callStaticObjectMethod("java/lang/String", "valueOf", + "(I)Ljava/lang/String;", 10); + \endcode +*/ +QJniObject QJniObject::callStaticObjectMethod(const char *className, const char *methodName, + const char *signature, ...) +{ + JNIEnv *env = QJniEnvironment::getJniEnv(); + jclass clazz = QJniObject::loadClass(className, env); + if (clazz) { + jmethodID id = QJniObject::getCachedMethodID(env, clazz, + className, + methodName, signature, true); + if (id) { + va_list args; + va_start(args, signature); + QJniObject res = getCleanJniObject(env->CallStaticObjectMethodV(clazz, id, args), env); + va_end(args); + return res; + } + } + + return QJniObject(); +} + +/*! + \fn QJniObject QJniObject::callStaticObjectMethod(jclass clazz, const char *methodName, const char *signature, ...) + + Calls the static method \a methodName from class \a clazz with \a signature + specifying the types of any subsequent arguments. +*/ +QJniObject QJniObject::callStaticObjectMethod(jclass clazz, const char *methodName, + const char *signature, ...) +{ + if (clazz) { + QJniEnvironment env; + jmethodID id = getMethodID(env.jniEnv(), clazz, methodName, signature, true); + if (id) { + va_list args; + va_start(args, signature); + QJniObject res = getCleanJniObject(env->CallStaticObjectMethodV(clazz, id, args), env.jniEnv()); + va_end(args); + return res; + } + } + + return QJniObject(); +} + +/*! + \fn QJniObject QJniObject::callStaticObjectMethod(jclass clazz, jmethodID methodId, ...) + + Calls the static method identified by \a methodId from the class \a clazz + with any subsequent arguments. Useful when \a clazz and \a methodId are + already cached from previous operations. + + \code + QJniEnvironment env; + jclass clazz = env.findClass("java/lang/String"); + jmethodID methodId = env.findStaticMethod(clazz, "valueOf", "(I)Ljava/lang/String;"); + if (methodId != 0) + QJniObject str = QJniObject::callStaticObjectMethod(clazz, methodId, 10); + \endcode +*/ +QJniObject QJniObject::callStaticObjectMethod(jclass clazz, jmethodID methodId, ...) +{ + if (clazz && methodId) { + QJniEnvironment env; + va_list args; + va_start(args, methodId); + QJniObject res = getCleanJniObject(env->CallStaticObjectMethodV(clazz, methodId, args), env.jniEnv()); + va_end(args); + return res; + } + + return QJniObject(); +} + +/*! + \fn template<typename Ret, typename ...Args> QJniObject QJniObject::callObjectMethod(const char *methodName, Args &&...args) const + \since 6.4 + + Calls the Java objects method \a methodName with arguments \a args and returns a + new QJniObject for the returned Java object. + + \code + QJniObject myJavaString = QJniObject::fromString("Hello, Java"); + QJniObject myJavaString2 = myJavaString1.callObjectMethod<jstring>("toString"); + \endcode + + The method signature is deduced at compile time from \c Ret and the types of \a args. +*/ + +/*! + \fn template<typename Ret, typename ...Args> QJniObject QJniObject::callStaticObjectMethod(const char *className, const char *methodName, Args &&...args) + \since 6.4 + + Calls the static method with \a methodName on the class \a className, passing + arguments \a args, and returns a new QJniObject for the returned Java object. + + \code + QJniObject string = QJniObject::callStaticObjectMethod<jstring>("CustomClass", "getClassName"); + \endcode + + The method signature is deduced at compile time from \c Ret and the types of \a args. +*/ + +/*! + \fn template<typename Ret, typename ...Args> QJniObject QJniObject::callStaticObjectMethod(jclass clazz, const char *methodName, Args &&...args) + \since 6.4 + + Calls the static method with \a methodName on \a clazz, passing arguments \a args, + and returns a new QJniObject for the returned Java object. +*/ + +/*! + \fn template <typename T, std::enable_if_t<std::is_convertible_v<T, jobject>, bool> = true> QJniObject &QJniObject::operator=(T object) + + Replace the current object with \a object. The old Java object will be released. +*/ + +/*! + \fn template <typename T> void QJniObject::setStaticField(const char *className, const char *fieldName, const char *signature, T value); + + Sets the static field \a fieldName on the class \a className to \a value + using the setter with \a signature. + +*/ + +/*! + \fn template <typename T> void QJniObject::setStaticField(jclass clazz, const char *fieldName, const char *signature, T value); + + Sets the static field \a fieldName on the class \a clazz to \a value using + the setter with \a signature. +*/ + +/*! + \fn template<typename T> T QJniObject::getField(const char *fieldName) const + + Retrieves the value of the field \a fieldName. + + \code + QJniObject volumeControl("org/qtproject/qt/TestClass"); + jint fieldValue = volumeControl.getField<jint>("FIELD_NAME"); + \endcode +*/ + +/*! + \fn template<typename T> T QJniObject::getStaticField(const char *className, const char *fieldName) + + Retrieves the value from the static field \a fieldName on the class \a className. +*/ + +/*! + \fn template<typename T> T QJniObject::getStaticField(jclass clazz, const char *fieldName) + + Retrieves the value from the static field \a fieldName on \a clazz. +*/ + +/*! + \fn template <typename Klass, typename T> auto QJniObject::getStaticField(const char *fieldName) + + Retrieves the value from the static field \a fieldName for the class \c Klass. + + \c Klass needs to be a C++ type with a registered type mapping to a Java type. +*/ + +/*! + \fn template <typename T> void QJniObject::setStaticField(const char *className, const char *fieldName, T value) + + Sets the static field \a fieldName of the class \a className to \a value. +*/ + +/*! + \fn template <typename T> void QJniObject::setStaticField(jclass clazz, const char *fieldName, T value) + + Sets the static field \a fieldName of the class \a clazz to \a value. +*/ + +/*! + \fn template <typename Klass, typename T> auto QJniObject::setStaticField(const char *fieldName, T value) + + Sets the static field \a fieldName of the class \c Klass to \a value. + + \c Klass needs to be a C++ type with a registered type mapping to a Java type. +*/ + +/*! + \fn QJniObject QJniObject::getStaticObjectField(const char *className, const char *fieldName, const char *signature) + + Retrieves a JNI object from the field \a fieldName with \a signature from + class \a className. + + \note This function can be used without a template type. + + \code + QJniObject jobj = QJniObject::getStaticObjectField("class/with/Fields", "FIELD_NAME", + "Ljava/lang/String;"); + \endcode +*/ +QJniObject QJniObject::getStaticObjectField(const char *className, + const char *fieldName, + const char *signature) +{ + JNIEnv *env = QJniEnvironment::getJniEnv(); + jclass clazz = QJniObject::loadClass(className, env); + if (!clazz) + return QJniObject(); + jfieldID id = QJniObject::getCachedFieldID(env, clazz, + className, + fieldName, + signature, true); + if (!id) + return QJniObject(); + + return getCleanJniObject(env->GetStaticObjectField(clazz, id), env); +} + +/*! + \fn QJniObject QJniObject::getStaticObjectField(jclass clazz, const char *fieldName, const char *signature) + + Retrieves a JNI object from the field \a fieldName with \a signature from + class \a clazz. + + \note This function can be used without a template type. + + \code + QJniObject jobj = QJniObject::getStaticObjectField(clazz, "FIELD_NAME", "Ljava/lang/String;"); + \endcode +*/ +QJniObject QJniObject::getStaticObjectField(jclass clazz, const char *fieldName, + const char *signature) +{ + JNIEnv *env = QJniEnvironment::getJniEnv(); + jfieldID id = getFieldID(env, clazz, fieldName, signature, true); + return getCleanJniObject(env->GetStaticObjectField(clazz, id), env); +} + +/*! + \fn template <typename T> void QJniObject::setField(const char *fieldName, const char *signature, T value) + + Sets the value of \a fieldName with \a signature to \a value. + + \code + QJniObject stringArray = ...; + QJniObject obj = ...; + obj.setObjectField<jobjectArray>("KEY_VALUES", "([Ljava/lang/String;)V", + stringArray.object<jobjectArray>()) + \endcode +*/ + +/*! + \fn template<typename T> QJniObject QJniObject::getObjectField(const char *fieldName) const + + Retrieves a JNI object from the field \a fieldName. + + \code + QJniObject field = jniObject.getObjectField<jstring>("FIELD_NAME"); + \endcode +*/ + +/*! + \fn QJniObject QJniObject::getObjectField(const char *fieldName, const char *signature) const + + Retrieves a JNI object from the field \a fieldName with \a signature. + + \note This function can be used without a template type. + + \code + QJniObject field = jniObject.getObjectField("FIELD_NAME", "Ljava/lang/String;"); + \endcode +*/ +QJniObject QJniObject::getObjectField(const char *fieldName, const char *signature) const +{ + jfieldID id = getCachedFieldID(jniEnv(), fieldName, signature); + if (!id) + return QJniObject(); + + return getCleanJniObject(jniEnv()->GetObjectField(d->m_jobject, id), jniEnv()); +} + +/*! + \fn template <typename T> void QJniObject::setField(const char *fieldName, T value) + + Sets the value of \a fieldName to \a value. + + \code + QJniObject obj; + obj.setField<jint>("AN_INT_FIELD", 10); + jstring myString = ...; + obj.setField<jstring>("A_STRING_FIELD", myString); + \endcode +*/ + +/*! + \fn template<typename T> QJniObject QJniObject::getStaticObjectField(const char *className, const char *fieldName) + + Retrieves the object from the field \a fieldName on the class \a className. + + \code + QJniObject jobj = QJniObject::getStaticObjectField<jstring>("class/with/Fields", "FIELD_NAME"); + \endcode +*/ + +/*! + \fn template<typename T> QJniObject QJniObject::getStaticObjectField(jclass clazz, const char *fieldName) + + Retrieves the object from the field \a fieldName on \a clazz. + + \code + QJniObject jobj = QJniObject::getStaticObjectField<jstring>(clazz, "FIELD_NAME"); + \endcode +*/ + +/*! + \fn QJniObject QJniObject::fromString(const QString &string) + + Creates a Java string from the QString \a string and returns a QJniObject holding that string. + + \code + QString myQString = "QString"; + QJniObject myJavaString = QJniObject::fromString(myQString); + \endcode + + \sa toString() +*/ +QJniObject QJniObject::fromString(const QString &string) +{ + QJniEnvironment env; + jstring stringRef = env->NewString(reinterpret_cast<const jchar*>(string.constData()), + string.length()); + QJniObject stringObject = getCleanJniObject(stringRef, env.jniEnv()); + stringObject.d->m_className = "java/lang/String"; + return stringObject; +} + +/*! + \fn QString QJniObject::toString() const + + Returns a QString with a string representation of the java object. + Calling this function on a Java String object is a convenient way of getting the actual string + data. + + \code + QJniObject string = ...; // "Hello Java" + QString qstring = string.toString(); // "Hello Java" + \endcode + + \sa fromString() +*/ +QString QJniObject::toString() const +{ + if (!isValid()) + return QString(); + + QJniObject string = callObjectMethod<jstring>("toString"); + const int strLength = string.jniEnv()->GetStringLength(string.object<jstring>()); + QString res(strLength, Qt::Uninitialized); + string.jniEnv()->GetStringRegion(string.object<jstring>(), 0, strLength, reinterpret_cast<jchar *>(res.data())); + return res; +} + +/*! + \fn bool QJniObject::isClassAvailable(const char *className) + + Returns true if the Java class \a className is available. + + \code + if (QJniObject::isClassAvailable("java/lang/String")) { + // condition statement + } + \endcode +*/ +bool QJniObject::isClassAvailable(const char *className) +{ + QJniEnvironment env; + + if (!env.jniEnv()) + return false; + + return loadClass(className, env.jniEnv()); +} + +/*! + \fn bool QJniObject::isValid() const + + Returns true if this instance holds a valid Java object. + + \code + QJniObject qjniObject; // ==> isValid() == false + QJniObject qjniObject(0) // ==> isValid() == false + QJniObject qjniObject("could/not/find/Class") // ==> isValid() == false + \endcode +*/ +bool QJniObject::isValid() const +{ + return d->m_jobject; +} + +/*! + \fn QJniObject QJniObject::fromLocalRef(jobject localRef) + + Creates a QJniObject from the local JNI reference \a localRef. + This function takes ownership of \a localRef and frees it before returning. + + \note Only call this function with a local JNI reference. For example, most raw JNI calls, + through the JNI environment, return local references to a java object. + + \code + jobject localRef = env->GetObjectArrayElement(array, index); + QJniObject element = QJniObject::fromLocalRef(localRef); + \endcode +*/ +QJniObject QJniObject::fromLocalRef(jobject lref) +{ + QJniObject obj(lref); + obj.jniEnv()->DeleteLocalRef(lref); + return obj; +} + +bool QJniObject::isSameObject(jobject obj) const +{ + if (d->m_jobject == obj) + return true; + if (!d->m_jobject || !obj) + return false; + return jniEnv()->IsSameObject(d->m_jobject, obj); +} + +bool QJniObject::isSameObject(const QJniObject &other) const +{ + return isSameObject(other.d->m_jobject); +} + +void QJniObject::assign(jobject obj) +{ + if (d && isSameObject(obj)) + return; + + d = QSharedPointer<QJniObjectPrivate>::create(); + if (obj) { + JNIEnv *env = QJniEnvironment::getJniEnv(); + d->m_jobject = env->NewGlobalRef(obj); + jclass objectClass = env->GetObjectClass(obj); + d->m_jclass = static_cast<jclass>(env->NewGlobalRef(objectClass)); + env->DeleteLocalRef(objectClass); + } +} + +jobject QJniObject::javaObject() const +{ + return d->m_jobject; +} + +QT_END_NAMESPACE |