Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qjniobject.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qjniobject.h"
5
6#include "qjnihelpers_p.h"
7
8#include <QtCore/qbytearray.h>
9#include <QtCore/qhash.h>
10#include <QtCore/qreadwritelock.h>
11#include <QtCore/qloggingcategory.h>
12
13
15
16Q_LOGGING_CATEGORY(lcJniThreadCheck, "qt.core.jni.threadcheck")
17
18using namespace Qt::StringLiterals;
19
282{
283public:
285 {
286 }
288 JNIEnv *env = QJniEnvironment::getJniEnv();
289 if (m_jobject)
290 env->DeleteGlobalRef(m_jobject);
291 if (m_jclass && m_own_jclass)
292 env->DeleteGlobalRef(m_jclass);
293 }
294
295 template <typename ...Args>
296 void construct(const char *signature = nullptr, Args &&...args)
297 {
298 if (m_jclass) {
299 JNIEnv *env = QJniEnvironment::getJniEnv();
300 // get default constructor
301 jmethodID constructorId = QJniObject::getCachedMethodID(env, m_jclass, m_className, "<init>",
302 signature ? signature : "()V");
303 if (constructorId) {
304 jobject obj = nullptr;
305 if constexpr (sizeof...(Args) == 0)
306 obj = env->NewObject(m_jclass, constructorId);
307 else
308 obj = env->NewObjectV(m_jclass, constructorId, std::forward<Args>(args)...);
309 if (obj) {
310 m_jobject = env->NewGlobalRef(obj);
311 env->DeleteLocalRef(obj);
312 }
313 }
314 }
315 }
316
318 jobject m_jobject = nullptr;
319 jclass m_jclass = nullptr;
320 bool m_own_jclass = true;
321};
322
323template <typename ...Args>
324static inline QByteArray cacheKey(Args &&...args)
325{
326 return (QByteArrayView(":") + ... + QByteArrayView(args));
327}
328
329typedef QHash<QByteArray, jclass> JClassHash;
330Q_GLOBAL_STATIC(JClassHash, cachedClasses)
331Q_GLOBAL_STATIC(QReadWriteLock, cachedClassesLock)
332
334{
335 QReadLocker locker(cachedClassesLock);
336 const auto &it = cachedClasses->constFind(className);
337 return it != cachedClasses->constEnd() ? it.value() : nullptr;
338}
339
347static QJniObject getCleanJniObject(jobject object, JNIEnv *env)
348{
349 if (QJniEnvironment::checkAndClearExceptions(env) || !object) {
350 if (object)
351 env->DeleteLocalRef(object);
352 return QJniObject();
353 }
354
355 QJniObject res(object);
356 env->DeleteLocalRef(object);
357 return res;
358}
359
364jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env)
365{
366 Q_ASSERT(env);
367 QByteArray classNameArray(className);
368#ifdef QT_DEBUG
369 if (classNameArray.contains('.')) {
370 qWarning("QtAndroidPrivate::findClass: className '%s' should use slash separators!",
371 className);
372 }
373#endif
374 classNameArray.replace('.', '/');
375 jclass clazz = getCachedClass(classNameArray);
376 if (clazz)
377 return clazz;
378
379 QWriteLocker locker(cachedClassesLock);
380 // Check again; another thread might have added the class to the cache after
381 // our call to getCachedClass and before we acquired the lock.
382 const auto &it = cachedClasses->constFind(classNameArray);
383 if (it != cachedClasses->constEnd())
384 return it.value();
385
386 // JNIEnv::FindClass wants "a fully-qualified class name or an array type signature"
387 // which is a slash-separated class name.
388 jclass localClazz = env->FindClass(classNameArray.constData());
389 if (localClazz) {
390 clazz = static_cast<jclass>(env->NewGlobalRef(localClazz));
391 env->DeleteLocalRef(localClazz);
392 } else {
393 // Clear the exception silently; we are going to try the ClassLoader next,
394 // so no need for warning unless that fails as well.
395 env->ExceptionClear();
396 }
397
398 if (!clazz) {
399 // Wrong class loader, try our own
401 if (!classLoader.isValid())
402 return nullptr;
403
404 // ClassLoader::loadClass on the other hand wants the binary name of the class,
405 // e.g. dot-separated. In testing it works also with /, but better to stick to
406 // the specification.
407 const QString binaryClassName = QString::fromLatin1(className).replace(u'/', u'.');
408 jstring classNameObject = env->NewString(reinterpret_cast<const jchar*>(binaryClassName.constData()),
409 binaryClassName.length());
410 QJniObject classObject = classLoader.callMethod<jclass>("loadClass", classNameObject);
411 env->DeleteLocalRef(classNameObject);
412
413 if (!QJniEnvironment::checkAndClearExceptions(env) && classObject.isValid())
414 clazz = static_cast<jclass>(env->NewGlobalRef(classObject.object()));
415 }
416
417 if (clazz)
418 cachedClasses->insert(classNameArray, clazz);
419
420 return clazz;
421}
422
423jclass QJniObject::loadClass(const QByteArray &className, JNIEnv *env)
424{
426}
427
428typedef QHash<QByteArray, jmethodID> JMethodIDHash;
429Q_GLOBAL_STATIC(JMethodIDHash, cachedMethodID)
430Q_GLOBAL_STATIC(QReadWriteLock, cachedMethodIDLock)
431
432jmethodID QJniObject::getMethodID(JNIEnv *env,
433 jclass clazz,
434 const char *name,
435 const char *signature,
436 bool isStatic)
437{
438 jmethodID id = isStatic ? env->GetStaticMethodID(clazz, name, signature)
439 : env->GetMethodID(clazz, name, signature);
440
441 if (QJniEnvironment::checkAndClearExceptions(env))
442 return nullptr;
443
444 return id;
445}
446
447void QJniObject::callVoidMethodV(JNIEnv *env, jmethodID id, ...) const
448{
449 va_list args;
450 va_start(args, id);
451 env->CallVoidMethodV(d->m_jobject, id, args);
452 va_end(args);
453}
454
455jmethodID QJniObject::getCachedMethodID(JNIEnv *env,
456 jclass clazz,
457 const QByteArray &className,
458 const char *name,
459 const char *signature,
460 bool isStatic)
461{
462 if (className.isEmpty())
463 return getMethodID(env, clazz, name, signature, isStatic);
464
465 const QByteArray key = cacheKey(className, name, signature);
467
468 {
469 QReadLocker locker(cachedMethodIDLock);
470 it = cachedMethodID->constFind(key);
471 if (it != cachedMethodID->constEnd())
472 return it.value();
473 }
474
475 {
476 QWriteLocker locker(cachedMethodIDLock);
477 it = cachedMethodID->constFind(key);
478 if (it != cachedMethodID->constEnd())
479 return it.value();
480
481 jmethodID id = getMethodID(env, clazz, name, signature, isStatic);
482
483 cachedMethodID->insert(key, id);
484 return id;
485 }
486}
487
488jmethodID QJniObject::getCachedMethodID(JNIEnv *env, const char *name,
489 const char *signature, bool isStatic) const
490{
491 return QJniObject::getCachedMethodID(env, d->m_jclass, d->m_className, name, signature, isStatic);
492}
493
494typedef QHash<QByteArray, jfieldID> JFieldIDHash;
495Q_GLOBAL_STATIC(JFieldIDHash, cachedFieldID)
496Q_GLOBAL_STATIC(QReadWriteLock, cachedFieldIDLock)
497
498jfieldID QJniObject::getFieldID(JNIEnv *env,
499 jclass clazz,
500 const char *name,
501 const char *signature,
502 bool isStatic)
503{
504 jfieldID id = isStatic ? env->GetStaticFieldID(clazz, name, signature)
505 : env->GetFieldID(clazz, name, signature);
506
507 if (QJniEnvironment::checkAndClearExceptions(env))
508 return nullptr;
509
510 return id;
511}
512
513jfieldID QJniObject::getCachedFieldID(JNIEnv *env,
514 jclass clazz,
515 const QByteArray &className,
516 const char *name,
517 const char *signature,
518 bool isStatic)
519{
520 if (className.isNull())
521 return getFieldID(env, clazz, name, signature, isStatic);
522
523 const QByteArray key = cacheKey(className, name, signature);
525
526 {
527 QReadLocker locker(cachedFieldIDLock);
528 it = cachedFieldID->constFind(key);
529 if (it != cachedFieldID->constEnd())
530 return it.value();
531 }
532
533 {
534 QWriteLocker locker(cachedFieldIDLock);
535 it = cachedFieldID->constFind(key);
536 if (it != cachedFieldID->constEnd())
537 return it.value();
538
539 jfieldID id = getFieldID(env, clazz, name, signature, isStatic);
540
541 cachedFieldID->insert(key, id);
542 return id;
543 }
544}
545
546jfieldID QJniObject::getCachedFieldID(JNIEnv *env,
547 const char *name,
548 const char *signature,
549 bool isStatic) const
550{
551 return QJniObject::getCachedFieldID(env, d->m_jclass, d->m_className, name, signature, isStatic);
552}
553
561QJniObject::QJniObject()
562 : d(new QJniObjectPrivate())
563{
564}
565
575QJniObject::QJniObject(const char *className)
576 : d(new QJniObjectPrivate())
577{
578 d->m_className = className;
579 d->m_jclass = loadClass(d->m_className, jniEnv());
580 d->m_own_jclass = false;
581
582 d->construct();
583}
584
598QJniObject::QJniObject(const char *className, const char *signature, ...)
599 : d(new QJniObjectPrivate())
600{
601 d->m_className = className;
602 d->m_jclass = loadClass(d->m_className, jniEnv());
603 d->m_own_jclass = false;
604
605 va_list args;
606 va_start(args, signature);
607 d->construct(signature, args);
608 va_end(args);
609}
610
637QJniObject::QJniObject(jclass clazz, const char *signature, ...)
638 : d(new QJniObjectPrivate())
639{
640 if (clazz) {
641 d->m_jclass = static_cast<jclass>(jniEnv()->NewGlobalRef(clazz));
642 va_list args;
643 va_start(args, signature);
644 d->construct(signature, args);
645 va_end(args);
646 }
647}
648
672QJniObject::QJniObject(jclass clazz)
673 : QJniObject(clazz, "()V")
674{
675}
676
689QJniObject::QJniObject(jobject object)
690 : d(new QJniObjectPrivate())
691{
692 if (!object)
693 return;
694
695 JNIEnv *env = QJniEnvironment::getJniEnv();
696 d->m_jobject = env->NewGlobalRef(object);
697 jclass cls = env->GetObjectClass(object);
698 d->m_jclass = static_cast<jclass>(env->NewGlobalRef(cls));
699 env->DeleteLocalRef(cls);
700}
701
722QJniObject::~QJniObject()
723{}
724
725namespace {
726QByteArray getClassNameHelper(JNIEnv *env, const QJniObjectPrivate *d)
727{
728 if (env->PushLocalFrame(3) != JNI_OK) // JVM out of memory
729 return QByteArray();
730
731 jmethodID mid = env->GetMethodID(d->m_jclass, "getClass", "()Ljava/lang/Class;");
732 jobject classObject = env->CallObjectMethod(d->m_jobject, mid);
733 jclass classObjectClass = env->GetObjectClass(classObject);
734 mid = env->GetMethodID(classObjectClass, "getName", "()Ljava/lang/String;");
735 jstring stringObject = static_cast<jstring>(env->CallObjectMethod(classObject, mid));
736 const jsize length = env->GetStringUTFLength(stringObject);
737 const char* nameString = env->GetStringUTFChars(stringObject, NULL);
738 const QByteArray result = QByteArray::fromRawData(nameString, length).replace('.', '/');
739 env->ReleaseStringUTFChars(stringObject, nameString);
740 env->PopLocalFrame(nullptr);
741 return result;
742}
743}
744
749JNIEnv *QJniObject::jniEnv() const noexcept
750{
751 return QJniEnvironment::getJniEnv();
752}
753
774jobject QJniObject::object() const
775{
776 return javaObject();
777}
778
792jclass QJniObject::objectClass() const
793{
794 return d->m_jclass;
795}
796
804QByteArray QJniObject::className() const
805{
806 if (d->m_className.isEmpty() && d->m_jclass && d->m_jobject) {
807 JNIEnv *env = jniEnv();
808 d->m_className = getClassNameHelper(env, d.get());
809 }
810 return d->m_className;
811}
812
955QJniObject QJniObject::callObjectMethod(const char *methodName, const char *signature, ...) const
956{
957 jmethodID id = getCachedMethodID(jniEnv(), methodName, signature);
958 if (id) {
959 va_list args;
960 va_start(args, signature);
961 QJniObject res = getCleanJniObject(jniEnv()->CallObjectMethodV(d->m_jobject, id, args), jniEnv());
962 va_end(args);
963 return res;
964 }
965
966 return QJniObject();
967}
968
982QJniObject QJniObject::callStaticObjectMethod(const char *className, const char *methodName,
983 const char *signature, ...)
984{
985 JNIEnv *env = QJniEnvironment::getJniEnv();
986 jclass clazz = QJniObject::loadClass(className, env);
987 if (clazz) {
988 jmethodID id = QJniObject::getCachedMethodID(env, clazz,
989 className,
990 methodName, signature, true);
991 if (id) {
992 va_list args;
993 va_start(args, signature);
994 QJniObject res = getCleanJniObject(env->CallStaticObjectMethodV(clazz, id, args), env);
995 va_end(args);
996 return res;
997 }
998 }
999
1000 return QJniObject();
1001}
1002
1009QJniObject QJniObject::callStaticObjectMethod(jclass clazz, const char *methodName,
1010 const char *signature, ...)
1011{
1012 if (clazz) {
1013 QJniEnvironment env;
1014 jmethodID id = getMethodID(env.jniEnv(), clazz, methodName, signature, true);
1015 if (id) {
1016 va_list args;
1017 va_start(args, signature);
1018 QJniObject res = getCleanJniObject(env->CallStaticObjectMethodV(clazz, id, args), env.jniEnv());
1019 va_end(args);
1020 return res;
1021 }
1022 }
1023
1024 return QJniObject();
1025}
1026
1042QJniObject QJniObject::callStaticObjectMethod(jclass clazz, jmethodID methodId, ...)
1043{
1044 if (clazz && methodId) {
1045 QJniEnvironment env;
1046 va_list args;
1047 va_start(args, methodId);
1048 QJniObject res = getCleanJniObject(env->CallStaticObjectMethodV(clazz, methodId, args), env.jniEnv());
1049 va_end(args);
1050 return res;
1051 }
1052
1053 return QJniObject();
1054}
1055
1178QJniObject QJniObject::getStaticObjectField(const char *className,
1179 const char *fieldName,
1180 const char *signature)
1181{
1182 JNIEnv *env = QJniEnvironment::getJniEnv();
1183 jclass clazz = QJniObject::loadClass(className, env);
1184 if (!clazz)
1185 return QJniObject();
1186 jfieldID id = QJniObject::getCachedFieldID(env, clazz,
1187 className,
1188 fieldName,
1189 signature, true);
1190 if (!id)
1191 return QJniObject();
1192
1193 return getCleanJniObject(env->GetStaticObjectField(clazz, id), env);
1194}
1195
1208QJniObject QJniObject::getStaticObjectField(jclass clazz, const char *fieldName,
1209 const char *signature)
1210{
1211 JNIEnv *env = QJniEnvironment::getJniEnv();
1212 jfieldID id = getFieldID(env, clazz, fieldName, signature, true);
1213 return getCleanJniObject(env->GetStaticObjectField(clazz, id), env);
1214}
1215
1250QJniObject QJniObject::getObjectField(const char *fieldName, const char *signature) const
1251{
1252 jfieldID id = getCachedFieldID(jniEnv(), fieldName, signature);
1253 if (!id)
1254 return QJniObject();
1255
1256 return getCleanJniObject(jniEnv()->GetObjectField(d->m_jobject, id), jniEnv());
1257}
1258
1304QJniObject QJniObject::fromString(const QString &string)
1305{
1306 QJniEnvironment env;
1307 jstring stringRef = env->NewString(reinterpret_cast<const jchar*>(string.constData()),
1308 string.length());
1309 QJniObject stringObject = getCleanJniObject(stringRef, env.jniEnv());
1310 stringObject.d->m_className = "java/lang/String";
1311 return stringObject;
1312}
1313
1328QString QJniObject::toString() const
1329{
1330 if (!isValid())
1331 return QString();
1332
1333 QJniObject string = callObjectMethod<jstring>("toString");
1334 const int strLength = string.jniEnv()->GetStringLength(string.object<jstring>());
1335 QString res(strLength, Qt::Uninitialized);
1336 string.jniEnv()->GetStringRegion(string.object<jstring>(), 0, strLength, reinterpret_cast<jchar *>(res.data()));
1337 return res;
1338}
1339
1351bool QJniObject::isClassAvailable(const char *className)
1352{
1353 QJniEnvironment env;
1354
1355 if (!env.jniEnv())
1356 return false;
1357
1358 return loadClass(className, env.jniEnv());
1359}
1360
1372bool QJniObject::isValid() const
1373{
1374 return d->m_jobject;
1375}
1376
1391QJniObject QJniObject::fromLocalRef(jobject lref)
1392{
1393 QJniObject obj(lref);
1394 obj.jniEnv()->DeleteLocalRef(lref);
1395 return obj;
1396}
1397
1398bool QJniObject::isSameObject(jobject obj) const
1399{
1400 if (d->m_jobject == obj)
1401 return true;
1402 if (!d->m_jobject || !obj)
1403 return false;
1404 return jniEnv()->IsSameObject(d->m_jobject, obj);
1405}
1406
1407bool QJniObject::isSameObject(const QJniObject &other) const
1408{
1409 return isSameObject(other.d->m_jobject);
1410}
1411
1412void QJniObject::assign(jobject obj)
1413{
1414 if (d && isSameObject(obj))
1415 return;
1416
1418 if (obj) {
1419 JNIEnv *env = QJniEnvironment::getJniEnv();
1420 d->m_jobject = env->NewGlobalRef(obj);
1421 jclass objectClass = env->GetObjectClass(obj);
1422 d->m_jclass = static_cast<jclass>(env->NewGlobalRef(objectClass));
1423 env->DeleteLocalRef(objectClass);
1424 }
1425}
1426
1427jobject QJniObject::javaObject() const
1428{
1429 return d->m_jobject;
1430}
1431
\inmodule QtCore
Definition qbytearray.h:57
static QByteArray fromRawData(const char *data, qsizetype size)
Constructs a QByteArray that uses the first size bytes of the data array.
Definition qbytearray.h:409
\inmodule QtCore
Definition qhash.h:1145
\inmodule QtCore
QByteArray m_className
void construct(const char *signature=nullptr, Args &&...args)
\inmodule QtCore
\inmodule QtCore
\inmodule QtCore
const_iterator constEnd() const noexcept
Definition qset.h:143
const_iterator constFind(const T &value) const
Definition qset.h:161
static QSharedPointer create(Args &&...arguments)
This is an overloaded member function, provided for convenience. It differs from the above function o...
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
static QString fromLatin1(QByteArrayView ba)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qstring.cpp:5871
\inmodule QtCore
QSet< QString >::iterator it
Combined button and popup list for selecting options.
Q_CORE_EXPORT jclass findClass(const char *className, JNIEnv *env)
jobject classLoader()
Definition qcompare.h:63
constexpr Initialization Uninitialized
static QString methodName(const QDBusIntrospection::Method &method)
typedef QByteArray(EGLAPIENTRYP PFNQGSGETDISPLAYSPROC)()
#define Q_GLOBAL_STATIC(TYPE, NAME,...)
static QByteArray cacheKey(Args &&...args)
QHash< QByteArray, jmethodID > JMethodIDHash
QHash< QByteArray, jclass > JClassHash
static jclass getCachedClass(const QByteArray &className)
static QJniObject getCleanJniObject(jobject object, JNIEnv *env)
QHash< QByteArray, jfieldID > JFieldIDHash
#define qWarning
Definition qlogging.h:166
#define Q_LOGGING_CATEGORY(name,...)
GLuint64 key
GLenum GLuint GLenum GLsizei length
GLenum GLuint id
[7]
GLuint name
GLhandleARB obj
[2]
GLuint res
GLuint64EXT * result
[6]
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
const char className[16]
[1]
Definition qwizard.cpp:100
QSharedPointer< T > other(t)
[5]
QJSValueList args