// Copyright (C) 2022 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 #ifndef QJNIOBJECT_H #define QJNIOBJECT_H #include #if defined(Q_QDOC) || defined(Q_OS_ANDROID) #include #include #include QT_BEGIN_NAMESPACE class QJniObjectPrivate; class QJniObject; namespace QtJniTypes { namespace Detail { // any type with an "jobject object()" member function stores a global reference template struct StoresGlobalRefTest : std::false_type {}; template struct StoresGlobalRefTest().object())>> : std::is_same().object()), jobject> {}; // detect if a type is std::expected-like template struct CallerHandlesException : std::false_type { using value_type = R; }; template struct CallerHandlesException> : std::true_type { using value_type = typename R::value_type; }; template static constexpr bool callerHandlesException = CallerHandlesException::value; template struct LocalFrame { using ReturnType = Ret; mutable JNIEnv *env; bool hasFrame = false; explicit LocalFrame(JNIEnv *env = nullptr) noexcept : env(env) { } ~LocalFrame() { if (hasFrame) env->PopLocalFrame(nullptr); } bool ensureFrame() { if (!hasFrame) hasFrame = jniEnv()->PushLocalFrame(sizeof...(Args)) == 0; return hasFrame; } JNIEnv *jniEnv() const { if (!env) env = QJniEnvironment::getJniEnv(); return env; } bool checkAndClearExceptions() const { if constexpr (callerHandlesException) return false; else return QJniEnvironment::checkAndClearExceptions(jniEnv()); } template auto convertToJni(T &&value) { using Type = q20::remove_cvref_t; using ResultType = decltype(QtJniTypes::Traits::convertToJni(jniEnv(), std::declval())); if constexpr (std::is_base_of_v, std::remove_pointer_t>) { // Make sure the local frame is engaged if we create a jobject, unless // we know that the value stores a global reference that it returns. if constexpr (!qxp::is_detected_v) { if (!ensureFrame()) return ResultType{}; } } return QtJniTypes::Traits::convertToJni(jniEnv(), std::forward(value)); } template auto convertFromJni(QJniObject &&object) { using Type = q20::remove_cvref_t; return QtJniTypes::Traits::convertFromJni(std::move(object)); } template auto convertFromJni(jobject object); auto makeResult() { if constexpr (callerHandlesException) { JNIEnv *env = jniEnv(); if (env->ExceptionCheck()) { jthrowable exception = env->ExceptionOccurred(); env->ExceptionClear(); return ReturnType(typename ReturnType::unexpected_type(exception)); } return ReturnType(); } else { checkAndClearExceptions(); } } template auto makeResult(Value &&value) { if constexpr (callerHandlesException) { auto maybeValue = makeResult(); if (maybeValue) return ReturnType(std::forward(value)); return std::move(maybeValue); } else { checkAndClearExceptions(); return std::forward(value); } } }; } } class Q_CORE_EXPORT QJniObject { template using LocalFrame = QtJniTypes::Detail::LocalFrame; public: QJniObject(); explicit QJniObject(const char *className); explicit QJniObject(const char *className, const char *signature, ...); template>...>>* = nullptr #endif > explicit QJniObject(const char *className, Args &&...args) : QJniObject(LocalFrame{}, className, std::forward(args)...) { } private: template explicit QJniObject(LocalFrame localFrame, const char *className, Args &&...args) : QJniObject(className, QtJniTypes::constructorSignature().data(), localFrame.convertToJni(std::forward(args))...) { } public: explicit QJniObject(jclass clazz); explicit QJniObject(jclass clazz, const char *signature, ...); template>...>>* = nullptr #endif > explicit QJniObject(jclass clazz, Args &&...args) : QJniObject(clazz, QtJniTypes::constructorSignature().data(), std::forward(args)...) {} QJniObject(jobject globalRef); QJniObject(const QJniObject &other) noexcept = default; QJniObject(QJniObject &&other) noexcept = default; QJniObject &operator=(const QJniObject &other) noexcept = default; QJniObject &operator=(QJniObject &&other) noexcept = default; ~QJniObject(); void swap(QJniObject &other) noexcept { d.swap(other.d); } template = true #endif > static inline auto construct(Args &&...args) { LocalFrame frame; jclass clazz = QJniObject::loadClassKeepExceptions(QtJniTypes::Traits::className().data(), frame.jniEnv()); auto res = clazz ? QJniObject(clazz, QtJniTypes::constructorSignature().data(), frame.convertToJni(std::forward(args))...) : QtJniTypes::Detail::callerHandlesException ? QJniObject(Qt::Initialization::Uninitialized) : QJniObject(); return frame.makeResult(std::move(res)); } jobject object() const; template T object() const { QtJniTypes::assertObjectType(); return static_cast(javaObject()); } jclass objectClass() const; QByteArray className() const; template = true #endif > auto callMethod(const char *methodName, const char *signature, Args &&...args) const { using Ret = typename QtJniTypes::Detail::CallerHandlesException::value_type; LocalFrame frame(jniEnv()); jmethodID id = getCachedMethodID(frame.jniEnv(), methodName, signature); if constexpr (QtJniTypes::isObjectType()) { return frame.makeResult(frame.template convertFromJni(callObjectMethodImpl( id, frame.convertToJni(std::forward(args))...)) ); } else { if (id) { if constexpr (std::is_same_v) { callVoidMethodV(frame.jniEnv(), id, frame.convertToJni(std::forward(args))...); } else { Ret res{}; callMethodForType(frame.jniEnv(), res, object(), id, frame.convertToJni(std::forward(args))...); return frame.makeResult(res); } } if constexpr (!std::is_same_v) return frame.makeResult(Ret{}); else return frame.makeResult(); } } template = true #endif > auto callMethod(const char *methodName, Args &&...args) const { constexpr auto signature = QtJniTypes::methodSignature(); return callMethod(methodName, signature.data(), std::forward(args)...); } template = true #endif > QJniObject callObjectMethod(const char *methodName, Args &&...args) const { QtJniTypes::assertObjectType(); constexpr auto signature = QtJniTypes::methodSignature(); LocalFrame frame(jniEnv()); auto object = frame.template convertFromJni(callObjectMethod(methodName, signature, frame.convertToJni(std::forward(args))...)); frame.checkAndClearExceptions(); return object; } QJniObject callObjectMethod(const char *methodName, const char *signature, ...) const; template static auto callStaticMethod(const char *className, const char *methodName, const char *signature, Args &&...args) { LocalFrame frame; jclass clazz = QJniObject::loadClass(className, frame.jniEnv()); return callStaticMethod(clazz, methodName, signature, std::forward(args)...); } template static auto callStaticMethod(jclass clazz, const char *methodName, const char *signature, Args &&...args) { LocalFrame frame; jmethodID id = clazz ? getMethodID(frame.jniEnv(), clazz, methodName, signature, true) : 0; return callStaticMethod(clazz, id, std::forward(args)...); } template = true #endif > static auto callStaticMethod(jclass clazz, jmethodID methodId, Args &&...args) { using Ret = typename QtJniTypes::Detail::CallerHandlesException::value_type; LocalFrame frame; if constexpr (QtJniTypes::isObjectType()) { return frame.makeResult(frame.template convertFromJni(callStaticObjectMethod( clazz, methodId, frame.convertToJni(std::forward(args))...)) ); } else { if (clazz && methodId) { if constexpr (std::is_same_v) { callStaticMethodForVoid(frame.jniEnv(), clazz, methodId, frame.convertToJni(std::forward(args))...); } else { Ret res{}; callStaticMethodForType(frame.jniEnv(), res, clazz, methodId, frame.convertToJni(std::forward(args))...); return frame.makeResult(res); } } if constexpr (!std::is_same_v) return frame.makeResult(Ret{}); else return frame.makeResult(); } } template = true #endif > static auto callStaticMethod(const char *className, const char *methodName, Args &&...args) { using Ret = typename QtJniTypes::Detail::CallerHandlesException::value_type; LocalFrame frame; jclass clazz = QJniObject::loadClass(className, frame.jniEnv()); const jmethodID id = clazz ? getMethodID(frame.jniEnv(), clazz, methodName, QtJniTypes::methodSignature().data(), true) : 0; return callStaticMethod(clazz, id, std::forward(args)...); } template = true #endif > static auto callStaticMethod(jclass clazz, const char *methodName, Args &&...args) { constexpr auto signature = QtJniTypes::methodSignature(); return callStaticMethod(clazz, methodName, signature.data(), std::forward(args)...); } template = true #endif > static auto callStaticMethod(const char *methodName, Args &&...args) { LocalFrame frame; const jclass clazz = QJniObject::loadClass(QtJniTypes::Traits::className().data(), frame.jniEnv()); const jmethodID id = clazz ? getMethodID(frame.jniEnv(), clazz, methodName, QtJniTypes::methodSignature().data(), true) : 0; return callStaticMethod(clazz, id, std::forward(args)...); } static QJniObject callStaticObjectMethod(const char *className, const char *methodName, const char *signature, ...); static QJniObject callStaticObjectMethod(jclass clazz, const char *methodName, const char *signature, ...); static QJniObject callStaticObjectMethod(jclass clazz, jmethodID methodId, ...); template = true #endif > static QJniObject callStaticObjectMethod(const char *className, const char *methodName, Args &&...args) { QtJniTypes::assertObjectType(); constexpr auto signature = QtJniTypes::methodSignature(); LocalFrame frame; return frame.template convertFromJni(callStaticObjectMethod(className, methodName, signature.data(), frame.convertToJni(std::forward(args))...)); } template = true #endif > static QJniObject callStaticObjectMethod(jclass clazz, const char *methodName, Args &&...args) { QtJniTypes::assertObjectType(); constexpr auto signature = QtJniTypes::methodSignature(); LocalFrame frame; return frame.template convertFromJni(callStaticObjectMethod(clazz, methodName, signature.data(), frame.convertToJni(std::forward(args))...)); } template = true #endif > auto getField(const char *fieldName) const { using T = typename QtJniTypes::Detail::CallerHandlesException::value_type; LocalFrame frame(jniEnv()); constexpr auto signature = QtJniTypes::fieldSignature(); jfieldID id = getCachedFieldID(frame.jniEnv(), fieldName, signature); if constexpr (QtJniTypes::isObjectType()) { return frame.makeResult(frame.template convertFromJni(getObjectFieldImpl( frame.jniEnv(), id)) ); } else { T res{}; if (id) getFieldForType(frame.jniEnv(), res, object(), id); return frame.makeResult(res); } } template = true #endif > static auto getStaticField(const char *fieldName) { return getStaticField(QtJniTypes::Traits::className(), fieldName); } template (), bool> = true #endif > QJniObject getObjectField(const char *fieldName) const { constexpr auto signature = QtJniTypes::fieldSignature(); return getObjectField(fieldName, signature); } QJniObject getObjectField(const char *fieldName, const char *signature) const; template = true #endif > static auto getStaticField(const char *className, const char *fieldName) { using T = typename QtJniTypes::Detail::CallerHandlesException::value_type; LocalFrame frame; jclass clazz = QJniObject::loadClass(className, frame.jniEnv()); return getStaticField(clazz, fieldName); } template = true #endif > static auto getStaticField(jclass clazz, const char *fieldName) { using T = typename QtJniTypes::Detail::CallerHandlesException::value_type; LocalFrame frame; constexpr auto signature = QtJniTypes::fieldSignature(); jfieldID id = clazz ? getFieldID(frame.jniEnv(), clazz, fieldName, signature, true) : nullptr; if constexpr (QtJniTypes::isObjectType()) { return frame.makeResult(frame.template convertFromJni(getStaticObjectFieldImpl( frame.jniEnv(), clazz, id)) ); } else { T res{}; if (id) getStaticFieldForType(frame.jniEnv(), res, clazz, id); return frame.makeResult(res); } } template (), bool> = true #endif > static QJniObject getStaticObjectField(const char *className, const char *fieldName) { constexpr auto signature = QtJniTypes::fieldSignature(); return getStaticObjectField(className, fieldName, signature); } static QJniObject getStaticObjectField(const char *className, const char *fieldName, const char *signature); template (), bool> = true #endif > static QJniObject getStaticObjectField(jclass clazz, const char *fieldName) { constexpr auto signature = QtJniTypes::fieldSignature(); return getStaticObjectField(clazz, fieldName, signature); } static QJniObject getStaticObjectField(jclass clazz, const char *fieldName, const char *signature); template = true #endif > auto setField(const char *fieldName, Type value) { // handle old code explicitly specifying a non-return type for Ret using T = std::conditional_t && !QtJniTypes::Detail::callerHandlesException, Ret, Type>; LocalFrame frame(jniEnv()); constexpr auto signature = QtJniTypes::fieldSignature(); jfieldID id = getCachedFieldID(jniEnv(), fieldName, signature); if (id) setFieldForType(jniEnv(), object(), id, value); return frame.makeResult(); } template = true #endif > auto setField(const char *fieldName, const char *signature, Type value) { // handle old code explicitly specifying a non-return type for Ret using T = std::conditional_t && !QtJniTypes::Detail::callerHandlesException, Ret, Type>; LocalFrame frame(jniEnv()); jfieldID id = getCachedFieldID(frame.jniEnv(), fieldName, signature); if (id) setFieldForType(jniEnv(), object(), id, value); return frame.makeResult(); } template = true #endif > static auto setStaticField(const char *className, const char *fieldName, Type value) { // handle old code explicitly specifying a non-return type for Ret using T = std::conditional_t && !QtJniTypes::Detail::callerHandlesException, Ret, Type>; LocalFrame frame; if (jclass clazz = QJniObject::loadClass(className, frame.jniEnv())) { constexpr auto signature = QtJniTypes::fieldSignature>(); jfieldID id = getCachedFieldID(frame.jniEnv(), clazz, className, fieldName, signature, true); if (id) setStaticFieldForType(frame.jniEnv(), clazz, id, value); } return frame.makeResult(); } template = true #endif > static auto setStaticField(const char *className, const char *fieldName, const char *signature, Type value) { // handle old code explicitly specifying a non-return type for Ret using T = std::conditional_t && !QtJniTypes::Detail::callerHandlesException, Ret, Type>; LocalFrame frame; if (jclass clazz = QJniObject::loadClass(className, frame.jniEnv())) { jfieldID id = getCachedFieldID(frame.jniEnv(), clazz, className, fieldName, signature, true); if (id) setStaticFieldForType(frame.jniEnv(), clazz, id, value); } return frame.makeResult(); } template = true #endif > static auto setStaticField(jclass clazz, const char *fieldName, const char *signature, Type value) { // handle old code explicitly specifying a non-return type for Ret using T = std::conditional_t && !QtJniTypes::Detail::callerHandlesException, Ret, Type>; LocalFrame frame; jfieldID id = getFieldID(frame.jniEnv(), clazz, fieldName, signature, true); if (id) setStaticFieldForType(frame.jniEnv(), clazz, id, value); return frame.makeResult(); } template = true #endif > static auto setStaticField(jclass clazz, const char *fieldName, Type value) { return setStaticField(clazz, fieldName, QtJniTypes::fieldSignature>(), value); } template = true #endif > static auto setStaticField(const char *fieldName, Type value) { return setStaticField(QtJniTypes::Traits::className(), fieldName, value); } static QJniObject fromString(const QString &string); QString toString() const; static bool isClassAvailable(const char *className); bool isValid() const; // This function takes ownership of the jobject and releases the local ref. before returning. static QJniObject fromLocalRef(jobject lref); template , bool> = true> QJniObject &operator=(T obj) { assign(static_cast(obj)); return *this; } protected: QJniObject(Qt::Initialization) {} JNIEnv *jniEnv() const noexcept; private: static jclass loadClass(const QByteArray &className, JNIEnv *env); static jclass loadClassKeepExceptions(const QByteArray &className, JNIEnv *env); #if QT_CORE_REMOVED_SINCE(6, 7) // these need to stay in the ABI as they were used in inline methods before 6.7 static jclass loadClass(const QByteArray &className, JNIEnv *env, bool binEncoded); static QByteArray toBinaryEncClassName(const QByteArray &className); void callVoidMethodV(JNIEnv *env, jmethodID id, va_list args) const; #endif static jfieldID getCachedFieldID(JNIEnv *env, jclass clazz, const QByteArray &className, const char *name, const char *signature, bool isStatic = false); jfieldID getCachedFieldID(JNIEnv *env, const char *name, const char *signature, bool isStatic = false) const; static jmethodID getCachedMethodID(JNIEnv *env, jclass clazz, const QByteArray &className, const char *name, const char *signature, bool isStatic = false); jmethodID getCachedMethodID(JNIEnv *env, const char *name, const char *signature, bool isStatic = false) const; static jfieldID getFieldID(JNIEnv *env, jclass clazz, const char *name, const char *signature, bool isStatic = false); static jmethodID getMethodID(JNIEnv *env, jclass clazz, const char *name, const char *signature, bool isStatic = false); void callVoidMethodV(JNIEnv *env, jmethodID id, ...) const; bool isSameObject(jobject obj) const; bool isSameObject(const QJniObject &other) const; void assign(jobject obj); jobject javaObject() const; friend bool operator==(const QJniObject &, const QJniObject &); friend bool operator!=(const QJniObject&, const QJniObject&); template static constexpr void callMethodForType(JNIEnv *env, T &res, jobject obj, jmethodID id, ...) { if (!id) return; va_list args = {}; va_start(args, id); QtJniTypes::Caller::callMethodForType(env, res, obj, id, args); va_end(args); } jobject callObjectMethodImpl(jmethodID method, ...) const { va_list args; va_start(args, method); jobject res = method ? jniEnv()->CallObjectMethodV(javaObject(), method, args) : nullptr; va_end(args); return res; } template static constexpr void callStaticMethodForType(JNIEnv *env, T &res, jclass clazz, jmethodID id, ...) { if (!clazz || !id) return; va_list args = {}; va_start(args, id); QtJniTypes::Caller::callStaticMethodForType(env, res, clazz, id, args); va_end(args); } static void callStaticMethodForVoid(JNIEnv *env, jclass clazz, jmethodID id, ...) { if (!clazz || !id) return; va_list args; va_start(args, id); env->CallStaticVoidMethodV(clazz, id, args); va_end(args); } template static constexpr void getFieldForType(JNIEnv *env, T &res, jobject obj, jfieldID id) { if (!id) return; QtJniTypes::Caller::getFieldForType(env, res, obj, id); } template static constexpr void getStaticFieldForType(JNIEnv *env, T &res, jclass clazz, jfieldID id) { QtJniTypes::Caller::getStaticFieldForType(env, res, clazz, id); } template static constexpr void setFieldForType(JNIEnv *env, jobject obj, jfieldID id, Type value) { if (!id) return; using T = q20::remove_cvref_t; if constexpr (QtJniTypes::isObjectType()) { LocalFrame frame(env); env->SetObjectField(obj, id, static_cast(frame.convertToJni(value))); } else { using ValueType = typename QtJniTypes::Detail::CallerHandlesException::value_type; QtJniTypes::Caller::setFieldForType(env, obj, id, value); } } jobject getObjectFieldImpl(JNIEnv *env, jfieldID field) const { return field ? env->GetObjectField(javaObject(), field) : nullptr; } static jobject getStaticObjectFieldImpl(JNIEnv *env, jclass clazz, jfieldID field) { return clazz && field ? env->GetStaticObjectField(clazz, field) : nullptr; } template static constexpr void setStaticFieldForType(JNIEnv *env, jclass clazz, jfieldID id, Type value) { if (!clazz || !id) return; using T = q20::remove_cvref_t; if constexpr (QtJniTypes::isObjectType()) { LocalFrame frame(env); env->SetStaticObjectField(clazz, id, static_cast(frame.convertToJni(value))); } else { QtJniTypes::Caller::setStaticFieldForType(env, clazz, id, value); } } friend QJniObjectPrivate; QSharedPointer d; }; inline bool operator==(const QJniObject &obj1, const QJniObject &obj2) { return obj1.isSameObject(obj2); } inline bool operator!=(const QJniObject &obj1, const QJniObject &obj2) { return !obj1.isSameObject(obj2); } namespace QtJniTypes { struct JObjectBase { operator QJniObject() const { return m_object; } bool isValid() const { return m_object.isValid(); } jclass objectClass() const { return m_object.objectClass(); } QString toString() const { return m_object.toString(); } template T object() const { return m_object.object(); } protected: JObjectBase() = default; JObjectBase(const JObjectBase &) = default; JObjectBase(JObjectBase &&) = default; JObjectBase &operator=(const JObjectBase &) = default; JObjectBase &operator=(JObjectBase &&) = default; ~JObjectBase() = default; Q_IMPLICIT JObjectBase(jobject object) : m_object(object) {} Q_IMPLICIT JObjectBase(const QJniObject &object) : m_object(object) {} Q_IMPLICIT JObjectBase(QJniObject &&object) noexcept : m_object(std::move(object)) {} QJniObject m_object; }; template class JObject : public JObjectBase { public: using Class = Type; JObject() : JObjectBase{QJniObject(QtJniTypes::Traits::className())} {} Q_IMPLICIT JObject(jobject object) : JObjectBase(object) {} Q_IMPLICIT JObject(const QJniObject &object) : JObjectBase(object) {} Q_IMPLICIT JObject(QJniObject &&object) noexcept : JObjectBase(std::move(object)) {} // base class SMFs are protected, make them public: JObject(const JObject &other) = default; JObject(JObject &&other) noexcept = default; JObject &operator=(const JObject &other) = default; JObject &operator=(JObject &&other) noexcept = default; ~JObject() = default; template, JObject>, bool> = true , IfValidSignatureTypes = true > explicit JObject(Arg && arg, Args &&...args) : JObjectBase{QJniObject(QtJniTypes::Traits::className(), std::forward(arg), std::forward(args)...)} {} // named constructors avoid ambiguities static JObject fromJObject(jobject object) { return JObject(object); } template static JObject construct(Args &&...args) { return JObject(std::forward(args)...); } static JObject fromLocalRef(jobject lref) { return JObject(QJniObject::fromLocalRef(lref)); } #ifdef Q_QDOC // from JObjectBase, which we don't document bool isValid() const; jclass objectClass() const; QString toString() const; template object() const; #endif static bool registerNativeMethods(std::initializer_list methods) { QJniEnvironment env; return env.registerNativeMethods(methods); } // public API forwarding to QJniObject, with the implicit Class template parameter template = true #endif > static auto callStaticMethod(const char *name, Args &&...args) { return QJniObject::callStaticMethod(name, std::forward(args)...); } template = true #endif > static auto getStaticField(const char *field) { return QJniObject::getStaticField(field); } template = true #endif > static auto setStaticField(const char *field, T &&value) { return QJniObject::setStaticField(field, std::forward(value)); } // keep only these overloads, the rest is made private template = true #endif > auto callMethod(const char *method, Args &&...args) const { return m_object.callMethod(method, std::forward(args)...); } template = true #endif > auto getField(const char *fieldName) const { return m_object.getField(fieldName); } template = true #endif > auto setField(const char *fieldName, T &&value) { return m_object.setField(fieldName, std::forward(value)); } QByteArray className() const { return QtJniTypes::Traits::className().data(); } static bool isClassAvailable() { return QJniObject::isClassAvailable(QtJniTypes::Traits::className().data()); } private: friend bool comparesEqual(const JObject &lhs, const JObject &rhs) { return lhs.m_object == rhs.m_object; } Q_DECLARE_EQUALITY_COMPARABLE_NON_NOEXCEPT(JObject); }; template struct Traits> { static constexpr auto signature() { return Traits::signature(); } static constexpr auto className() { return Traits::className(); } static auto convertToJni(JNIEnv *, const JObject &value) { return value.object(); } static auto convertFromJni(QJniObject &&object) { return JObject(std::move(object)); } }; template<> struct Traits { static constexpr auto className() { return CTString("java/lang/Object"); } static constexpr auto signature() { return CTString("Ljava/lang/Object;"); } static auto convertToJni(JNIEnv *, const QJniObject &value) { return value.object(); } static auto convertFromJni(QJniObject &&object) { return std::move(object); } }; template<> struct Traits { static constexpr auto className() { return CTString("java/lang/String"); } static constexpr auto signature() { return CTString("Ljava/lang/String;"); } static auto convertToJni(JNIEnv *env, const QString &value) { return QtJniTypes::Detail::fromQString(value, env); } static auto convertFromJni(QJniObject &&object) { return object.toString(); } }; template struct Traits>> { static constexpr auto className() { return Traits::className(); } static constexpr auto signature() { return Traits::signature(); } }; } template template auto QtJniTypes::Detail::LocalFrame::convertFromJni(jobject object) { // If the caller wants to handle exceptions through a std::expected-like // type, then we cannot turn the jobject into a QJniObject, as a // std::expected cannot be constructed from a QJniObject. // The caller will have to take care of this themselves, by asking for a // std::expected, or (typically) using a declared JNI class // or implicitly supported Qt type (QString or array type). if constexpr (callerHandlesException && std::is_base_of_v, std::remove_pointer_t>) return static_cast(object); else return convertFromJni(object ? QJniObject::fromLocalRef(object) : QJniObject()); } QT_END_NAMESPACE #endif #endif // QJNIOBJECT_H