JNI: move JNI type macros into separate header

This removes the dependency from QJniObject to QtJniTypes, and allows us
to add more useful helpers to types declared through the macros, as we
can rely on QJniObject being fully declared.

Note: this is all undocumented API, so fine to change even though it
currently lives in a public header.

Change-Id: I07478ecb80ae166d619a09aed6820f680afed31b
Reviewed-by: Tinja Paavoseppä <tinja.paavoseppa@qt.io>
bb10
Volker Hilsheimer 2023-09-12 17:48:40 +02:00
parent 7a27609d73
commit 1de8cb8f91
5 changed files with 339 additions and 317 deletions

View File

@ -999,7 +999,7 @@ qt_internal_extend_target(Core CONDITION ANDROID
SOURCES
io/qstandardpaths_android.cpp
io/qstorageinfo_linux.cpp
kernel/qjnitypes.h
kernel/qjnitypes.h kernel/qjnitypes_impl.h
kernel/qjnienvironment.cpp kernel/qjnienvironment.h
kernel/qjniobject.cpp kernel/qjniobject.h
kernel/qjnihelpers.cpp kernel/qjnihelpers_p.h

View File

@ -8,7 +8,7 @@
#if defined(Q_QDOC) || defined(Q_OS_ANDROID)
#include <jni.h>
#include <QtCore/qjnitypes.h>
#include <QtCore/qjnitypes_impl.h>
QT_BEGIN_NAMESPACE

View File

@ -9,7 +9,6 @@
#if defined(Q_QDOC) || defined(Q_OS_ANDROID)
#include <jni.h>
#include <QtCore/qjnienvironment.h>
#include <QtCore/qjnitypes.h>
QT_BEGIN_NAMESPACE
@ -42,7 +41,6 @@ public:
std::forward<Args>(args)...)
{}
QJniObject(jobject globalRef);
inline QJniObject(QtJniTypes::Object wrapper) noexcept : QJniObject(jobject(wrapper)) {}
~QJniObject();
template<typename Class, typename ...Args>

View File

