From 31f98957cf5c061f868588ef81059a907c9d30ad Mon Sep 17 00:00:00 2001 From: Volker Hilsheimer Date: Tue, 24 May 2022 17:20:15 +0200 Subject: [PATCH] Add compile-time generation of JNI class names As with method signatures, register class names using template function specialization in the QtJniTypes namespace, and then declare C++ types as JNI classes with a class name string. Such classes implicitly get registered as JNI types as well. Add a QJniObject construct method (since C++ constructors that are templates cannot be explicitly instantiated with a type), and a QJniEnvironment::findClass overload. Add test coverage, also for the recently added macros for native methods. As a drive-by, change the name of the Q_JNI_DECLARE_NATIVE_METHOD macro to Q_DECLARE_JNI_NATIVE_METHOD for consistency. Change-Id: Ic19562d78da726f202b3bdf4e9354e8ad24d8bd9 Reviewed-by: Ivan Solovev Reviewed-by: Assam Boudjelthia --- src/corelib/kernel/qjnienvironment.h | 2 + src/corelib/kernel/qjniobject.h | 8 ++++ src/corelib/kernel/qjnitypes.h | 39 ++++++++++++++++++- .../qjnienvironment/tst_qjnienvironment.cpp | 7 ++-- .../kernel/qjniobject/tst_qjniobject.cpp | 7 +++- .../kernel/qjnitypes/tst_qjnitypes.cpp | 22 +++++++++++ 6 files changed, 79 insertions(+), 6 deletions(-) diff --git a/src/corelib/kernel/qjnienvironment.h b/src/corelib/kernel/qjnienvironment.h index 6cc8693227..f7ffa836c2 100644 --- a/src/corelib/kernel/qjnienvironment.h +++ b/src/corelib/kernel/qjnienvironment.h @@ -24,6 +24,8 @@ public: JNIEnv &operator*() const; JNIEnv *jniEnv() const; jclass findClass(const char *className); + template + jclass findClass() { return findClass(QtJniTypes::className().data()); } jmethodID findMethod(jclass clazz, const char *methodName, const char *signature); template jmethodID findMethod(jclass clazz, const char *methodName) { diff --git a/src/corelib/kernel/qjniobject.h b/src/corelib/kernel/qjniobject.h index bbaa6ee70c..3c7ca13ff2 100644 --- a/src/corelib/kernel/qjniobject.h +++ b/src/corelib/kernel/qjniobject.h @@ -45,6 +45,14 @@ public: inline QJniObject(QtJniTypes::Object wrapper) noexcept : QJniObject(jobject(wrapper)) {} ~QJniObject(); + template + static inline QJniObject construct(Args &&...args) + { + return QJniObject(QtJniTypes::className().data(), + QtJniTypes::constructorSignature().data(), + std::forward(args)...); + } + jobject object() const; template T object() const { diff --git a/src/corelib/kernel/qjnitypes.h b/src/corelib/kernel/qjnitypes.h index 0ebc1a6bc9..ecb8ae02f9 100644 --- a/src/corelib/kernel/qjnitypes.h +++ b/src/corelib/kernel/qjnitypes.h @@ -214,6 +214,21 @@ constexpr auto typeSignature() staticAssertTypeMismatch(); } +template +static void staticAssertClassNotRegistered() +{ + static_assert(flag, "Class not registered, use Q_DECLARE_JNI_CLASS"); +} + +template +constexpr auto className() +{ + if constexpr(std::is_same::value) + return String("java/lang/String"); + else + staticAssertClassNotRegistered(); +} + template static constexpr bool isPrimitiveType() { @@ -298,13 +313,17 @@ struct Object } // namespace QtJniTypes -#define Q_DECLARE_JNI_TYPE(Type, Signature) \ +#define Q_DECLARE_JNI_TYPE_HELPER(Type) \ namespace QtJniTypes { \ struct Type : Object \ { \ constexpr Type(jobject o) noexcept : Object{o} {} \ }; \ } \ + + +#define Q_DECLARE_JNI_TYPE(Type, Signature) \ +Q_DECLARE_JNI_TYPE_HELPER(Type) \ template<> \ constexpr auto QtJniTypes::typeSignature() \ { \ @@ -315,7 +334,23 @@ constexpr auto QtJniTypes::typeSignature() \ return QtJniTypes::String(Signature); \ } \ -#define Q_JNI_DECLARE_NATIVE_METHOD(Method) \ +#define Q_DECLARE_JNI_CLASS(Type, Signature) \ +Q_DECLARE_JNI_TYPE_HELPER(Type) \ +template<> \ +constexpr auto QtJniTypes::className() \ +{ \ + return QtJniTypes::String(Signature); \ +} \ +template<> \ +constexpr auto QtJniTypes::typeSignature() \ +{ \ + return QtJniTypes::String("L") \ + + QtJniTypes::String(Signature) \ + + QtJniTypes::String(";"); \ +} \ + + +#define Q_DECLARE_JNI_NATIVE_METHOD(Method) \ namespace QtJniMethods { \ static constexpr auto Method##_signature = \ QtJniTypes::nativeMethodSignature(Method); \ diff --git a/tests/auto/corelib/kernel/qjnienvironment/tst_qjnienvironment.cpp b/tests/auto/corelib/kernel/qjnienvironment/tst_qjnienvironment.cpp index f699d6816a..76cad4ddc0 100644 --- a/tests/auto/corelib/kernel/qjnienvironment/tst_qjnienvironment.cpp +++ b/tests/auto/corelib/kernel/qjnienvironment/tst_qjnienvironment.cpp @@ -65,6 +65,7 @@ void tst_QJniEnvironment::jniEnv() // try to find an existing class with QJniEnvironment QJniEnvironment env; QVERIFY(env.findClass("java/lang/Object")); + QVERIFY(env.findClass()); // try to find a nonexistent class QVERIFY(!env.findClass("this/doesnt/Exist")); @@ -98,14 +99,14 @@ static void callbackFromJava(JNIEnv *env, jobject /*thiz*/, jstring value) Q_UNUSED(env) registerNativesString = QJniObject(value).toString(); } -Q_JNI_DECLARE_NATIVE_METHOD(callbackFromJava); +Q_DECLARE_JNI_NATIVE_METHOD(callbackFromJava); static void callbackFromJavaNoCtor(JNIEnv *env, jobject /*thiz*/, jstring value) { Q_UNUSED(env) registerNativesString = QJniObject(value).toString(); } -Q_JNI_DECLARE_NATIVE_METHOD(callbackFromJavaNoCtor); +Q_DECLARE_JNI_NATIVE_METHOD(callbackFromJavaNoCtor); void tst_QJniEnvironment::registerNativeMethods() { @@ -145,7 +146,7 @@ static void intCallbackFromJava(JNIEnv *env, jobject /*thiz*/, jint value) Q_UNUSED(env) registerNativeInteger = static_cast(value); } -Q_JNI_DECLARE_NATIVE_METHOD(intCallbackFromJava); +Q_DECLARE_JNI_NATIVE_METHOD(intCallbackFromJava); void tst_QJniEnvironment::registerNativeMethodsByJclass() { diff --git a/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp b/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp index 8faa9b6fbb..e57785c5c5 100644 --- a/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp +++ b/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp @@ -134,6 +134,11 @@ void tst_QJniObject::ctor() QVERIFY(object.isValid()); } + { + QJniObject object = QJniObject::construct(); + QVERIFY(object.isValid()); + } + { QJniObject string = QJniObject::fromString(QLatin1String("Hello, Java")); QJniObject object("java/lang/String", "(Ljava/lang/String;)V", string.object()); @@ -143,7 +148,7 @@ void tst_QJniObject::ctor() { QJniObject string = QJniObject::fromString(QLatin1String("Hello, Java")); - QJniObject object("java/lang/String", string.object()); + QJniObject object = QJniObject::construct(string.object()); QVERIFY(object.isValid()); QCOMPARE(string.toString(), object.toString()); } diff --git a/tests/auto/corelib/kernel/qjnitypes/tst_qjnitypes.cpp b/tests/auto/corelib/kernel/qjnitypes/tst_qjnitypes.cpp index c4abfe399a..395028b8cb 100644 --- a/tests/auto/corelib/kernel/qjnitypes/tst_qjnitypes.cpp +++ b/tests/auto/corelib/kernel/qjnitypes/tst_qjnitypes.cpp @@ -14,6 +14,7 @@ public: private slots: void initTestCase(); + void nativeMethod(); }; struct QtJavaWrapper {}; @@ -45,6 +46,11 @@ static_assert(QtJniTypes::typeSignature() == "Lorg/qtproje Q_DECLARE_JNI_TYPE(ArrayType, "[Lorg/qtproject/qt/ArrayType;") static_assert(QtJniTypes::typeSignature() == "[Lorg/qtproject/qt/ArrayType;"); +static_assert(QtJniTypes::className() == "java/lang/String"); + +Q_DECLARE_JNI_CLASS(QtTextToSpeech, "org/qtproject/qt/android/speech/QtTextToSpeech") +static_assert(QtJniTypes::className() == "org/qtproject/qt/android/speech/QtTextToSpeech"); + static_assert(QtJniTypes::fieldSignature() == "I"); static_assert(QtJniTypes::fieldSignature() != "X"); static_assert(QtJniTypes::fieldSignature() != "Ljava/lang/Object;"); @@ -97,6 +103,22 @@ void tst_QJniTypes::initTestCase() } +static bool nativeFunction(JNIEnv *, jclass, int, jstring, long) +{ + return true; +} +Q_DECLARE_JNI_NATIVE_METHOD(nativeFunction) + +static_assert(QtJniTypes::nativeMethodSignature(nativeFunction) == "(ILjava/lang/String;J)Z"); + +void tst_QJniTypes::nativeMethod() +{ + const auto method = Q_JNI_NATIVE_METHOD(nativeFunction); + QVERIFY(method.fnPtr == nativeFunction); + QCOMPARE(method.name, "nativeFunction"); + QCOMPARE(method.signature, "(ILjava/lang/String;J)Z"); +} + QTEST_MAIN(tst_QJniTypes) #include "tst_qjnitypes.moc"