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 <ivan.solovev@qt.io>
Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
This commit is contained in:
Volker Hilsheimer 2022-05-24 17:20:15 +02:00
parent 9614f4d434
commit 31f98957cf
6 changed files with 79 additions and 6 deletions

View File

@ -24,6 +24,8 @@ public:
JNIEnv &operator*() const;
JNIEnv *jniEnv() const;
jclass findClass(const char *className);
template<typename Class>
jclass findClass() { return findClass(QtJniTypes::className<Class>().data()); }
jmethodID findMethod(jclass clazz, const char *methodName, const char *signature);
template<typename ...Args>
jmethodID findMethod(jclass clazz, const char *methodName) {

View File

@ -45,6 +45,14 @@ public:
inline QJniObject(QtJniTypes::Object wrapper) noexcept : QJniObject(jobject(wrapper)) {}
~QJniObject();
template<typename Class, typename ...Args>
static inline QJniObject construct(Args &&...args)
{
return QJniObject(QtJniTypes::className<Class>().data(),
QtJniTypes::constructorSignature<Args...>().data(),
std::forward<Args>(args)...);
}
jobject object() const;
template <typename T> T object() const
{

View File

@ -214,6 +214,21 @@ constexpr auto typeSignature()
staticAssertTypeMismatch();
}
template<bool flag = false>
static void staticAssertClassNotRegistered()
{
static_assert(flag, "Class not registered, use Q_DECLARE_JNI_CLASS");
}
template<typename T>
constexpr auto className()
{
if constexpr(std::is_same<T, jstring>::value)
return String("java/lang/String");
else
staticAssertClassNotRegistered();
}
template<typename T>
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<QtJniTypes::Type>() \
{ \
@ -315,7 +334,23 @@ constexpr auto QtJniTypes::typeSignature<QtJniTypes::Type>() \
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<QtJniTypes::Type>() \
{ \
return QtJniTypes::String(Signature); \
} \
template<> \
constexpr auto QtJniTypes::typeSignature<QtJniTypes::Type>() \
{ \
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); \

View File

@ -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<jstring>());
// 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<int>(value);
}
Q_JNI_DECLARE_NATIVE_METHOD(intCallbackFromJava);
Q_DECLARE_JNI_NATIVE_METHOD(intCallbackFromJava);
void tst_QJniEnvironment::registerNativeMethodsByJclass()
{

View File

@ -134,6 +134,11 @@ void tst_QJniObject::ctor()
QVERIFY(object.isValid());
}
{
QJniObject object = QJniObject::construct<jstring>();
QVERIFY(object.isValid());
}
{
QJniObject string = QJniObject::fromString(QLatin1String("Hello, Java"));
QJniObject object("java/lang/String", "(Ljava/lang/String;)V", string.object<jstring>());
@ -143,7 +148,7 @@ void tst_QJniObject::ctor()
{
QJniObject string = QJniObject::fromString(QLatin1String("Hello, Java"));
QJniObject object("java/lang/String", string.object<jstring>());
QJniObject object = QJniObject::construct<jstring>(string.object<jstring>());
QVERIFY(object.isValid());
QCOMPARE(string.toString(), object.toString());
}

View File

@ -14,6 +14,7 @@ public:
private slots:
void initTestCase();
void nativeMethod();
};
struct QtJavaWrapper {};
@ -45,6 +46,11 @@ static_assert(QtJniTypes::typeSignature<QtJniTypes::JavaType>() == "Lorg/qtproje
Q_DECLARE_JNI_TYPE(ArrayType, "[Lorg/qtproject/qt/ArrayType;")
static_assert(QtJniTypes::typeSignature<QtJniTypes::ArrayType>() == "[Lorg/qtproject/qt/ArrayType;");
static_assert(QtJniTypes::className<jstring>() == "java/lang/String");
Q_DECLARE_JNI_CLASS(QtTextToSpeech, "org/qtproject/qt/android/speech/QtTextToSpeech")
static_assert(QtJniTypes::className<QtJniTypes::QtTextToSpeech>() == "org/qtproject/qt/android/speech/QtTextToSpeech");
static_assert(QtJniTypes::fieldSignature<jint>() == "I");
static_assert(QtJniTypes::fieldSignature<jint>() != "X");
static_assert(QtJniTypes::fieldSignature<jint>() != "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"