JNI: Optimize string handling

A lot of code passes strings through JNI calls, and we always ended up
constructing a temporary QJniObject just to convert it to a QString, or
to get the jstring out of it. This is expensive as QJniObject allocates
a private object and maintains a reference count.

For code paths that have a jstring, or only want a jstring, and already
have a JNIEnv pointer, we can shortcut this and use JNI APIs directly
rather than going through the QJniObject detour. This was already done
when accessing elements of a QJniArray<QString>.

Add internal helper methods to the QtJniTypes::Details namespace that
convert between jstring and QString directly. Use that where we can,
including the QJniObject::to/fromString implementations.

Since the new helper for converting from a QString already returns a
local reference, split the LocalFrame::newLocalRef into a helper that
pushes the local frame, and use that to explicitly do so in the case
of QString conversion. This also removes the last use of the
QJniObject overload of LocalFrame::newLocalRef, so remove that.

Change-Id: Ib3a261efd0dcca86a1a99c2a054d50e932c3fe71
Reviewed-by: Soheil Armin <soheil.armin@qt.io>
Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
This commit is contained in:
Volker Hilsheimer 2024-11-12 13:10:42 +01:00
parent 37638c84ef
commit fedf761000
5 changed files with 35 additions and 21 deletions

View File

@ -735,9 +735,7 @@ public:
} else if constexpr (std::is_same_v<QString, T>) { } else if constexpr (std::is_same_v<QString, T>) {
jstring string = static_cast<jstring>(env->GetObjectArrayElement(arrayObject(), i)); jstring string = static_cast<jstring>(env->GetObjectArrayElement(arrayObject(), i));
if (string) { if (string) {
const auto length = env->GetStringLength(string); QString res = QtJniTypes::Detail::toQString(string, env);
QString res(length, Qt::Uninitialized);
env->GetStringRegion(string, 0, length, reinterpret_cast<jchar *>(res.data()));
env->DeleteLocalRef(string); env->DeleteLocalRef(string);
return res; return res;
} else { } else {
@ -817,6 +815,8 @@ public:
for (auto element : *this) { for (auto element : *this) {
if constexpr (std::is_same_v<decltype(element), QString>) if constexpr (std::is_same_v<decltype(element), QString>)
container.emplace_back(element); container.emplace_back(element);
else if constexpr (std::is_same_v<decltype(element), jstring>)
container.emplace_back(QtJniTypes::Detail::toQString(element, env));
else else
container.emplace_back(QJniObject(element).toString()); container.emplace_back(QJniObject(element).toString());
} }

View File

@ -1325,8 +1325,7 @@ QJniObject QJniObject::getObjectField(const char *fieldName, const char *signatu
QJniObject QJniObject::fromString(const QString &string) QJniObject QJniObject::fromString(const QString &string)
{ {
QJniEnvironment env; QJniEnvironment env;
jstring stringRef = env->NewString(reinterpret_cast<const jchar*>(string.constData()), jstring stringRef = QtJniTypes::Detail::fromQString(string, env.jniEnv());
string.length());
QJniObject stringObject = getCleanJniObject(stringRef, env.jniEnv()); QJniObject stringObject = getCleanJniObject(stringRef, env.jniEnv());
stringObject.d->m_className = "java/lang/String"; stringObject.d->m_className = "java/lang/String";
return stringObject; return stringObject;
@ -1352,10 +1351,7 @@ QString QJniObject::toString() const
return QString(); return QString();
QJniObject string = callObjectMethod<jstring>("toString"); QJniObject string = callObjectMethod<jstring>("toString");
const int strLength = string.jniEnv()->GetStringLength(string.object<jstring>()); return QtJniTypes::Detail::toQString(string.object<jstring>(), string.jniEnv());
QString res(strLength, Qt::Uninitialized);
string.jniEnv()->GetStringRegion(string.object<jstring>(), 0, strLength, reinterpret_cast<jchar *>(res.data()));
return res;
} }
/*! /*!

View File

@ -31,21 +31,21 @@ class Q_CORE_EXPORT QJniObject
if (hasFrame) if (hasFrame)
env->PopLocalFrame(nullptr); env->PopLocalFrame(nullptr);
} }
bool ensureFrame()
{
if (!hasFrame)
hasFrame = jniEnv()->PushLocalFrame(sizeof...(Args)) == 0;
return hasFrame;
}
template <typename T> template <typename T>
auto newLocalRef(jobject object) auto newLocalRef(jobject object)
{ {
if (!hasFrame) { if (!ensureFrame()) {
if (jniEnv()->PushLocalFrame(sizeof...(Args)) < 0) // if the JVM is out of memory, avoid making matters worse
return T{}; // JVM is out of memory, avoid making matters worse return T{};
hasFrame = true;
} }
return static_cast<T>(jniEnv()->NewLocalRef(object)); return static_cast<T>(jniEnv()->NewLocalRef(object));
} }
template <typename T>
auto newLocalRef(const QJniObject &object)
{
return newLocalRef<T>(object.template object<T>());
}
JNIEnv *jniEnv() const JNIEnv *jniEnv() const
{ {
if (!env) if (!env)
@ -870,7 +870,9 @@ auto QJniObject::LocalFrame<Args...>::convertToJni(T &&value)
{ {
using Type = q20::remove_cvref_t<T>; using Type = q20::remove_cvref_t<T>;
if constexpr (std::is_same_v<Type, QString>) { if constexpr (std::is_same_v<Type, QString>) {
return newLocalRef<jstring>(QJniObject::fromString(value)); if (ensureFrame()) // fromQString already returns a local reference
return QtJniTypes::Detail::fromQString(value, jniEnv());
return jstring{};
} else if constexpr (QtJniTypes::IsJniArray<Type>::value) { } else if constexpr (QtJniTypes::IsJniArray<Type>::value) {
return value.arrayObject(); return value.arrayObject();
} else if constexpr (QtJniTypes::detail::isCompatibleSourceContainer<T>) { } else if constexpr (QtJniTypes::detail::isCompatibleSourceContainer<T>) {

View File

@ -163,7 +163,7 @@ struct JNITypeForArgImpl<QString>
static QString fromVarArg(Type t) static QString fromVarArg(Type t)
{ {
return QJniObject(t).toString(); return QtJniTypes::Detail::toQString(t, QJniEnvironment::getJniEnv());
} }
}; };

View File

@ -4,7 +4,7 @@
#ifndef QJNITYPES_IMPL_H #ifndef QJNITYPES_IMPL_H
#define QJNITYPES_IMPL_H #define QJNITYPES_IMPL_H
#include <QtCore/qglobal.h> #include <QtCore/qstring.h>
#include <QtCore/q20type_traits.h> #include <QtCore/q20type_traits.h>
#if defined(Q_QDOC) || defined(Q_OS_ANDROID) #if defined(Q_QDOC) || defined(Q_OS_ANDROID)
@ -15,6 +15,22 @@ QT_BEGIN_NAMESPACE
namespace QtJniTypes namespace QtJniTypes
{ {
namespace Detail
{
static inline jstring fromQString(const QString &string, JNIEnv *env)
{
return env->NewString(reinterpret_cast<const jchar*>(string.constData()), string.length());
}
static inline QString toQString(jstring string, JNIEnv *env)
{
const jsize length = env->GetStringLength(string);
QString res(length, Qt::Uninitialized);
env->GetStringRegion(string, 0, length, reinterpret_cast<jchar *>(res.data()));
return res;
}
} // namespace Detail
// a constexpr type for string literals of any character width, aware of the length // a constexpr type for string literals of any character width, aware of the length
// of the string. // of the string.
template<size_t N_WITH_NULL, typename BaseType = char> template<size_t N_WITH_NULL, typename BaseType = char>