Commit 5930baae authored by Volker Krause's avatar Volker Krause
Browse files

Rewrite JNI method call wrappers

More macro use unfortunately, but this can now handle overloads correctly
as well. As a side-effect this gives us even stricter argument
compatibility checking than the previous approach.
parent deed122b
Pipeline #184175 passed with stage
in 3 minutes and 56 seconds
......@@ -29,6 +29,13 @@ public:
JNI_METHOD(Jni::Array<java::lang::String>, getStringList)
JNI_METHOD(Jni::Array<jshort>, getShortList)
// overloads
JNI_METHOD(void, overloaded)
JNI_METHOD(void, overloaded, jint)
JNI_METHOD(void, overloaded, java::lang::String, jboolean)
JNI_METHOD(jint, overloaded, jlong, java::lang::String, Jni::Array<jshort>)
JNI_METHOD(Jni::Array<jshort>, overloaded, Intent)
JNI_PROPERTY(java::lang::String, name)
JNI_STATIC_METHOD(void, noRetNoArg)
......@@ -43,27 +50,6 @@ class JniMethodTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testArgumentConversion()
{
static_assert(Internal::is_argument_compatible<jint, jint>::value);
static_assert(Internal::is_argument_compatible<java::lang::String, QAndroidJniObject>::value);
static_assert(Internal::is_argument_compatible<java::lang::String, QString>::value);
static_assert(!Internal::is_argument_compatible<jint, QAndroidJniObject>::value);
static_assert(!Internal::is_argument_compatible<jobject, jint>::value);
static_assert(!Internal::is_argument_compatible<java::lang::String, QUrl>::value);
static_assert(Internal::is_call_compatible<jint>::with<jint>::value);
static_assert(Internal::is_call_compatible<java::lang::String, jfloat>::with<QAndroidJniObject, float>::value);
static_assert(!Internal::is_call_compatible<java::lang::String, jfloat>::with<float, QAndroidJniObject>::value);
static_assert(Internal::is_call_compatible<java::lang::String, java::lang::String>::with<QString, QAndroidJniObject>::value);
static_assert(Internal::is_call_compatible<java::lang::String, java::lang::String>::with<QAndroidJniObject, QString>::value);
// implicit conversion
static_assert(Internal::is_argument_compatible<jint, jdouble>::value);
static_assert(Internal::is_argument_compatible<jfloat, jdouble>::value);
}
void testMethodCalls()
{
#ifndef Q_OS_ANDROID
......@@ -107,7 +93,15 @@ private Q_SLOTS:
// nullptr arguments
obj.startIntent(nullptr);
QCOMPARE(obj.jniHandle().protocol().size(), 22);
// overloaded methods
obj.overloaded();
obj.overloaded(42);
obj.overloaded(QStringLiteral("hello"), true);
Jni::Array<jshort> l4 = obj.overloaded(intent);
obj.overloaded(23, QStringLiteral("world"), l4);
QCOMPARE(obj.jniHandle().protocol().size(), 27);
QCOMPARE(obj.jniHandle().protocol()[0], QLatin1String("callObjectMethod: getName ()Ljava/lang/String; ()"));
QCOMPARE(obj.jniHandle().protocol()[1], QLatin1String("callMethod: setName (Ljava/lang/String;)V (o)"));
QCOMPARE(obj.jniHandle().protocol()[2], QLatin1String("callMethod: setName (Ljava/lang/String;)V (o)"));
......@@ -132,8 +126,14 @@ private Q_SLOTS:
QCOMPARE(obj.jniHandle().protocol()[20], QLatin1String("callObjectMethod: getShortList ()[S ()"));
QCOMPARE(obj.jniHandle().protocol()[21], QLatin1String("callMethod: startIntent (Landroid/content/Intent;)V (o)"));
QCOMPARE(obj.jniHandle().protocol()[22], QLatin1String("callMethod: overloaded ()V ()"));
QCOMPARE(obj.jniHandle().protocol()[23], QLatin1String("callMethod: overloaded (I)V (I)"));
QCOMPARE(obj.jniHandle().protocol()[24], QLatin1String("callMethod: overloaded (Ljava/lang/String;Z)V (oZ)"));
QCOMPARE(obj.jniHandle().protocol()[25], QLatin1String("callObjectMethod: overloaded (Landroid/content/Intent;)[S (o)"));
QCOMPARE(obj.jniHandle().protocol()[26], QLatin1String("callMethod: overloaded (JLjava/lang/String;[S)I (Joo)"));
// ctor call
obj = TestClass(intent);
obj = TestClass(Jni::Object<android::content::Intent>(intent)); // FIXME: this require removal of the ctor in JNI_OBJECT to work without the explicit cast
QCOMPARE(obj.jniHandle().protocol().size(), 1);
QCOMPARE(obj.jniHandle().protocol()[0], QLatin1String("ctor: android/content/Intent (Landroid/content/Intent;)V"));
#if 0
......
......@@ -61,6 +61,7 @@ ecm_generate_headers(KAndroidExtras_java_FORWARDING_HEADERS
ecm_generate_headers(KAndroidExtras_jni_FORWARDING_HEADERS
HEADER_NAMES
JniArgument
JniArray
JniObject
JniMethod
......
......@@ -6,6 +6,7 @@
// list all headers here that have no .cpp file
// this only serves as a guarantee that these headers actually compile
#include "jniargument.h"
#include "jniarray.h"
#include "jnimethod.h"
#include "jniobject.h"
......
/*
SPDX-FileCopyrightText: 2021-2022 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_JNIARGUMENTVALUE_H
#define KANDROIDEXTRAS_JNIARGUMENTVALUE_H
#include "jniobject.h"
#include "jnitypetraits.h"
#include <QAndroidJniObject>
namespace KAndroidExtras {
namespace Jni {
template <typename T> class Array;
}
///@cond internal
namespace Internal {
/** Call argument wrapper. */
template <typename T, typename = std::void_t<>>
struct argument {
typedef std::conditional_t<Jni::is_basic_type<T>::value, T, const Jni::Object<T>&> type;
static inline constexpr auto toCallArgument(type value)
{
if constexpr (Jni::is_basic_type<T>::value) {
return value;
} else {
return value.jniHandle().object();
}
}
};
template <typename T>
struct argument<T, std::void_t<typename T::_jni_ThisType>> {
typedef const T &type;
static inline auto toCallArgument(const T &value)
{
return Jni::handle(value).object();
}
};
template <typename T>
struct argument<Jni::Array<T>> {
typedef const Jni::Array<T>& type;
static inline auto toCallArgument(const Jni::Array<T> &value)
{
return value.jniHandle().object();
}
};
}
///@endcond
}
#endif
......@@ -137,6 +137,7 @@ public:
inline operator QAndroidJniObject() const {
return m_array;
}
inline QAndroidJniObject jniHandle() const { return m_array; }
protected:
typedef array_trait<T> _t;
......
......@@ -6,8 +6,10 @@
#ifndef KANDROIDEXTRAS_JNIMETHOD_H
#define KANDROIDEXTRAS_JNIMETHOD_H
#include "jniargument.h"
#include "jniarray.h"
#include "jniobject.h"
#include "jnipp.h"
#include "jnireturnvalue.h"
#include "jnitypetraits.h"
......@@ -15,96 +17,43 @@
namespace KAndroidExtras {
///@cond internal
namespace Internal {
// argument compatibility checking
template <typename SigT, typename ArgT> struct is_argument_compatible
{
static inline constexpr bool value =
!std::is_same_v<SigT, void> && !std::is_same_v<ArgT, void> &&
(std::is_convertible_v<ArgT, SigT> ||
(!Jni::is_basic_type<SigT>::value && std::is_convertible_v<ArgT, typename Jni::converter<SigT>::type>) ||
(!Jni::is_basic_type<SigT>::value && std::is_convertible_v<ArgT, QAndroidJniObject>) ||
(!Jni::is_basic_type<SigT>::value && std::is_convertible_v<ArgT, typename Jni::converter<SigT>::type>));
};
// method parameter generation
#define JNI_PARAM(Type, Name) typename KAndroidExtras::Internal::argument<Type>::type Name
template <typename ...Sig> struct is_call_compatible
{
template <typename ...Args> struct with {
template<typename... Values>
inline static constexpr bool all(Values... values) { return (... && values); }
static inline constexpr bool value = all(is_argument_compatible<Sig, Args>::value...);
};
};
// Argument type conversion
// This happens in two phases:
// - phase one applies implicit conversions for basic types, and conversion to QAndroidJniObject for non-basic types
// - the results of this are stored on the stack to outlive the JNI call
// - phase two converts QAndroidJniObject instances to jobject
// This is needed as we need temporary QAndroidJniObjects resulting from implicit conversion to
// still be valid when the JNI call is performed.
template <typename SigT, typename ArgT, bool is_basic, bool is_convertible> struct call_argument {};
template <typename SigT, typename ArgT> struct call_argument<SigT, ArgT, true, false>
{
inline constexpr SigT operator()(ArgT value) const
{
return value;
}
};
template <typename SigT, typename ArgT> struct call_argument<SigT, ArgT, false, true>
{
inline QAndroidJniObject operator()(const typename Jni::converter<SigT>::type &value) const
{
return Jni::reverse_converter<SigT>::type::convert(value);
}
};
template <typename ArgT, typename SigT> struct call_argument<SigT, ArgT, false, false>
{
inline QAndroidJniObject operator()(const QAndroidJniObject &value) const
{
return value;
}
};
#define JNI_PARAMS_0(accu, arg)
#define JNI_PARAMS_1(accu, arg, ...) JNI_PARAM(arg, a1)
#define JNI_PARAMS_2(accu, arg, ...) JNI_PARAM(arg, a2), JNI_PARAMS_1(accu, __VA_ARGS__)
#define JNI_PARAMS_3(accu, arg, ...) JNI_PARAM(arg, a3), JNI_PARAMS_2(accu, __VA_ARGS__)
template <typename SigT, typename ArgT> struct is_convertible {
static inline constexpr bool value =
!std::is_same_v<typename Jni::converter<SigT>::type, void> &&
!std::is_same_v<std::remove_cv_t<std::remove_reference_t<ArgT>>, QAndroidJniObject> &&
// prefer conversion-less pass-through when possible
!std::is_convertible_v<ArgT, QAndroidJniObject>;
};
#define JNI_PARAMS_(N, accu, ...) JNI_PP_CONCAT(JNI_PARAMS_, N)(accu, __VA_ARGS__)
#define JNI_PARAMS(...) JNI_PARAMS_(JNI_PP_NARG(__VA_ARGS__), "", __VA_ARGS__)
template <typename SigT, typename ArgT> constexpr call_argument<SigT, std::remove_reference_t<ArgT>, Jni::is_basic_type<SigT>::value, is_convertible<SigT, ArgT>::value> toCallArgument = {};
// method argument forwarding generation
#define JNI_ARG(Type, Name) KAndroidExtras::Internal::argument<Type>::toCallArgument(Name)
#define JNI_ARGS_0(accu, arg)
#define JNI_ARGS_1(accu, arg, ...) JNI_ARG(arg, a1)
#define JNI_ARGS_2(accu, arg, ...) JNI_ARG(arg, a2), JNI_ARGS_1(accu, __VA_ARGS__)
#define JNI_ARGS_3(accu, arg, ...) JNI_ARG(arg, a3), JNI_ARGS_2(accu, __VA_ARGS__)
template <typename T> inline constexpr T toFinalCallArgument(T value) { return value; }
inline jobject toFinalCallArgument(const QAndroidJniObject &value) { return value.object(); }
#define JNI_ARGS_(N, accu, ...) JNI_PP_CONCAT(JNI_ARGS_, N)(accu, __VA_ARGS__)
#define JNI_ARGS(...) JNI_ARGS_(JNI_PP_NARG(__VA_ARGS__), "", __VA_ARGS__)
// call wrapper
template <typename RetT, typename ...Sig>
struct invoker {
namespace Internal {
// method call wrapper
template <typename RetT>
struct caller {
template <typename ...Args>
static auto call(QAndroidJniObject handle, const char *name, const char *signature, Args&&... args)
{
static_assert(is_call_compatible<Sig...>::template with<Args...>::value, "incompatible call arguments");
const auto params = std::make_tuple(toCallArgument<Sig, Args>(std::forward<Args>(args))...);
return doCall(handle, name, signature, params, std::index_sequence_for<Args...>{});
}
template <typename ParamT, std::size_t ...Index>
static auto doCall(QAndroidJniObject handle, const char *name, const char *signature, const ParamT &params, std::index_sequence<Index...>)
static auto call(const QAndroidJniObject &handle, const char *name, const char *signature, Args&&... args)
{
if constexpr (Jni::is_basic_type<RetT>::value) {
return handle.callMethod<RetT>(name, signature, toFinalCallArgument(std::get<Index>(params))...);
return handle.callMethod<RetT>(name, signature, std::forward<Args>(args)...);
} else {
return Internal::return_wrapper<RetT>::toReturnValue(handle.callObjectMethod(name, signature, toFinalCallArgument(std::get<Index>(params))...));
return Internal::return_wrapper<RetT>::toReturnValue(handle.callObjectMethod(name, signature, std::forward<Args>(args)...));
}
}
};
template <typename RetT>
struct invoker<RetT> {
static auto call(QAndroidJniObject handle, const char *name, const char *signature)
static auto call(const QAndroidJniObject &handle, const char *name, const char *signature)
{
if constexpr (Jni::is_basic_type<RetT>::value) {
return handle.callMethod<RetT>(name, signature);
......@@ -114,30 +63,18 @@ namespace Internal {
}
};
// static call wrapper
template <typename RetT, typename ...Sig>
struct static_invoker {
// static method call wrapper
template <typename RetT>
struct static_caller {
template <typename ...Args>
static auto call(const char *className, const char *name, const char *signature, Args&&... args)
{
static_assert(is_call_compatible<Sig...>::template with<Args...>::value, "incompatible call arguments");
const auto params = std::make_tuple(toCallArgument<Sig, Args>(std::forward<Args>(args))...);
return doCall(className, name, signature, params, std::index_sequence_for<Args...>{});
}
template <typename ParamT, std::size_t ...Index>
static auto doCall(const char *className, const char *name, const char *signature, const ParamT &params, std::index_sequence<Index...>)
{
if constexpr (Jni::is_basic_type<RetT>::value) {
return QAndroidJniObject::callStaticMethod<RetT>(className, name, signature, toFinalCallArgument(std::get<Index>(params))...);
return QAndroidJniObject::callStaticMethod<RetT>(className, name, signature, std::forward<Args>(args)...);
} else {
return Internal::return_wrapper<RetT>::toReturnValue(QAndroidJniObject::callStaticObjectMethod(className, name, signature, toFinalCallArgument(std::get<Index>(params))...));
return Internal::return_wrapper<RetT>::toReturnValue(QAndroidJniObject::callStaticObjectMethod(className, name, signature, std::forward<Args>(args)...));
}
}
};
template <typename RetT>
struct static_invoker<RetT> {
static auto call(const char *className, const char *name, const char *signature)
{
if constexpr (Jni::is_basic_type<RetT>::value) {
......@@ -147,32 +84,6 @@ namespace Internal {
}
}
};
// ctor wrapper
template <typename ...Sig>
struct constructor {
template <typename ...Args>
static QAndroidJniObject call(const char *className, const char *signature, Args&&... args)
{
static_assert(is_call_compatible<Sig...>::template with<Args...>::value, "incompatible constructor arguments");
const auto params = std::make_tuple(toCallArgument<Sig, Args>(std::forward<Args>(args))...);
return doCall(className, signature, params, std::index_sequence_for<Args...>{});
}
template <typename ParamT, std::size_t ...Index>
static QAndroidJniObject doCall(const char *className, const char *signature, const ParamT &params, std::index_sequence<Index...>)
{
return QAndroidJniObject(className, signature, toFinalCallArgument(std::get<Index>(params))...);
}
};
template <>
struct constructor<> {
static QAndroidJniObject call(const char *className, const char *signature)
{
return QAndroidJniObject(className, signature);
}
};
}
///@endcond
......@@ -202,10 +113,9 @@ namespace Internal {
* with @c JNI_TYPE.
*/
#define JNI_METHOD(RetT, Name, ...) \
template <typename ...Args> \
inline auto Name(Args&&... args) const { \
inline auto Name( JNI_PARAMS(__VA_ARGS__) ) const { \
using namespace KAndroidExtras; \
return Internal::invoker<RetT, ## __VA_ARGS__>::call(jniHandle(), "" #Name, Jni::signature<RetT(__VA_ARGS__)>(), std::forward<Args>(args)...); \
return Internal::caller<RetT>::call(jniHandle(), "" #Name, Jni::signature<RetT(__VA_ARGS__)>() __VA_OPT__(,) JNI_ARGS(__VA_ARGS__)); \
}
/**
......@@ -234,10 +144,9 @@ inline auto Name(Args&&... args) const { \
* with @c JNI_TYPE.
*/
#define JNI_STATIC_METHOD(RetT, Name, ...) \
template <typename ...Args> \
static inline auto Name(Args&&... args) { \
static inline auto Name( JNI_PARAMS(__VA_ARGS__) ) { \
using namespace KAndroidExtras; \
return Internal::static_invoker<RetT, ## __VA_ARGS__>::call(Jni::typeName<_jni_ThisType>(), "" #Name, Jni::signature<RetT(__VA_ARGS__)>(), std::forward<Args>(args)...); \
return Internal::static_caller<RetT>::call(Jni::typeName<_jni_ThisType>(), "" #Name, Jni::signature<RetT(__VA_ARGS__)>() __VA_OPT__(,) JNI_ARGS(__VA_ARGS__)); \
}
/**
......@@ -255,10 +164,9 @@ static inline auto Name(Args&&... args) { \
* with @c JNI_TYPE.
*/
#define JNI_CONSTRUCTOR(Name, ...) \
template <typename ...Args> \
inline Name(Args&&... args) { \
inline Name( JNI_PARAMS(__VA_ARGS__) ) { \
using namespace KAndroidExtras; \
setJniHandle(Internal::constructor<__VA_ARGS__>::call(Jni::typeName<_jni_ThisType>(), Jni::signature<void(__VA_ARGS__)>(), std::forward<Args>(args)...)); \
setJniHandle(QAndroidJniObject(Jni::typeName<_jni_ThisType>(), Jni::signature<void(__VA_ARGS__)>() __VA_OPT__(,) JNI_ARGS(__VA_ARGS__))); \
}
}
......
......@@ -4,8 +4,8 @@
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_JNICOMMON_H
#define KANDROIDEXTRAS_JNICOMMON_H
#ifndef KANDROIDEXTRAS_JNIOBJECT_H
#define KANDROIDEXTRAS_JNIOBJECT_H
#include "jnitypetraits.h"
......@@ -15,14 +15,47 @@ namespace java { namespace lang { struct String; } }
namespace Jni {
template <typename T>
inline QAndroidJniObject handle(const T &wrapper)
{
return wrapper.jniHandle();
}
/** Wrapper for JNI objects with a convertible C++ type.
* This provides implicit on-demand conversion to the C++ type, for types
* that don't have a manually defined wrapper.
*/
template <typename T>
class Object {
private:
template <typename DummyT> class _dummy_t {};
public:
explicit inline Object(const QAndroidJniObject &v) : m_handle(v) {}
inline Object(const QAndroidJniObject &v) : m_handle(v) {}
// implicit conversion from a compatible C++ type
inline Object(const std::conditional_t<std::is_same_v<typename Jni::converter<T>::type, void>,
_dummy_t<T>, typename Jni::converter<T>::type> &v)
: m_handle(Jni::reverse_converter<T>::type::convert(v))
{}
// implicit conversion from a custom wrapper for the same type
template <typename WrapperT, typename = std::enable_if_t<std::is_same_v<typename WrapperT::_jni_BaseType, T>, WrapperT>>
inline Object(const WrapperT &w)
: m_handle(Jni::handle(w))
{}
// special-case QString, as that often comes out of implicitly converted types,
// and thus can't be consumed by another implicit conversion step
template <typename StrT = QString, typename = std::enable_if_t<std::is_same_v<T, java::lang::String>, StrT>>
inline Object(const QString &s)
: m_handle(QAndroidJniObject::fromString(s))
{}
// special case for null values
inline Object(std::nullptr_t)
: m_handle((jobject)nullptr)
{}
inline operator QAndroidJniObject() const {
return m_handle;
}
......@@ -30,6 +63,10 @@ public:
return Jni::converter<T>::convert(m_handle);
}
inline QAndroidJniObject jniHandle() const {
return m_handle;
}
// forward basic QAndroidJniObject API
inline bool isValid() const {
return m_handle.isValid();
......@@ -56,6 +93,7 @@ private:
#define JNI_UNMANAGED_OBJECT(Class, BaseType) \
public: \
typedef Class _jni_ThisType; \
typedef BaseType _jni_BaseType; \
private: \
static inline constexpr const char *jniName() { return KAndroidExtras::Jni::typeName<BaseType>(); } \
friend constexpr const char* KAndroidExtras::Jni::typeName<Class>();
......@@ -72,6 +110,7 @@ private: \
QAndroidJniObject _m_jni_handle; \
inline QAndroidJniObject jniHandle() const { return _m_jni_handle; } \
inline void setJniHandle(const QAndroidJniObject &h) { _m_jni_handle = h; } \
friend QAndroidJniObject KAndroidExtras::Jni::handle<Class>(const Class&); \
public: \
explicit Class(const QAndroidJniObject &h) : _m_jni_handle(h) {}
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment