Compile-time generate a JNINativeMethod from a C function

Add a template function that allows us to get the method signature
string from a free function, which are used as native callbacks methods
from Java.

Provide a macro that defines a signature object, and a JNINativeMethod
object based on it, in an internal namespace so that we don't pollute
the namespace with generated names.

Add another macro to get the generated JNINativeMethod object based on
the free function name.

Lastly, add overloads to QJniEnvironment::registerNativeMethods that
take a std::initializer_list of JNINativeMethods.

We can now declare a free function to be a JNI native method:

static bool callbackFromJava(JNIEnv *e, jobject /*thiz*/, jstring p1)
{
   // ...
}
Q_JNI_DECLARE_NATIVE_METHOD(callbackFromJava);

and register it with the JNI environment like this:

QJniEnvironment jni;
jni.registerNativeMethods(clazz, {
    Q_JNI_NATIVE_METHOD(callbackFromJava)
});

removing a significant amount of boiler plate code.

Change-Id: Ie4007b24125879fed3dae1f4d232b4aa95999b44
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-22 00:55:28 +02:00
parent fb56a0f2ce
commit dd8da7ebd9
3 changed files with 46 additions and 14 deletions

View File

@ -52,6 +52,16 @@ public:
bool registerNativeMethods(const char *className, const JNINativeMethod methods[], int size);
bool registerNativeMethods(jclass clazz, const JNINativeMethod methods[], int size);
bool registerNativeMethods(const char *className, std::initializer_list<JNINativeMethod> methods)
{
return registerNativeMethods(className, std::data(methods), methods.size());
}
bool registerNativeMethods(jclass clazz, std::initializer_list<JNINativeMethod> methods)
{
return registerNativeMethods(clazz, std::data(methods), methods.size());
}
#if QT_DEPRECATED_SINCE(6, 2)
// ### Qt 7: remove
QT_DEPRECATED_VERSION_X_6_2("Use the overload with a const JNINativeMethod[] instead.")

View File

@ -274,6 +274,18 @@ static constexpr auto constructorSignature()
return methodSignature<void, Args...>();
}
template<typename Ret, typename ...Args>
static constexpr auto nativeMethodSignature(Ret (*)(JNIEnv *, jobject, Args...))
{
return methodSignature<Ret, Args...>();
}
template<typename Ret, typename ...Args>
static constexpr auto nativeMethodSignature(Ret (*)(JNIEnv *, jclass, Args...))
{
return methodSignature<Ret, Args...>();
}
// A generic thin wrapper around jobject, convertible to jobject.
// We need this as a baseclass so that QJniObject can be implicitly
// constructed from the various subclasses - we can't provide an
@ -303,6 +315,18 @@ constexpr auto QtJniTypes::typeSignature<QtJniTypes::Type>() \
return QtJniTypes::String(Signature); \
} \
#define Q_JNI_DECLARE_NATIVE_METHOD(Method) \
namespace QtJniMethods { \
static constexpr auto Method##_signature = \
QtJniTypes::nativeMethodSignature(Method); \
static const JNINativeMethod Method##_method = { \
#Method, Method##_signature.data(), \
reinterpret_cast<void *>(Method) \
}; \
} \
#define Q_JNI_NATIVE_METHOD(Method) QtJniMethods::Method##_method
QT_END_NAMESPACE
#endif

View File

@ -98,12 +98,14 @@ static void callbackFromJava(JNIEnv *env, jobject /*thiz*/, jstring value)
Q_UNUSED(env)
registerNativesString = QJniObject(value).toString();
}
Q_JNI_DECLARE_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);
void tst_QJniEnvironment::registerNativeMethods()
{
@ -111,11 +113,9 @@ void tst_QJniEnvironment::registerNativeMethods()
QJniEnvironment env;
{
const JNINativeMethod methods[] {
{"callbackFromJava", "(Ljava/lang/String;)V", reinterpret_cast<void *>(callbackFromJava)}
};
QVERIFY(env.registerNativeMethods(javaTestClass, methods, 1));
QVERIFY(env.registerNativeMethods(javaTestClass, {
Q_JNI_NATIVE_METHOD(callbackFromJava)
}));
QJniObject::callStaticMethod<void>(javaTestClass,
"appendJavaToString",
@ -127,10 +127,9 @@ void tst_QJniEnvironment::registerNativeMethods()
// No default constructor in class
{
const JNINativeMethod methods[] {{"callbackFromJavaNoCtor", "(Ljava/lang/String;)V",
reinterpret_cast<void *>(callbackFromJavaNoCtor)}};
QVERIFY(env.registerNativeMethods(javaTestClassNoCtor, methods, 1));
QVERIFY(env.registerNativeMethods(javaTestClassNoCtor, {
Q_JNI_NATIVE_METHOD(callbackFromJavaNoCtor)
}));
QJniObject::callStaticMethod<void>(javaTestClassNoCtor,
"appendJavaToString",
@ -146,17 +145,16 @@ static void intCallbackFromJava(JNIEnv *env, jobject /*thiz*/, jint value)
Q_UNUSED(env)
registerNativeInteger = static_cast<int>(value);
}
Q_JNI_DECLARE_NATIVE_METHOD(intCallbackFromJava);
void tst_QJniEnvironment::registerNativeMethodsByJclass()
{
const JNINativeMethod methods[] {
{ "intCallbackFromJava", "(I)V", reinterpret_cast<void *>(intCallbackFromJava) }
};
QJniEnvironment env;
jclass clazz = env.findClass(javaTestClass);
QVERIFY(clazz != 0);
QVERIFY(env.registerNativeMethods(clazz, methods, 1));
QVERIFY(env.registerNativeMethods(clazz, {
Q_JNI_NATIVE_METHOD(intCallbackFromJava)
}));
QCOMPARE(registerNativeInteger, 0);