@ -4,330 +4,25 @@
#ifndef QJNITYPES_H
#define QJNITYPES_H
#include <QtCore/qglobal.h>
#include <QtCore/q20type_traits.h>
#if defined(Q_QDOC) || defined(Q_OS_ANDROID)
#include <jni.h>
#include <QtCore/qjnitypes_impl.h>
#include <QtCore/qjniobject.h>
QT_BEGIN_NAMESPACE
namespace QtJniTypes
{
// a constexpr type for string literals of any character width, aware of the length
// of the string.
template<size_t N_WITH_NULL, typename BaseType = char>
struct String
{
BaseType m_data[N_WITH_NULL] = {};
constexpr String() noexcept {}
// Can be instantiated (only) with a string literal
constexpr explicit String(const BaseType (&data)[N_WITH_NULL]) noexcept
{
for (size_t i = 0; i < N_WITH_NULL - 1; ++i)
m_data[i] = data[i];
}
constexpr BaseType at(size_t i) const { return m_data[i]; }
constexpr BaseType operator[](size_t i) const { return at(i); }
static constexpr size_t size() noexcept { return N_WITH_NULL; }
constexpr operator const BaseType *() const noexcept { return m_data; }
constexpr const BaseType *data() const noexcept { return m_data; }
template<size_t N2_WITH_NULL>
constexpr bool startsWith(const BaseType (&lit)[N2_WITH_NULL]) const noexcept
{
if constexpr (N2_WITH_NULL > N_WITH_NULL) {
return false;
} else {
for (size_t i = 0; i < N2_WITH_NULL - 1; ++i) {
if (m_data[i] != lit[i])
return false;
}
}
return true;
}
constexpr bool startsWith(BaseType c) const noexcept
{
return N_WITH_NULL > 1 && m_data[0] == c;
}
template<size_t N2_WITH_NULL>
constexpr bool endsWith(const BaseType (&lit)[N2_WITH_NULL]) const noexcept
{
if constexpr (N2_WITH_NULL > N_WITH_NULL) {
return false;
} else {
for (size_t i = 0; i < N2_WITH_NULL; ++i) {
if (m_data[N_WITH_NULL - i - 1] != lit[N2_WITH_NULL - i - 1])
return false;
}
}
return true;
}
constexpr bool endsWith(BaseType c) const noexcept
{
return N_WITH_NULL > 1 && m_data[N_WITH_NULL - 2] == c;
}
template<size_t N2_WITH_NULL>
friend inline constexpr bool operator==(const String<N_WITH_NULL> &lhs,
const String<N2_WITH_NULL> &rhs) noexcept
{
if constexpr (N_WITH_NULL != N2_WITH_NULL) {
return false;
} else {
for (size_t i = 0; i < N_WITH_NULL - 1; ++i) {
if (lhs.at(i) != rhs.at(i))
return false;
}
}
return true;
}
template<size_t N2_WITH_NULL>
friend inline constexpr bool operator!=(const String<N_WITH_NULL> &lhs,
const String<N2_WITH_NULL> &rhs) noexcept
{
return !operator==(lhs, rhs);
}
template<size_t N2_WITH_NULL>
friend inline constexpr bool operator==(const String<N_WITH_NULL> &lhs,
const BaseType (&rhs)[N2_WITH_NULL]) noexcept
{
return operator==(lhs, String<N2_WITH_NULL>(rhs));
}
template<size_t N2_WITH_NULL>
friend inline constexpr bool operator==(const BaseType (&lhs)[N2_WITH_NULL],
const String<N_WITH_NULL> &rhs) noexcept
{
return operator==(String<N2_WITH_NULL>(lhs), rhs);
}
template<size_t N2_WITH_NULL>
friend inline constexpr bool operator!=(const String<N_WITH_NULL> &lhs,
const BaseType (&rhs)[N2_WITH_NULL]) noexcept
{
return operator!=(lhs, String<N2_WITH_NULL>(rhs));
}
template<size_t N2_WITH_NULL>
friend inline constexpr bool operator!=(const BaseType (&lhs)[N2_WITH_NULL],
const String<N_WITH_NULL> &rhs) noexcept
{
return operator!=(String<N2_WITH_NULL>(lhs), rhs);
}
template<size_t N2_WITH_NULL>
friend inline constexpr auto operator+(const String<N_WITH_NULL> &lhs,
const String<N2_WITH_NULL> &rhs) noexcept
{
char data[N_WITH_NULL + N2_WITH_NULL - 1] = {};
for (size_t i = 0; i < N_WITH_NULL - 1; ++i)
data[i] = lhs[i];
for (size_t i = 0; i < N2_WITH_NULL - 1; ++i)
data[N_WITH_NULL - 1 + i] = rhs[i];
return String<N_WITH_NULL + N2_WITH_NULL - 1>(data);
}
};
// Helper types that allow us to disable variadic overloads that would conflict
// with overloads that take a const char*.
template<typename T, size_t N = 0> struct IsStringType : std::false_type {};
template<> struct IsStringType<const char*, 0> : std::true_type {};
template<size_t N> struct IsStringType<String<N>> : std::true_type {};
template<size_t N> struct IsStringType<const char[N]> : std::true_type {};
template<bool flag = false>
static void staticAssertTypeMismatch()
{
static_assert(flag, "The used type is not supported by this template call. "
"Use a JNI based type instead.");
}
template<typename T>
constexpr auto typeSignature()
{
if constexpr (std::is_array_v<T>) {
using UnderlyingType = typename std::remove_extent_t<T>;
static_assert(!std::is_array_v<UnderlyingType>,
"typeSignature() does not handle multi-dimensional arrays");
return String("[") + typeSignature<UnderlyingType>();
} else if constexpr (std::is_same_v<T, jobject>) {
return String("Ljava/lang/Object;");
} else if constexpr (std::is_same_v<T, jclass>) {
return String("Ljava/lang/Class;");
} else if constexpr (std::is_same_v<T, jstring>) {
return String("Ljava/lang/String;");
} else if constexpr (std::is_same_v<T, jobjectArray>) {
return String("[Ljava/lang/Object;");
} else if constexpr (std::is_same_v<T, jthrowable>) {
return String("Ljava/lang/Throwable;");
} else if constexpr (std::is_same_v<T, jbooleanArray>) {
return String("[Z");
} else if constexpr (std::is_same_v<T, jbyteArray>) {
return String("[B");
} else if constexpr (std::is_same_v<T, jshortArray>) {
return String("[S");
} else if constexpr (std::is_same_v<T, jintArray>) {
return String("[I");
} else if constexpr (std::is_same_v<T, jlongArray>) {
return String("[J");
} else if constexpr (std::is_same_v<T, jfloatArray>) {
return String("[F");
} else if constexpr (std::is_same_v<T, jdoubleArray>) {
return String("[D");
} else if constexpr (std::is_same_v<T, jcharArray>) {
return String("[C");
} else if constexpr (std::is_same_v<T, jboolean>) {
return String("Z");
} else if constexpr (std::is_same_v<T, bool>) {
return String("Z");
} else if constexpr (std::is_same_v<T, jbyte>) {
return String("B");
} else if constexpr (std::is_same_v<T, jchar>) {
return String("C");
} else if constexpr (std::is_same_v<T, char>) {
return String("C");
} else if constexpr (std::is_same_v<T, jshort>) {
return String("S");
} else if constexpr (std::is_same_v<T, short>) {
return String("S");
} else if constexpr (std::is_same_v<T, jint>) {
return String("I");
} else if constexpr (std::is_same_v<T, int>) {
return String("I");
} else if constexpr (std::is_same_v<T, uint>) {
return String("I");
} else if constexpr (std::is_same_v<T, jlong>) {
return String("J");
} else if constexpr (std::is_same_v<T, long>) {
return String("J");
} else if constexpr (std::is_same_v<T, jfloat>) {
return String("F");
} else if constexpr (std::is_same_v<T, float>) {
return String("F");
} else if constexpr (std::is_same_v<T, jdouble>) {
return String("D");
} else if constexpr (std::is_same_v<T, double>) {
return String("D");
} else if constexpr (std::is_same_v<T, void>) {
return String("V");
}
// else: The return type becomes void, indicating that the typeSignature
// template is not implemented for the respective type. We use this to
// detect invalid types in the ValidSignatureTypes and ValidFieldType
// predicates below.
}
template<bool flag = false>
static void staticAssertClassNotRegistered()
{
static_assert(flag, "Class not registered, use Q_DECLARE_JNI_CLASS");
}
template<typename T>
constexpr auto className()
{
if constexpr (std::is_same_v<T, jstring>)
return String("java/lang/String");
else
staticAssertClassNotRegistered();
}
template<typename T>
static constexpr bool isPrimitiveType()
{
return typeSignature<T>().size() == 2;
}
template<typename T>
static constexpr bool isObjectType()
{
if constexpr (std::is_convertible_v<T, jobject>) {
return true;
} else {
constexpr auto signature = typeSignature<T>();
return (signature.startsWith('L') || signature.startsWith('['))
&& signature.endsWith(';');
}
}
template<typename T>
static constexpr bool isArrayType()
{
constexpr auto signature = typeSignature<T>();
return signature.startsWith('[');
}
template<typename T>
static constexpr void assertObjectType()
{
static_assert(isObjectType<T>(),
"Type needs to be a JNI object type (convertible to jobject, or with "
"an object type signature registered)!");
}
// A set of types is valid if typeSignature is implemented for all of them
template<typename ...Types>
constexpr bool ValidSignatureTypesDetail = !std::disjunction<std::is_same<
decltype(QtJniTypes::typeSignature<Types>()),
void>...,
IsStringType<Types>...>::value;
template<typename ...Types>
using ValidSignatureTypes = std::enable_if_t<
ValidSignatureTypesDetail<q20::remove_cvref_t<Types>...>, bool>;
template<typename Type>
constexpr bool ValidFieldTypeDetail = isObjectType<Type>() || isPrimitiveType<Type>();
template<typename Type>
using ValidFieldType = std::enable_if_t<
ValidFieldTypeDetail<q20::remove_cvref_t<Type>>, bool>;
template<typename R, typename ...Args, ValidSignatureTypes<R, Args...> = true>
static constexpr auto methodSignature()
{
return (String("(") +
... + typeSignature<q20::remove_cvref_t<Args>>())
+ String(")")
+ typeSignature<R>();
}
template<typename T, ValidSignatureTypes<T> = true>
static constexpr auto fieldSignature()
{
return QtJniTypes::typeSignature<T>();
}
template<typename ...Args, ValidSignatureTypes<Args...> = true>
static constexpr auto constructorSignature()
{
return methodSignature<void, Args...>();
}
template<typename Ret, typename ...Args, ValidSignatureTypes<Ret, Args...> = true>
static constexpr auto nativeMethodSignature(Ret (*)(JNIEnv *, jobject, Args...))
{
return methodSignature<Ret, Args...>();
}
template<typename Ret, typename ...Args, ValidSignatureTypes<Ret, Args...> = true>
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
// operator QJniObject() here as the class is not declared.
// constructed from the various subclasses. We can also pass instances
// of this type (or of any of the generated subclasses) as if it was
// a jobject.
struct Object
{
jobject _object;
constexpr operator jobject() const { return _object; }
operator QJniObject() const { return QJniObject(_object); }
};
} // namespace QtJniTypes
@ -402,6 +97,6 @@ static const JNINativeMethod Method##_method = { \
QT_END_NAMESPACE
#endif
#endif // defined(Q_QDOC) || defined(Q_OS_ANDROID)
#endif // QJNITYPES_H

