JNI: Implicitly convert QString to jstring in API calls
Support this both for parameters and return types, in all template functions for calling methods or accessing fields. To manage the life-time of the temporary objects, create a local stack frame in the JVM around the calling function. Popping that implicilty releases all local refernces, so that we don't have to worry about doing so explicilty. Adding a local reference to the temporary jstring objects is then enough for the object to live as long as it's needed. The LocalFrame RAII-like type needs a QJniEnvironment to push and pop the frame in the JVM, so we don't need a local QJniEnvironment anymore. Reduce code duplication a bit as a drive-by, and add test coverage. Change-Id: I801a1b006eea5af02f57d8bb7cca089508dadd1d Reviewed-by: Tinja Paavoseppä <tinja.paavoseppa@qt.io>
This commit is contained in:
parent
4702208b40
commit
f826d1615a
@ -16,6 +16,47 @@ class QJniObjectPrivate;
|
||||
|
||||
class Q_CORE_EXPORT QJniObject
|
||||
{
|
||||
template <typename ...Args>
|
||||
struct LocalFrame {
|
||||
QJniEnvironment env;
|
||||
bool hasFrame = false;
|
||||
~LocalFrame() {
|
||||
if (hasFrame)
|
||||
env->PopLocalFrame(nullptr);
|
||||
}
|
||||
template <typename T>
|
||||
auto newLocalRef(QJniObject &&object) {
|
||||
if (!hasFrame) {
|
||||
if (env->PushLocalFrame(sizeof...(Args)) < 0)
|
||||
return T{}; // JVM is out of memory, avoid making matters worse
|
||||
hasFrame = true;
|
||||
}
|
||||
return static_cast<T>(env->NewLocalRef(object.template object<T>()));
|
||||
}
|
||||
JNIEnv *jniEnv() const { return env.jniEnv(); }
|
||||
bool checkAndClearExceptions() { return env.checkAndClearExceptions(); }
|
||||
|
||||
template <typename T>
|
||||
auto convertToJni(T &&value)
|
||||
{
|
||||
using Type = q20::remove_cvref_t<T>;
|
||||
if constexpr (std::is_same_v<Type, QString>) {
|
||||
return newLocalRef<jstring>(QJniObject::fromString(value));
|
||||
} else {
|
||||
return static_cast<T &&>(value);
|
||||
}
|
||||
}
|
||||
template <typename T>
|
||||
auto convertFromJni(QJniObject &&object)
|
||||
{
|
||||
using Type = q20::remove_cvref_t<T>;
|
||||
if constexpr (std::is_same_v<Type, QString>) {
|
||||
return object.toString();
|
||||
} else {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
};
|
||||
public:
|
||||
QJniObject();
|
||||
explicit QJniObject(const char *className);
|
||||
@ -46,9 +87,10 @@ public:
|
||||
template<typename Class, typename ...Args>
|
||||
static inline QJniObject construct(Args &&...args)
|
||||
{
|
||||
LocalFrame<Args...> frame;
|
||||
return QJniObject(QtJniTypes::Traits<Class>::className().data(),
|
||||
QtJniTypes::constructorSignature<Args...>().data(),
|
||||
std::forward<Args>(args)...);
|
||||
frame.convertToJni(std::forward<Args>(args))...);
|
||||
}
|
||||
|
||||
jobject object() const;
|
||||
@ -68,19 +110,22 @@ public:
|
||||
>
|
||||
auto callMethod(const char *methodName, const char *signature, Args &&...args) const
|
||||
{
|
||||
LocalFrame<Args...> frame;
|
||||
if constexpr (QtJniTypes::isObjectType<Ret>()) {
|
||||
return callObjectMethod(methodName, signature, std::forward<Args>(args)...);
|
||||
return frame.template convertFromJni<Ret>(callObjectMethod(methodName, signature,
|
||||
frame.convertToJni(std::forward<Args>(args))...));
|
||||
} else {
|
||||
QJniEnvironment env;
|
||||
jmethodID id = getCachedMethodID(env.jniEnv(), methodName, signature);
|
||||
jmethodID id = getCachedMethodID(frame.jniEnv(), methodName, signature);
|
||||
if (id) {
|
||||
if constexpr (std::is_same_v<Ret, void>) {
|
||||
callVoidMethodV(env.jniEnv(), id, std::forward<Args>(args)...);
|
||||
env.checkAndClearExceptions();
|
||||
callVoidMethodV(frame.jniEnv(), id,
|
||||
frame.convertToJni(std::forward<Args>(args))...);
|
||||
frame.checkAndClearExceptions();
|
||||
} else {
|
||||
Ret res{};
|
||||
callMethodForType<Ret>(env.jniEnv(), res, object(), id, std::forward<Args>(args)...);
|
||||
if (env.checkAndClearExceptions())
|
||||
callMethodForType<Ret>(frame.jniEnv(), res, object(), id,
|
||||
frame.convertToJni(std::forward<Args>(args))...);
|
||||
if (frame.checkAndClearExceptions())
|
||||
res = {};
|
||||
return res;
|
||||
}
|
||||
@ -139,18 +184,21 @@ public:
|
||||
>
|
||||
static auto callStaticMethod(jclass clazz, jmethodID methodId, Args &&...args)
|
||||
{
|
||||
LocalFrame<Args...> frame;
|
||||
if constexpr (QtJniTypes::isObjectType<Ret>()) {
|
||||
return callStaticObjectMethod(clazz, methodId, std::forward<Args>(args)...);
|
||||
return frame.template convertFromJni<Ret>(callStaticObjectMethod(clazz, methodId,
|
||||
frame.convertToJni(std::forward<Args>(args))...));
|
||||
} else {
|
||||
QJniEnvironment env;
|
||||
if (clazz && methodId) {
|
||||
if constexpr (std::is_same_v<Ret, void>) {
|
||||
callStaticMethodForVoid(env.jniEnv(), clazz, methodId, std::forward<Args>(args)...);
|
||||
env.checkAndClearExceptions();
|
||||
callStaticMethodForVoid(frame.jniEnv(), clazz, methodId,
|
||||
frame.convertToJni(std::forward<Args>(args))...);
|
||||
frame.checkAndClearExceptions();
|
||||
} else {
|
||||
Ret res{};
|
||||
callStaticMethodForType<Ret>(env.jniEnv(), res, clazz, methodId, std::forward<Args>(args)...);
|
||||
if (env.checkAndClearExceptions())
|
||||
callStaticMethodForType<Ret>(frame.jniEnv(), res, clazz, methodId,
|
||||
frame.convertToJni(std::forward<Args>(args))...);
|
||||
if (frame.checkAndClearExceptions())
|
||||
res = {};
|
||||
return res;
|
||||
}
|
||||
@ -216,7 +264,9 @@ public:
|
||||
{
|
||||
QtJniTypes::assertObjectType<Ret>();
|
||||
constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>();
|
||||
return callStaticObjectMethod(className, methodName, signature.data(), std::forward<Args>(args)...);
|
||||
LocalFrame<Args...> frame;
|
||||
return frame.template convertFromJni<Ret>(callStaticObjectMethod(className, methodName, signature.data(),
|
||||
frame.convertToJni(std::forward<Args>(args))...));
|
||||
}
|
||||
|
||||
template <typename Ret, typename ...Args
|
||||
@ -228,7 +278,9 @@ public:
|
||||
{
|
||||
QtJniTypes::assertObjectType<Ret>();
|
||||
constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>();
|
||||
return callStaticObjectMethod(clazz, methodName, signature.data(), std::forward<Args>(args)...);
|
||||
LocalFrame<Args...> frame;
|
||||
return frame.template convertFromJni<Ret>(callStaticObjectMethod(clazz, methodName, signature.data(),
|
||||
frame.convertToJni(std::forward<Args>(args))...));
|
||||
}
|
||||
|
||||
template <typename T
|
||||
@ -238,16 +290,16 @@ public:
|
||||
>
|
||||
auto getField(const char *fieldName) const
|
||||
{
|
||||
LocalFrame<T> frame;
|
||||
if constexpr (QtJniTypes::isObjectType<T>()) {
|
||||
return getObjectField<T>(fieldName);
|
||||
return frame.template convertFromJni<T>(getObjectField<T>(fieldName));
|
||||
} else {
|
||||
QJniEnvironment env;
|
||||
T res{};
|
||||
constexpr auto signature = QtJniTypes::fieldSignature<T>();
|
||||
jfieldID id = getCachedFieldID(env.jniEnv(), fieldName, signature);
|
||||
jfieldID id = getCachedFieldID(frame.jniEnv(), fieldName, signature);
|
||||
if (id) {
|
||||
getFieldForType<T>(env.jniEnv(), res, object(), id);
|
||||
if (env.checkAndClearExceptions())
|
||||
getFieldForType<T>(frame.jniEnv(), res, object(), id);
|
||||
if (frame.checkAndClearExceptions())
|
||||
res = {};
|
||||
}
|
||||
return res;
|
||||
@ -261,27 +313,14 @@ public:
|
||||
>
|
||||
static auto getStaticField(const char *className, const char *fieldName)
|
||||
{
|
||||
LocalFrame<T> frame;
|
||||
if constexpr (QtJniTypes::isObjectType<T>()) {
|
||||
return getStaticObjectField<T>(className, fieldName);
|
||||
return frame.template convertFromJni<T>(getStaticObjectField<T>(className, fieldName));
|
||||
} else {
|
||||
QJniEnvironment env;
|
||||
jclass clazz = QJniObject::loadClass(className, env.jniEnv());
|
||||
T res{};
|
||||
jclass clazz = QJniObject::loadClass(className, frame.jniEnv());
|
||||
if (!clazz)
|
||||
return res;
|
||||
|
||||
constexpr auto signature = QtJniTypes::fieldSignature<T>();
|
||||
jfieldID id = getCachedFieldID(env.jniEnv(), clazz,
|
||||
QJniObject::toBinaryEncClassName(className),
|
||||
fieldName,
|
||||
signature, true);
|
||||
if (!id)
|
||||
return res;
|
||||
|
||||
getStaticFieldForType<T>(env.jniEnv(), res, clazz, id);
|
||||
if (env.checkAndClearExceptions())
|
||||
res = {};
|
||||
return res;
|
||||
return T{};
|
||||
return getStaticField<T>(clazz, fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
@ -292,16 +331,16 @@ public:
|
||||
>
|
||||
static auto getStaticField(jclass clazz, const char *fieldName)
|
||||
{
|
||||
LocalFrame<T> frame;
|
||||
if constexpr (QtJniTypes::isObjectType<T>()) {
|
||||
return getStaticObjectField<T>(clazz, fieldName);
|
||||
return frame.template convertFromJni<T>(getStaticObjectField<T>(clazz, fieldName));
|
||||
} else {
|
||||
QJniEnvironment env;
|
||||
T res{};
|
||||
constexpr auto signature = QtJniTypes::fieldSignature<T>();
|
||||
jfieldID id = getFieldID(env.jniEnv(), clazz, fieldName, signature, true);
|
||||
jfieldID id = getFieldID(frame.jniEnv(), clazz, fieldName, signature, true);
|
||||
if (id) {
|
||||
getStaticFieldForType<T>(env.jniEnv(), res, clazz, id);
|
||||
if (env.checkAndClearExceptions())
|
||||
getStaticFieldForType<T>(frame.jniEnv(), res, clazz, id);
|
||||
if (frame.checkAndClearExceptions())
|
||||
res = {};
|
||||
}
|
||||
return res;
|
||||
@ -398,19 +437,19 @@ public:
|
||||
>
|
||||
static void setStaticField(const char *className, const char *fieldName, T value)
|
||||
{
|
||||
QJniEnvironment env;
|
||||
jclass clazz = QJniObject::loadClass(className, env.jniEnv());
|
||||
LocalFrame<T> frame;
|
||||
jclass clazz = QJniObject::loadClass(className, frame.jniEnv());
|
||||
if (!clazz)
|
||||
return;
|
||||
|
||||
constexpr auto signature = QtJniTypes::fieldSignature<T>();
|
||||
jfieldID id = getCachedFieldID(env.jniEnv(), clazz, className, fieldName,
|
||||
jfieldID id = getCachedFieldID(frame.jniEnv(), clazz, className, fieldName,
|
||||
signature, true);
|
||||
if (!id)
|
||||
return;
|
||||
|
||||
setStaticFieldForType<T>(env.jniEnv(), clazz, id, value);
|
||||
env.checkAndClearExceptions();
|
||||
setStaticFieldForType<T>(frame.jniEnv(), clazz, id, value);
|
||||
frame.checkAndClearExceptions();
|
||||
}
|
||||
|
||||
template <typename T
|
||||
@ -459,13 +498,7 @@ public:
|
||||
>
|
||||
static void setStaticField(jclass clazz, const char *fieldName, T value)
|
||||
{
|
||||
QJniEnvironment env;
|
||||
constexpr auto signature = QtJniTypes::fieldSignature<T>();
|
||||
jfieldID id = getFieldID(env.jniEnv(), clazz, fieldName, signature, true);
|
||||
if (id) {
|
||||
setStaticFieldForType<T>(env.jniEnv(), clazz, id, value);
|
||||
env.checkAndClearExceptions();
|
||||
}
|
||||
setStaticField(clazz, fieldName, QtJniTypes::fieldSignature<T>(), value);
|
||||
}
|
||||
|
||||
template <typename Klass, typename T
|
||||
@ -662,6 +695,7 @@ private:
|
||||
static constexpr void setFieldForType(JNIEnv *env, jobject obj,
|
||||
jfieldID id, T value)
|
||||
{
|
||||
LocalFrame<T> frame;
|
||||
if constexpr (std::is_same_v<T, jboolean>)
|
||||
env->SetBooleanField(obj, id, value);
|
||||
else if constexpr (likeIntegerType<T, jbyte>)
|
||||
@ -678,8 +712,8 @@ private:
|
||||
env->SetFloatField(obj, id, value);
|
||||
else if constexpr (std::is_same_v<T, jdouble>)
|
||||
env->SetDoubleField(obj, id, value);
|
||||
else if constexpr (std::is_convertible_v<T, jobject>)
|
||||
env->SetObjectField(obj, id, value);
|
||||
else if constexpr (QtJniTypes::isObjectType<T>())
|
||||
env->SetObjectField(obj, id, static_cast<jobject>(frame.convertToJni(value)));
|
||||
else
|
||||
QtJniTypes::staticAssertTypeMismatch();
|
||||
}
|
||||
@ -688,6 +722,7 @@ private:
|
||||
static constexpr void setStaticFieldForType(JNIEnv *env, jclass clazz,
|
||||
jfieldID id, T value)
|
||||
{
|
||||
LocalFrame<T> frame;
|
||||
if constexpr (std::is_same_v<T, jboolean>)
|
||||
env->SetStaticBooleanField(clazz, id, value);
|
||||
else if constexpr (likeIntegerType<T, jbyte>)
|
||||
@ -704,8 +739,8 @@ private:
|
||||
env->SetStaticFloatField(clazz, id, value);
|
||||
else if constexpr (std::is_same_v<T, jdouble>)
|
||||
env->SetStaticDoubleField(clazz, id, value);
|
||||
else if constexpr (std::is_convertible_v<T, jobject>)
|
||||
env->SetStaticObjectField(clazz, id, value);
|
||||
else if constexpr (QtJniTypes::isObjectType<T>())
|
||||
env->SetStaticObjectField(clazz, id, static_cast<jobject>(frame.convertToJni(value)));
|
||||
else
|
||||
QtJniTypes::staticAssertTypeMismatch();
|
||||
}
|
||||
|
@ -235,6 +235,8 @@ struct Traits {
|
||||
return CTString("V");
|
||||
} else if constexpr (std::is_enum_v<T>) {
|
||||
return Traits<std::underlying_type_t<T>>::signature();
|
||||
} else if constexpr (std::is_same_v<T, QString>) {
|
||||
return CTString("Ljava/lang/String;");
|
||||
}
|
||||
// else: return void -> not implemented
|
||||
}
|
||||
|
@ -187,8 +187,10 @@ void tst_QJniObject::ctor()
|
||||
void tst_QJniObject::callMethodTest()
|
||||
{
|
||||
{
|
||||
QJniObject jString1 = QJniObject::fromString(QLatin1String("Hello, Java"));
|
||||
QJniObject jString2 = QJniObject::fromString(QLatin1String("hELLO, jAVA"));
|
||||
const QString qString1 = u"Hello, Java"_s;
|
||||
const QString qString2 = u"hELLO, jAVA"_s;
|
||||
QJniObject jString1 = QJniObject::fromString(qString1);
|
||||
QJniObject jString2 = QJniObject::fromString(qString2);
|
||||
QVERIFY(jString1 != jString2);
|
||||
|
||||
const jboolean isEmpty = jString1.callMethod<jboolean>("isEmpty");
|
||||
@ -201,6 +203,10 @@ void tst_QJniObject::callMethodTest()
|
||||
|
||||
ret = jString1.callMethod<jint>("compareToIgnoreCase", jString2.object<jstring>());
|
||||
QVERIFY(0 == ret);
|
||||
|
||||
// as of Qt 6.7, we can pass QString directly
|
||||
ret = jString1.callMethod<jint>("compareToIgnoreCase", qString2);
|
||||
QVERIFY(0 == ret);
|
||||
}
|
||||
|
||||
{
|
||||
@ -219,6 +225,13 @@ void tst_QJniObject::callMethodTest()
|
||||
|
||||
QJniObject subString = jString.callMethod<jstring>("substring", 0, 4);
|
||||
QCOMPARE(subString.toString(), qString.mid(0, 4));
|
||||
|
||||
// and as of Qt 6.7, we can return and take QString directly
|
||||
QCOMPARE(jString.callMethod<QString>("substring", 0, 4), qString.mid(0, 4));
|
||||
|
||||
QCOMPARE(jString.callMethod<jstring>("substring", 0, 7)
|
||||
.callMethod<jstring>("toUpperCase")
|
||||
.callMethod<QString>("concat", u"C++"_s), u"HELLO, C++"_s);
|
||||
}
|
||||
}
|
||||
|
||||
@ -346,6 +359,11 @@ void tst_QJniObject::callStaticObjectMethod()
|
||||
QVERIFY(returnValue.isValid());
|
||||
QCOMPARE(returnValue.toString(), string);
|
||||
|
||||
// from 6.7 we can pass QString directly, both as parameters and return type
|
||||
QString result = QJniObject::callStaticMethod<jstring, QString>("format",
|
||||
string,
|
||||
jobjectArray(0));
|
||||
QCOMPARE(result, string);
|
||||
}
|
||||
|
||||
void tst_QJniObject::callStaticObjectMethodById()
|
||||
@ -1053,11 +1071,16 @@ void tst_QJniObject::setObjectField()
|
||||
QJniObject obj(testClassName);
|
||||
QVERIFY(obj.isValid());
|
||||
|
||||
QJniObject testValue = QJniObject::fromString(QStringLiteral("Hello"));
|
||||
const QString qString = u"Hello"_s;
|
||||
QJniObject testValue = QJniObject::fromString(qString);
|
||||
obj.setField("STRING_OBJECT_VAR", testValue.object<jstring>());
|
||||
|
||||
QJniObject res = obj.getObjectField<jstring>("STRING_OBJECT_VAR");
|
||||
QCOMPARE(res.toString(), testValue.toString());
|
||||
|
||||
// as of Qt 6.7, we can set and get strings directly
|
||||
obj.setField("STRING_OBJECT_VAR", qString);
|
||||
QCOMPARE(obj.getField<QString>("STRING_OBJECT_VAR"), qString);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@ -1127,11 +1150,17 @@ void tst_QJniObject::setStaticBooleanField()
|
||||
|
||||
void tst_QJniObject::setStaticObjectField()
|
||||
{
|
||||
QJniObject testValue = QJniObject::fromString(QStringLiteral("Hello"));
|
||||
const QString qString = u"Hello"_s;
|
||||
QJniObject testValue = QJniObject::fromString(qString);
|
||||
QJniObject::setStaticField(testClassName, "S_STRING_OBJECT_VAR", testValue.object<jstring>());
|
||||
|
||||
QJniObject res = QJniObject::getStaticObjectField<jstring>(testClassName, "S_STRING_OBJECT_VAR");
|
||||
QCOMPARE(res.toString(), testValue.toString());
|
||||
|
||||
// as of Qt 6.7, we can set and get strings directly
|
||||
using namespace QtJniTypes;
|
||||
QJniObject::setStaticField<QtJniObjectTestClass>("S_STRING_OBJECT_VAR", qString);
|
||||
QCOMPARE((QJniObject::getStaticField<QtJniObjectTestClass, QString>("S_STRING_OBJECT_VAR")), qString);
|
||||
}
|
||||
|
||||
void tst_QJniObject::templateApiCheck()
|
||||
|
Loading…
x
Reference in New Issue
Block a user