View File

@ -0,0 +1,329 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QJNITYPES_IMPL_H
#define QJNITYPES_IMPL_H
#include <QtCore/qglobal.h>
#include <QtCore/q20type_traits.h>
#if defined(Q_QDOC) || defined(Q_OS_ANDROID)
#include <jni.h>
QT_BEGIN_NAMESPACE
namespace QtJniTypes
{
// a constexpr type for string literals of any character width, aware of the length
// of the string.
template<size_t N_WITH_NULL, typename BaseType = char>
struct String
{
BaseType m_data[N_WITH_NULL] = {};
constexpr String() noexcept {}
// Can be instantiated (only) with a string literal
constexpr explicit String(const BaseType (&data)[N_WITH_NULL]) noexcept
{
for (size_t i = 0; i < N_WITH_NULL - 1; ++i)
m_data[i] = data[i];
}
constexpr BaseType at(size_t i) const { return m_data[i]; }
constexpr BaseType operator[](size_t i) const { return at(i); }
static constexpr size_t size() noexcept { return N_WITH_NULL; }
constexpr operator const BaseType *() const noexcept { return m_data; }
constexpr const BaseType *data() const noexcept { return m_data; }
template<size_t N2_WITH_NULL>
constexpr bool startsWith(const BaseType (&lit)[N2_WITH_NULL]) const noexcept
{
if constexpr (N2_WITH_NULL > N_WITH_NULL) {
return false;
} else {
for (size_t i = 0; i < N2_WITH_NULL - 1; ++i) {
if (m_data[i] != lit[i])
return false;
}
}
return true;
}
constexpr bool startsWith(BaseType c) const noexcept
{
return N_WITH_NULL > 1 && m_data[0] == c;
}
template<size_t N2_WITH_NULL>
constexpr bool endsWith(const BaseType (&lit)[N2_WITH_NULL]) const noexcept
{
if constexpr (N2_WITH_NULL > N_WITH_NULL) {
return false;
} else {
for (size_t i = 0; i < N2_WITH_NULL; ++i) {
if (m_data[N_WITH_NULL - i - 1] != lit[N2_WITH_NULL - i - 1])
return false;
}
}
return true;
}
constexpr bool endsWith(BaseType c) const noexcept
{
return N_WITH_NULL > 1 && m_data[N_WITH_NULL - 2] == c;
}
template<size_t N2_WITH_NULL>
friend inline constexpr bool operator==(const String<N_WITH_NULL> &lhs,
const String<N2_WITH_NULL> &rhs) noexcept
{
if constexpr (N_WITH_NULL != N2_WITH_NULL) {
return false;
} else {
for (size_t i = 0; i < N_WITH_NULL - 1; ++i) {
if (lhs.at(i) != rhs.at(i))
return false;
}
}
return true;
}
template<size_t N2_WITH_NULL>
friend inline constexpr bool operator!=(const String<N_WITH_NULL> &lhs,
const String<N2_WITH_NULL> &rhs) noexcept
{
return !operator==(lhs, rhs);
}
template<size_t N2_WITH_NULL>
friend inline constexpr bool operator==(const String<N_WITH_NULL> &lhs,
const BaseType (&rhs)[N2_WITH_NULL]) noexcept
{
return operator==(lhs, String<N2_WITH_NULL>(rhs));
}
template<size_t N2_WITH_NULL>
friend inline constexpr bool operator==(const BaseType (&lhs)[N2_WITH_NULL],
const String<N_WITH_NULL> &rhs) noexcept
{
return operator==(String<N2_WITH_NULL>(lhs), rhs);
}
template<size_t N2_WITH_NULL>
friend inline constexpr bool operator!=(const String<N_WITH_NULL> &lhs,
const BaseType (&rhs)[N2_WITH_NULL]) noexcept
{
return operator!=(lhs, String<N2_WITH_NULL>(rhs));
}
template<size_t N2_WITH_NULL>
friend inline constexpr bool operator!=(const BaseType (&lhs)[N2_WITH_NULL],
const String<N_WITH_NULL> &rhs) noexcept
{
return operator!=(String<N2_WITH_NULL>(lhs), rhs);
}
template<size_t N2_WITH_NULL>
friend inline constexpr auto operator+(const String<N_WITH_NULL> &lhs,
const String<N2_WITH_NULL> &rhs) noexcept
{
char data[N_WITH_NULL + N2_WITH_NULL - 1] = {};
for (size_t i = 0; i < N_WITH_NULL - 1; ++i)
data[i] = lhs[i];
for (size_t i = 0; i < N2_WITH_NULL - 1; ++i)
data[N_WITH_NULL - 1 + i] = rhs[i];
return String<N_WITH_NULL + N2_WITH_NULL - 1>(data);
}
};
// Helper types that allow us to disable variadic overloads that would conflict
// with overloads that take a const char*.
template<typename T, size_t N = 0> struct IsStringType : std::false_type {};
template<> struct IsStringType<const char*, 0> : std::true_type {};
template<size_t N> struct IsStringType<String<N>> : std::true_type {};
template<size_t N> struct IsStringType<const char[N]> : std::true_type {};
template<bool flag = false>
static void staticAssertTypeMismatch()
{
static_assert(flag, "The used type is not supported by this template call. "
"Use a JNI based type instead.");
}
template<typename T>
constexpr auto typeSignature()
{
if constexpr (std::is_array_v<T>) {
using UnderlyingType = typename std::remove_extent_t<T>;
static_assert(!std::is_array_v<UnderlyingType>,
"typeSignature() does not handle multi-dimensional arrays");
return String("[") + typeSignature<UnderlyingType>();
} else if constexpr (std::is_same_v<T, jobject>) {
return String("Ljava/lang/Object;");
} else if constexpr (std::is_same_v<T, jclass>) {
return String("Ljava/lang/Class;");
} else if constexpr (std::is_same_v<T, jstring>) {
return String("Ljava/lang/String;");
} else if constexpr (std::is_same_v<T, jobjectArray>) {
return String("[Ljava/lang/Object;");
} else if constexpr (std::is_same_v<T, jthrowable>) {
return String("Ljava/lang/Throwable;");
} else if constexpr (std::is_same_v<T, jbooleanArray>) {
return String("[Z");
} else if constexpr (std::is_same_v<T, jbyteArray>) {
return String("[B");
} else if constexpr (std::is_same_v<T, jshortArray>) {
return String("[S");
} else if constexpr (std::is_same_v<T, jintArray>) {
return String("[I");
} else if constexpr (std::is_same_v<T, jlongArray>) {
return String("[J");
} else if constexpr (std::is_same_v<T, jfloatArray>) {
return String("[F");
} else if constexpr (std::is_same_v<T, jdoubleArray>) {
return String("[D");
} else if constexpr (std::is_same_v<T, jcharArray>) {
return String("[C");
} else if constexpr (std::is_same_v<T, jboolean>) {
return String("Z");
} else if constexpr (std::is_same_v<T, bool>) {
return String("Z");
} else if constexpr (std::is_same_v<T, jbyte>) {
return String("B");
} else if constexpr (std::is_same_v<T, jchar>) {
return String("C");
} else if constexpr (std::is_same_v<T, char>) {
return String("C");
} else if constexpr (std::is_same_v<T, jshort>) {
return String("S");
} else if constexpr (std::is_same_v<T, short>) {
return String("S");
} else if constexpr (std::is_same_v<T, jint>) {
return String("I");
} else if constexpr (std::is_same_v<T, int>) {
return String("I");
} else if constexpr (std::is_same_v<T, uint>) {
return String("I");
} else if constexpr (std::is_same_v<T, jlong>) {
return String("J");
} else if constexpr (std::is_same_v<T, long>) {
return String("J");
} else if constexpr (std::is_same_v<T, jfloat>) {
return String("F");
} else if constexpr (std::is_same_v<T, float>) {
return String("F");
} else if constexpr (std::is_same_v<T, jdouble>) {
return String("D");
} else if constexpr (std::is_same_v<T, double>) {
return String("D");
} else if constexpr (std::is_same_v<T, void>) {
return String("V");
}
// else: The return type becomes void, indicating that the typeSignature
// template is not implemented for the respective type. We use this to
// detect invalid types in the ValidSignatureTypes and ValidFieldType
// predicates below.
}
template<bool flag = false>
static void staticAssertClassNotRegistered()
{
static_assert(flag, "Class not registered, use Q_DECLARE_JNI_CLASS");
}
template<typename T>
constexpr auto className()
{
if constexpr (std::is_same_v<T, jstring>)
return String("java/lang/String");
else
staticAssertClassNotRegistered();
}
template<typename T>
static constexpr bool isPrimitiveType()
{
return typeSignature<T>().size() == 2;
}
template<typename T>
static constexpr bool isObjectType()
{
if constexpr (std::is_convertible_v<T, jobject>) {
return true;
} else {
constexpr auto signature = typeSignature<T>();
return (signature.startsWith('L') || signature.startsWith('['))
&& signature.endsWith(';');
}
}
template<typename T>
static constexpr bool isArrayType()
{
constexpr auto signature = typeSignature<T>();
return signature.startsWith('[');
}
template<typename T>
static constexpr void assertObjectType()
{
static_assert(isObjectType<T>(),
"Type needs to be a JNI object type (convertible to jobject, or with "
"an object type signature registered)!");
}
// A set of types is valid if typeSignature is implemented for all of them
template<typename ...Types>
constexpr bool ValidSignatureTypesDetail = !std::disjunction<std::is_same<
decltype(QtJniTypes::typeSignature<Types>()),
void>...,
IsStringType<Types>...>::value;
template<typename ...Types>
using ValidSignatureTypes = std::enable_if_t<
ValidSignatureTypesDetail<q20::remove_cvref_t<Types>...>, bool>;
template<typename Type>
constexpr bool ValidFieldTypeDetail = isObjectType<Type>() || isPrimitiveType<Type>();
template<typename Type>
using ValidFieldType = std::enable_if_t<
ValidFieldTypeDetail<q20::remove_cvref_t<Type>>, bool>;
template<typename R, typename ...Args, ValidSignatureTypes<R, Args...> = true>
static constexpr auto methodSignature()
{
return (String("(") +
... + typeSignature<q20::remove_cvref_t<Args>>())
+ String(")")
+ typeSignature<R>();
}
template<typename T, ValidSignatureTypes<T> = true>
static constexpr auto fieldSignature()
{
return QtJniTypes::typeSignature<T>();
}
template<typename ...Args, ValidSignatureTypes<Args...> = true>
static constexpr auto constructorSignature()
{
return methodSignature<void, Args...>();
}
template<typename Ret, typename ...Args, ValidSignatureTypes<Ret, Args...> = true>
static constexpr auto nativeMethodSignature(Ret (*)(JNIEnv *, jobject, Args...))
{
return methodSignature<Ret, Args...>();
}
template<typename Ret, typename ...Args, ValidSignatureTypes<Ret, Args...> = true>
static constexpr auto nativeMethodSignature(Ret (*)(JNIEnv *, jclass, Args...))
{
return methodSignature<Ret, Args...>();
}
} // namespace QtJniTypes
QT_END_NAMESPACE
#endif
#endif // QJNITYPES_IMPL_H