refactor(android): move djinni jni files to thrid_party
This commit is contained in:
parent
d6ff3470f1
commit
5eb3174edb
|
@ -8,11 +8,17 @@ cmake_minimum_required(VERSION 3.4.1)
|
||||||
get_filename_component(MYNTETE_ROOT "${PROJECT_SOURCE_DIR}/../../../.." ABSOLUTE)
|
get_filename_component(MYNTETE_ROOT "${PROJECT_SOURCE_DIR}/../../../.." ABSOLUTE)
|
||||||
message(STATUS "MYNTETE_ROOT: ${MYNTETE_ROOT}")
|
message(STATUS "MYNTETE_ROOT: ${MYNTETE_ROOT}")
|
||||||
|
|
||||||
|
get_filename_component(PRO_ROOT "${PROJECT_SOURCE_DIR}/.." ABSOLUTE)
|
||||||
|
message(STATUS "PRO_ROOT: ${PRO_ROOT}")
|
||||||
|
|
||||||
|
set(LIB_ROOT "${PROJECT_SOURCE_DIR}")
|
||||||
|
message(STATUS "LIB_ROOT: ${LIB_ROOT}")
|
||||||
|
|
||||||
if(NOT DJINNI_DIR)
|
if(NOT DJINNI_DIR)
|
||||||
if(DEFINED ENV{DJINNI_DIR})
|
if(DEFINED ENV{DJINNI_DIR})
|
||||||
set(DJINNI_DIR $ENV{DJINNI_DIR})
|
set(DJINNI_DIR $ENV{DJINNI_DIR})
|
||||||
else()
|
else()
|
||||||
set(DJINNI_DIR "$ENV{HOME}/Workspace/Fever/Dropbox/djinni")
|
set(DJINNI_DIR "${PRO_ROOT}/third_party/djinni")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
|
33
wrappers/android/mynteye/third_party/djinni/support-lib/djinni_common.hpp
vendored
Normal file
33
wrappers/android/mynteye/third_party/djinni/support-lib/djinni_common.hpp
vendored
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
//
|
||||||
|
// Copyright 2015 Dropbox, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#define DJINNI_WEAK_DEFINITION // weak attribute not supported by MSVC
|
||||||
|
#define DJINNI_NORETURN_DEFINITION __declspec(noreturn)
|
||||||
|
#if _MSC_VER < 1900 // snprintf not implemented prior to VS2015
|
||||||
|
#define DJINNI_SNPRINTF snprintf
|
||||||
|
#define noexcept _NOEXCEPT // work-around for missing noexcept VS2015
|
||||||
|
#define constexpr // work-around for missing constexpr VS2015
|
||||||
|
#else
|
||||||
|
#define DJINNI_SNPRINTF _snprintf
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
#define DJINNI_WEAK_DEFINITION __attribute__((weak))
|
||||||
|
#define DJINNI_NORETURN_DEFINITION __attribute__((noreturn))
|
||||||
|
#define DJINNI_SNPRINTF snprintf
|
||||||
|
#endif
|
536
wrappers/android/mynteye/third_party/djinni/support-lib/jni/Marshal.hpp
vendored
Normal file
536
wrappers/android/mynteye/third_party/djinni/support-lib/jni/Marshal.hpp
vendored
Normal file
|
@ -0,0 +1,536 @@
|
||||||
|
//
|
||||||
|
// Copyright 2014 Dropbox, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "djinni_support.hpp"
|
||||||
|
#include <cassert>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace djinni
|
||||||
|
{
|
||||||
|
template<class Self, class CppT, class JniT>
|
||||||
|
class Primitive
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using CppType = CppT;
|
||||||
|
using JniType = JniT;
|
||||||
|
|
||||||
|
static CppType toCpp(JNIEnv* /*jniEnv*/, JniType j) noexcept { return static_cast<CppType>(j); }
|
||||||
|
static JniType fromCpp(JNIEnv* /*jniEnv*/, CppType c) noexcept { return static_cast<JniType>(c); }
|
||||||
|
|
||||||
|
struct Boxed
|
||||||
|
{
|
||||||
|
using JniType = jobject;
|
||||||
|
static CppType toCpp(JNIEnv* jniEnv, JniType j)
|
||||||
|
{
|
||||||
|
assert(j != nullptr);
|
||||||
|
const auto& data = JniClass<Self>::get();
|
||||||
|
assert(jniEnv->IsInstanceOf(j, data.clazz.get()));
|
||||||
|
auto ret = Primitive::toCpp(jniEnv, Self::unbox(jniEnv, data.method_unbox, j));
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
static LocalRef<JniType> fromCpp(JNIEnv* jniEnv, CppType c)
|
||||||
|
{
|
||||||
|
const auto& data = JniClass<Self>::get();
|
||||||
|
auto ret = jniEnv->CallStaticObjectMethod(data.clazz.get(), data.method_box, Primitive::fromCpp(jniEnv, c));
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
return {jniEnv, ret};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Primitive(const char* javaClassSpec,
|
||||||
|
const char* staticBoxMethod,
|
||||||
|
const char* staticBoxMethodSignature,
|
||||||
|
const char* unboxMethod,
|
||||||
|
const char* unboxMethodSignature)
|
||||||
|
: clazz(jniFindClass(javaClassSpec))
|
||||||
|
, method_box(jniGetStaticMethodID(clazz.get(), staticBoxMethod, staticBoxMethodSignature))
|
||||||
|
, method_unbox(jniGetMethodID(clazz.get(), unboxMethod, unboxMethodSignature))
|
||||||
|
{}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const GlobalRef<jclass> clazz;
|
||||||
|
const jmethodID method_box;
|
||||||
|
const jmethodID method_unbox;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Bool : public Primitive<Bool, bool, jboolean>
|
||||||
|
{
|
||||||
|
Bool() : Primitive("java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", "booleanValue", "()Z") {}
|
||||||
|
friend JniClass<Bool>;
|
||||||
|
friend Primitive<Bool, bool, jboolean>;
|
||||||
|
static JniType unbox(JNIEnv* jniEnv, jmethodID method, jobject j) {
|
||||||
|
auto result = jniEnv->CallBooleanMethod(j, method);
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class I8 : public Primitive<I8, int8_t, jbyte>
|
||||||
|
{
|
||||||
|
I8() : Primitive("java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", "byteValue", "()B") {}
|
||||||
|
friend JniClass<I8>;
|
||||||
|
friend Primitive<I8, int8_t, jbyte>;
|
||||||
|
static JniType unbox(JNIEnv* jniEnv, jmethodID method, jobject j) {
|
||||||
|
auto result = jniEnv->CallByteMethod(j, method);
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class I16 : public Primitive<I16, int16_t, jshort>
|
||||||
|
{
|
||||||
|
I16() : Primitive("java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", "shortValue", "()S") {}
|
||||||
|
friend JniClass<I16>;
|
||||||
|
friend Primitive<I16, int16_t, jshort>;
|
||||||
|
static JniType unbox(JNIEnv* jniEnv, jmethodID method, jobject j) {
|
||||||
|
auto result = jniEnv->CallShortMethod(j, method);
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class I32 : public Primitive<I32, int32_t, jint>
|
||||||
|
{
|
||||||
|
I32() : Primitive("java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", "intValue", "()I") {}
|
||||||
|
friend JniClass<I32>;
|
||||||
|
friend Primitive<I32, int32_t, jint>;
|
||||||
|
static JniType unbox(JNIEnv* jniEnv, jmethodID method, jobject j) {
|
||||||
|
auto result = jniEnv->CallIntMethod(j, method);
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class I64 : public Primitive<I64, int64_t, jlong>
|
||||||
|
{
|
||||||
|
I64() : Primitive("java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", "longValue", "()J") {}
|
||||||
|
friend JniClass<I64>;
|
||||||
|
friend Primitive<I64, int64_t, jlong>;
|
||||||
|
static JniType unbox(JNIEnv* jniEnv, jmethodID method, jobject j) {
|
||||||
|
auto result = jniEnv->CallLongMethod(j, method);
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class F32 : public Primitive<F32, float, jfloat>
|
||||||
|
{
|
||||||
|
F32() : Primitive("java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", "floatValue", "()F") {}
|
||||||
|
friend JniClass<F32>;
|
||||||
|
friend Primitive<F32, float, jfloat>;
|
||||||
|
static JniType unbox(JNIEnv* jniEnv, jmethodID method, jobject j) {
|
||||||
|
auto result = jniEnv->CallFloatMethod(j, method);
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class F64 : public Primitive<F64, double, jdouble>
|
||||||
|
{
|
||||||
|
F64() : Primitive("java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", "doubleValue", "()D") {}
|
||||||
|
friend JniClass<F64>;
|
||||||
|
friend Primitive<F64, double, jdouble>;
|
||||||
|
static JniType unbox(JNIEnv* jniEnv, jmethodID method, jobject j) {
|
||||||
|
auto result = jniEnv->CallDoubleMethod(j, method);
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct String
|
||||||
|
{
|
||||||
|
using CppType = std::string;
|
||||||
|
using JniType = jstring;
|
||||||
|
|
||||||
|
using Boxed = String;
|
||||||
|
|
||||||
|
static CppType toCpp(JNIEnv* jniEnv, JniType j)
|
||||||
|
{
|
||||||
|
assert(j != nullptr);
|
||||||
|
return jniUTF8FromString(jniEnv, j);
|
||||||
|
}
|
||||||
|
|
||||||
|
static LocalRef<JniType> fromCpp(JNIEnv* jniEnv, const CppType& c)
|
||||||
|
{
|
||||||
|
return {jniEnv, jniStringFromUTF8(jniEnv, c)};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WString
|
||||||
|
{
|
||||||
|
using CppType = std::wstring;
|
||||||
|
using JniType = jstring;
|
||||||
|
|
||||||
|
using Boxed = WString;
|
||||||
|
|
||||||
|
static CppType toCpp(JNIEnv* jniEnv, JniType j)
|
||||||
|
{
|
||||||
|
assert(j != nullptr);
|
||||||
|
return jniWStringFromString(jniEnv, j);
|
||||||
|
}
|
||||||
|
|
||||||
|
static LocalRef<JniType> fromCpp(JNIEnv* jniEnv, const CppType& c)
|
||||||
|
{
|
||||||
|
return {jniEnv, jniStringFromWString(jniEnv, c)};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Binary
|
||||||
|
{
|
||||||
|
using CppType = std::vector<uint8_t>;
|
||||||
|
using JniType = jbyteArray;
|
||||||
|
|
||||||
|
using Boxed = Binary;
|
||||||
|
|
||||||
|
static CppType toCpp(JNIEnv* jniEnv, JniType j)
|
||||||
|
{
|
||||||
|
assert(j != nullptr);
|
||||||
|
|
||||||
|
std::vector<uint8_t> ret;
|
||||||
|
jsize length = jniEnv->GetArrayLength(j);
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
|
||||||
|
if (!length) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto deleter = [jniEnv, j] (void* c) {
|
||||||
|
if (c) {
|
||||||
|
jniEnv->ReleasePrimitiveArrayCritical(j, c, JNI_ABORT);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<uint8_t, decltype(deleter)> ptr(
|
||||||
|
reinterpret_cast<uint8_t*>(jniEnv->GetPrimitiveArrayCritical(j, nullptr)),
|
||||||
|
deleter
|
||||||
|
);
|
||||||
|
|
||||||
|
if (ptr) {
|
||||||
|
// Construct and then move-assign. This copies the elements only once,
|
||||||
|
// and avoids having to initialize before filling (as with resize())
|
||||||
|
ret = std::vector<uint8_t>{ptr.get(), ptr.get() + length};
|
||||||
|
} else {
|
||||||
|
// Something failed...
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LocalRef<JniType> fromCpp(JNIEnv* jniEnv, const CppType& c)
|
||||||
|
{
|
||||||
|
assert(c.size() <= std::numeric_limits<jsize>::max());
|
||||||
|
auto j = LocalRef<jbyteArray>(jniEnv, jniEnv->NewByteArray(static_cast<jsize>(c.size())));
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
// Using .data() on an empty vector is UB
|
||||||
|
if(!c.empty())
|
||||||
|
{
|
||||||
|
jniEnv->SetByteArrayRegion(j.get(), 0, jsize(c.size()), reinterpret_cast<const jbyte*>(c.data()));
|
||||||
|
}
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Date
|
||||||
|
{
|
||||||
|
using CppType = std::chrono::system_clock::time_point;
|
||||||
|
using JniType = jobject;
|
||||||
|
|
||||||
|
using Boxed = Date;
|
||||||
|
|
||||||
|
static CppType toCpp(JNIEnv* jniEnv, JniType j)
|
||||||
|
{
|
||||||
|
static const auto POSIX_EPOCH = std::chrono::system_clock::from_time_t(0);
|
||||||
|
assert(j != nullptr);
|
||||||
|
const auto & data = JniClass<Date>::get();
|
||||||
|
assert(jniEnv->IsInstanceOf(j, data.clazz.get()));
|
||||||
|
auto time_millis = jniEnv->CallLongMethod(j, data.method_get_time);
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
return POSIX_EPOCH + std::chrono::milliseconds{time_millis};
|
||||||
|
}
|
||||||
|
|
||||||
|
static LocalRef<JniType> fromCpp(JNIEnv* jniEnv, const CppType& c)
|
||||||
|
{
|
||||||
|
static const auto POSIX_EPOCH = std::chrono::system_clock::from_time_t(0);
|
||||||
|
const auto & data = JniClass<Date>::get();
|
||||||
|
const auto cpp_millis = std::chrono::duration_cast<std::chrono::milliseconds>(c - POSIX_EPOCH);
|
||||||
|
const jlong millis = static_cast<jlong>(cpp_millis.count());
|
||||||
|
auto j = LocalRef<jobject>(jniEnv, jniEnv->NewObject(data.clazz.get(), data.constructor, millis));
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Date() = default;
|
||||||
|
friend ::djinni::JniClass<Date>;
|
||||||
|
|
||||||
|
const GlobalRef<jclass> clazz { jniFindClass("java/util/Date") };
|
||||||
|
const jmethodID constructor { jniGetMethodID(clazz.get(), "<init>", "(J)V") };
|
||||||
|
const jmethodID method_get_time { jniGetMethodID(clazz.get(), "getTime", "()J") };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <template <class> class OptionalType, class T>
|
||||||
|
struct Optional
|
||||||
|
{
|
||||||
|
// SFINAE helper: if C::CppOptType exists, opt_type<T>(nullptr) will return
|
||||||
|
// that type. If not, it returns OptionalType<C::CppType>. This is necessary
|
||||||
|
// because we special-case optional interfaces to be represented as a nullable
|
||||||
|
// std::shared_ptr<T>, not optional<shared_ptr<T>> or optional<nn<shared_ptr<T>>>.
|
||||||
|
template <typename C> static OptionalType<typename C::CppType> opt_type(...);
|
||||||
|
template <typename C> static typename C::CppOptType opt_type(typename C::CppOptType *);
|
||||||
|
using CppType = decltype(opt_type<T>(nullptr));
|
||||||
|
|
||||||
|
using JniType = typename T::Boxed::JniType;
|
||||||
|
|
||||||
|
using Boxed = Optional;
|
||||||
|
|
||||||
|
static CppType toCpp(JNIEnv* jniEnv, JniType j)
|
||||||
|
{
|
||||||
|
if (j) {
|
||||||
|
return T::Boxed::toCpp(jniEnv, j);
|
||||||
|
} else {
|
||||||
|
return CppType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static LocalRef<JniType> fromCpp(JNIEnv* jniEnv, const OptionalType<typename T::CppType> &c)
|
||||||
|
{
|
||||||
|
return c ? T::Boxed::fromCpp(jniEnv, *c) : LocalRef<JniType>{};
|
||||||
|
}
|
||||||
|
|
||||||
|
// fromCpp used for nullable shared_ptr
|
||||||
|
template <typename C = T>
|
||||||
|
static LocalRef<JniType> fromCpp(JNIEnv* jniEnv, const typename C::CppOptType & cppOpt) {
|
||||||
|
return T::Boxed::fromCppOpt(jniEnv, cppOpt);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ListJniInfo
|
||||||
|
{
|
||||||
|
const GlobalRef<jclass> clazz { jniFindClass("java/util/ArrayList") };
|
||||||
|
const jmethodID constructor { jniGetMethodID(clazz.get(), "<init>", "(I)V") };
|
||||||
|
const jmethodID method_add { jniGetMethodID(clazz.get(), "add", "(Ljava/lang/Object;)Z") };
|
||||||
|
const jmethodID method_get { jniGetMethodID(clazz.get(), "get", "(I)Ljava/lang/Object;") };
|
||||||
|
const jmethodID method_size { jniGetMethodID(clazz.get(), "size", "()I") };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
class List
|
||||||
|
{
|
||||||
|
using ECppType = typename T::CppType;
|
||||||
|
using EJniType = typename T::Boxed::JniType;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using CppType = std::vector<ECppType>;
|
||||||
|
using JniType = jobject;
|
||||||
|
|
||||||
|
using Boxed = List;
|
||||||
|
|
||||||
|
static CppType toCpp(JNIEnv* jniEnv, JniType j)
|
||||||
|
{
|
||||||
|
assert(j != nullptr);
|
||||||
|
const auto& data = JniClass<ListJniInfo>::get();
|
||||||
|
assert(jniEnv->IsInstanceOf(j, data.clazz.get()));
|
||||||
|
auto size = jniEnv->CallIntMethod(j, data.method_size);
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
auto c = CppType();
|
||||||
|
c.reserve(size);
|
||||||
|
for(jint i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
auto je = LocalRef<jobject>(jniEnv, jniEnv->CallObjectMethod(j, data.method_get, i));
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
c.push_back(T::Boxed::toCpp(jniEnv, static_cast<EJniType>(je.get())));
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LocalRef<JniType> fromCpp(JNIEnv* jniEnv, const CppType& c)
|
||||||
|
{
|
||||||
|
const auto& data = JniClass<ListJniInfo>::get();
|
||||||
|
assert(c.size() <= std::numeric_limits<jint>::max());
|
||||||
|
auto size = static_cast<jint>(c.size());
|
||||||
|
auto j = LocalRef<jobject>(jniEnv, jniEnv->NewObject(data.clazz.get(), data.constructor, size));
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
for(const auto& ce : c)
|
||||||
|
{
|
||||||
|
auto je = T::Boxed::fromCpp(jniEnv, ce);
|
||||||
|
jniEnv->CallBooleanMethod(j, data.method_add, get(je));
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
}
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IteratorJniInfo
|
||||||
|
{
|
||||||
|
const GlobalRef<jclass> clazz { jniFindClass("java/util/Iterator") };
|
||||||
|
const jmethodID method_next { jniGetMethodID(clazz.get(), "next", "()Ljava/lang/Object;") };
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SetJniInfo
|
||||||
|
{
|
||||||
|
const GlobalRef<jclass> clazz { jniFindClass("java/util/HashSet") };
|
||||||
|
const jmethodID constructor { jniGetMethodID(clazz.get(), "<init>", "()V") };
|
||||||
|
const jmethodID method_add { jniGetMethodID(clazz.get(), "add", "(Ljava/lang/Object;)Z") };
|
||||||
|
const jmethodID method_size { jniGetMethodID(clazz.get(), "size", "()I") };
|
||||||
|
const jmethodID method_iterator { jniGetMethodID(clazz.get(), "iterator", "()Ljava/util/Iterator;") };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
class Set
|
||||||
|
{
|
||||||
|
using ECppType = typename T::CppType;
|
||||||
|
using EJniType = typename T::Boxed::JniType;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using CppType = std::unordered_set<ECppType>;
|
||||||
|
using JniType = jobject;
|
||||||
|
|
||||||
|
using Boxed = Set;
|
||||||
|
|
||||||
|
static CppType toCpp(JNIEnv* jniEnv, JniType j)
|
||||||
|
{
|
||||||
|
assert(j != nullptr);
|
||||||
|
const auto& data = JniClass<SetJniInfo>::get();
|
||||||
|
const auto& iteData = JniClass<IteratorJniInfo>::get();
|
||||||
|
assert(jniEnv->IsInstanceOf(j, data.clazz.get()));
|
||||||
|
auto size = jniEnv->CallIntMethod(j, data.method_size);
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
auto c = CppType();
|
||||||
|
auto it = LocalRef<jobject>(jniEnv, jniEnv->CallObjectMethod(j, data.method_iterator));
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
for(jint i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
auto je = LocalRef<jobject>(jniEnv, jniEnv->CallObjectMethod(it, iteData.method_next));
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
c.insert(T::Boxed::toCpp(jniEnv, static_cast<EJniType>(je.get())));
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LocalRef<JniType> fromCpp(JNIEnv* jniEnv, const CppType& c)
|
||||||
|
{
|
||||||
|
const auto& data = JniClass<SetJniInfo>::get();
|
||||||
|
assert(c.size() <= std::numeric_limits<jint>::max());
|
||||||
|
auto size = static_cast<jint>(c.size());
|
||||||
|
auto j = LocalRef<jobject>(jniEnv, jniEnv->NewObject(data.clazz.get(), data.constructor, size));
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
for(const auto& ce : c)
|
||||||
|
{
|
||||||
|
auto je = T::Boxed::fromCpp(jniEnv, ce);
|
||||||
|
jniEnv->CallBooleanMethod(j, data.method_add, get(je));
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
}
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MapJniInfo
|
||||||
|
{
|
||||||
|
const GlobalRef<jclass> clazz { jniFindClass("java/util/HashMap") };
|
||||||
|
const jmethodID constructor { jniGetMethodID(clazz.get(), "<init>", "()V") };
|
||||||
|
const jmethodID method_put { jniGetMethodID(clazz.get(), "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;") };
|
||||||
|
const jmethodID method_size { jniGetMethodID(clazz.get(), "size", "()I") };
|
||||||
|
const jmethodID method_entrySet { jniGetMethodID(clazz.get(), "entrySet", "()Ljava/util/Set;") };
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EntrySetJniInfo
|
||||||
|
{
|
||||||
|
const GlobalRef<jclass> clazz { jniFindClass("java/util/Set") };
|
||||||
|
const jmethodID method_iterator { jniGetMethodID(clazz.get(), "iterator", "()Ljava/util/Iterator;") };
|
||||||
|
};
|
||||||
|
|
||||||
|
struct EntryJniInfo
|
||||||
|
{
|
||||||
|
const GlobalRef<jclass> clazz { jniFindClass("java/util/Map$Entry") };
|
||||||
|
const jmethodID method_getKey { jniGetMethodID(clazz.get(), "getKey", "()Ljava/lang/Object;") };
|
||||||
|
const jmethodID method_getValue { jniGetMethodID(clazz.get(), "getValue", "()Ljava/lang/Object;") };
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class Key, class Value>
|
||||||
|
class Map
|
||||||
|
{
|
||||||
|
using CppKeyType = typename Key::CppType;
|
||||||
|
using CppValueType = typename Value::CppType;
|
||||||
|
using JniKeyType = typename Key::Boxed::JniType;
|
||||||
|
using JniValueType = typename Value::Boxed::JniType;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using CppType = std::unordered_map<CppKeyType, CppValueType>;
|
||||||
|
using JniType = jobject;
|
||||||
|
|
||||||
|
using Boxed = Map;
|
||||||
|
|
||||||
|
static CppType toCpp(JNIEnv* jniEnv, JniType j)
|
||||||
|
{
|
||||||
|
assert(j != nullptr);
|
||||||
|
const auto& data = JniClass<MapJniInfo>::get();
|
||||||
|
const auto& entrySetData = JniClass<EntrySetJniInfo>::get();
|
||||||
|
const auto& entryData = JniClass<EntryJniInfo>::get();
|
||||||
|
const auto& iteData = JniClass<IteratorJniInfo>::get();
|
||||||
|
assert(jniEnv->IsInstanceOf(j, data.clazz.get()));
|
||||||
|
auto size = jniEnv->CallIntMethod(j, data.method_size);
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
auto entrySet = LocalRef<jobject>(jniEnv, jniEnv->CallObjectMethod(j, data.method_entrySet));
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
auto c = CppType();
|
||||||
|
c.reserve(size);
|
||||||
|
auto it = LocalRef<jobject>(jniEnv, jniEnv->CallObjectMethod(entrySet, entrySetData.method_iterator));
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
for(jint i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
auto je = LocalRef<jobject>(jniEnv, jniEnv->CallObjectMethod(it, iteData.method_next));
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
auto jKey = LocalRef<jobject>(jniEnv, jniEnv->CallObjectMethod(je, entryData.method_getKey));
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
auto jValue = LocalRef<jobject>(jniEnv, jniEnv->CallObjectMethod(je, entryData.method_getValue));
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
c.emplace(Key::Boxed::toCpp(jniEnv, static_cast<JniKeyType>(jKey.get())),
|
||||||
|
Value::Boxed::toCpp(jniEnv, static_cast<JniValueType>(jValue.get())));
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
static LocalRef<JniType> fromCpp(JNIEnv* jniEnv, const CppType& c)
|
||||||
|
{
|
||||||
|
const auto& data = JniClass<MapJniInfo>::get();
|
||||||
|
assert(c.size() <= std::numeric_limits<jint>::max());
|
||||||
|
auto size = c.size();
|
||||||
|
auto j = LocalRef<jobject>(jniEnv, jniEnv->NewObject(data.clazz.get(), data.constructor, size));
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
for(const auto& ce : c)
|
||||||
|
{
|
||||||
|
auto jKey = Key::Boxed::fromCpp(jniEnv, ce.first);
|
||||||
|
auto jValue = Value::Boxed::fromCpp(jniEnv, ce.second);
|
||||||
|
jniEnv->CallObjectMethod(j, data.method_put, get(jKey), get(jValue));
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
}
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace djinni
|
31
wrappers/android/mynteye/third_party/djinni/support-lib/jni/djinni_main.cpp
vendored
Normal file
31
wrappers/android/mynteye/third_party/djinni/support-lib/jni/djinni_main.cpp
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
//
|
||||||
|
// Copyright 2014 Dropbox, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
// This provides a minimal JNI_OnLoad and JNI_OnUnload implementation - include it if your
|
||||||
|
// app doesn't use JNI except through Djinni.
|
||||||
|
|
||||||
|
#include "djinni_support.hpp"
|
||||||
|
|
||||||
|
// Called when library is loaded by the first class which uses it.
|
||||||
|
CJNIEXPORT jint JNICALL JNI_OnLoad(JavaVM * jvm, void * /*reserved*/) {
|
||||||
|
djinni::jniInit(jvm);
|
||||||
|
return JNI_VERSION_1_6;
|
||||||
|
}
|
||||||
|
|
||||||
|
// (Potentially) called when library is about to be unloaded.
|
||||||
|
CJNIEXPORT void JNICALL JNI_OnUnload(JavaVM * /*jvm*/, void * /*reserved*/) {
|
||||||
|
djinni::jniShutdown();
|
||||||
|
}
|
686
wrappers/android/mynteye/third_party/djinni/support-lib/jni/djinni_support.cpp
vendored
Normal file
686
wrappers/android/mynteye/third_party/djinni/support-lib/jni/djinni_support.cpp
vendored
Normal file
|
@ -0,0 +1,686 @@
|
||||||
|
//
|
||||||
|
// Copyright 2014 Dropbox, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "../djinni_common.hpp"
|
||||||
|
#include "djinni_support.hpp"
|
||||||
|
#include "../proxy_cache_impl.hpp"
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
static_assert(sizeof(jlong) >= sizeof(void*), "must be able to fit a void* into a jlong");
|
||||||
|
|
||||||
|
namespace djinni {
|
||||||
|
|
||||||
|
// Set only once from JNI_OnLoad before any other JNI calls, so no lock needed.
|
||||||
|
static JavaVM * g_cachedJVM;
|
||||||
|
|
||||||
|
/*static*/
|
||||||
|
JniClassInitializer::registration_vec & JniClassInitializer::get_vec() {
|
||||||
|
static JniClassInitializer::registration_vec m;
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*static*/
|
||||||
|
std::mutex & JniClassInitializer::get_mutex() {
|
||||||
|
static std::mutex mtx;
|
||||||
|
return mtx;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*static*/
|
||||||
|
JniClassInitializer::registration_vec JniClassInitializer::get_all() {
|
||||||
|
const std::lock_guard<std::mutex> lock(get_mutex());
|
||||||
|
return get_vec();
|
||||||
|
}
|
||||||
|
|
||||||
|
JniClassInitializer::JniClassInitializer(std::function<void()> init) {
|
||||||
|
const std::lock_guard<std::mutex> lock(get_mutex());
|
||||||
|
get_vec().push_back(std::move(init));
|
||||||
|
}
|
||||||
|
|
||||||
|
void jniInit(JavaVM * jvm) {
|
||||||
|
g_cachedJVM = jvm;
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const auto & initializer : JniClassInitializer::get_all()) {
|
||||||
|
initializer();
|
||||||
|
}
|
||||||
|
} catch (const std::exception &) {
|
||||||
|
// Default exception handling only, since non-default might not be safe if init
|
||||||
|
// is incomplete.
|
||||||
|
jniDefaultSetPendingFromCurrent(jniGetThreadEnv(), __func__);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void jniShutdown() {
|
||||||
|
g_cachedJVM = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEnv * jniGetThreadEnv() {
|
||||||
|
assert(g_cachedJVM);
|
||||||
|
JNIEnv * env = nullptr;
|
||||||
|
jint get_res = g_cachedJVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
|
||||||
|
#ifdef EXPERIMENTAL_AUTO_CPP_THREAD_ATTACH
|
||||||
|
if (get_res == JNI_EDETACHED) {
|
||||||
|
get_res = g_cachedJVM->AttachCurrentThread(&env, nullptr);
|
||||||
|
thread_local struct DetachOnExit {
|
||||||
|
~DetachOnExit() {
|
||||||
|
g_cachedJVM->DetachCurrentThread();
|
||||||
|
}
|
||||||
|
} detachOnExit;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (get_res != 0 || !env) {
|
||||||
|
// :(
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
static JNIEnv * getOptThreadEnv() {
|
||||||
|
if (!g_cachedJVM) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case: this allows us to ignore GlobalRef deletions that happen after this
|
||||||
|
// thread has been detached. (This is known to happen during process shutdown, when
|
||||||
|
// there's no need to release the ref anyway.)
|
||||||
|
JNIEnv * env = nullptr;
|
||||||
|
const jint get_res = g_cachedJVM->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
|
||||||
|
|
||||||
|
if (get_res == JNI_EDETACHED) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Still bail on any other error.
|
||||||
|
if (get_res != 0 || !env) {
|
||||||
|
// :(
|
||||||
|
std::abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GlobalRefDeleter::operator() (jobject globalRef) noexcept {
|
||||||
|
if (globalRef) {
|
||||||
|
if (JNIEnv * env = getOptThreadEnv()) {
|
||||||
|
env->DeleteGlobalRef(globalRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocalRefDeleter::operator() (jobject localRef) noexcept {
|
||||||
|
if (localRef) {
|
||||||
|
jniGetThreadEnv()->DeleteLocalRef(localRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void jni_exception::set_as_pending(JNIEnv * env) const noexcept {
|
||||||
|
assert(env);
|
||||||
|
env->Throw(java_exception());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void jniExceptionCheck(JNIEnv * env) {
|
||||||
|
if (!env) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
const LocalRef<jthrowable> e(env->ExceptionOccurred());
|
||||||
|
if (e) {
|
||||||
|
env->ExceptionClear();
|
||||||
|
jniThrowCppFromJavaException(env, e.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DJINNI_WEAK_DEFINITION
|
||||||
|
DJINNI_NORETURN_DEFINITION
|
||||||
|
void jniThrowCppFromJavaException(JNIEnv * env, jthrowable java_exception) {
|
||||||
|
throw jni_exception { env, java_exception };
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace { // anonymous namespace to guard the struct below
|
||||||
|
struct SystemClassInfo {
|
||||||
|
// This is a singleton class - an instance will be constructed by
|
||||||
|
// JniClassInitializer::init_all() at library init time.
|
||||||
|
const GlobalRef<jclass> clazz { jniFindClass("java/lang/System") };
|
||||||
|
const jmethodID staticmethIdentityHashCode { jniGetStaticMethodID(clazz.get(),
|
||||||
|
"identityHashCode", "(Ljava/lang/Object;)I") };
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Hasher and comparator based on Java object identity.
|
||||||
|
*/
|
||||||
|
struct JavaIdentityHash { size_t operator() (jobject obj) const; };
|
||||||
|
struct JavaIdentityEquals { bool operator() (jobject obj1, jobject obj2) const; };
|
||||||
|
|
||||||
|
size_t JavaIdentityHash::operator() (jobject obj) const {
|
||||||
|
JNIEnv * const env = jniGetThreadEnv();
|
||||||
|
const SystemClassInfo & sys = JniClass<SystemClassInfo>::get();
|
||||||
|
jint res = env->CallStaticIntMethod(sys.clazz.get(), sys.staticmethIdentityHashCode, obj);
|
||||||
|
jniExceptionCheck(env);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
bool JavaIdentityEquals::operator() (jobject obj1, jobject obj2) const {
|
||||||
|
JNIEnv * const env = jniGetThreadEnv();
|
||||||
|
const bool res = env->IsSameObject(obj1, obj2);
|
||||||
|
jniExceptionCheck(env);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void jniThrowAssertionError(JNIEnv * env, const char * file, int line, const char * check) {
|
||||||
|
// basename() exists, but is bad (it's allowed to modify its input).
|
||||||
|
const char * slash = strrchr(file, '/');
|
||||||
|
const char * file_basename = slash ? slash + 1 : file;
|
||||||
|
|
||||||
|
char buf[256];
|
||||||
|
DJINNI_SNPRINTF(buf, sizeof buf, "djinni (%s:%d): %s", file_basename, line, check);
|
||||||
|
|
||||||
|
const jclass cassert = env->FindClass("java/lang/Error");
|
||||||
|
assert(cassert);
|
||||||
|
env->ThrowNew(cassert, buf);
|
||||||
|
assert(env->ExceptionCheck());
|
||||||
|
const jthrowable e = env->ExceptionOccurred();
|
||||||
|
assert(e);
|
||||||
|
env->ExceptionClear();
|
||||||
|
|
||||||
|
env->DeleteLocalRef(cassert);
|
||||||
|
|
||||||
|
jniThrowCppFromJavaException(env, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalRef<jclass> jniFindClass(const char * name) {
|
||||||
|
JNIEnv * env = jniGetThreadEnv();
|
||||||
|
DJINNI_ASSERT(name, env);
|
||||||
|
GlobalRef<jclass> guard(env, LocalRef<jclass>(env, env->FindClass(name)).get());
|
||||||
|
jniExceptionCheck(env);
|
||||||
|
if (!guard) {
|
||||||
|
jniThrowAssertionError(env, __FILE__, __LINE__, "FindClass returned null");
|
||||||
|
}
|
||||||
|
return guard;
|
||||||
|
}
|
||||||
|
|
||||||
|
jmethodID jniGetStaticMethodID(jclass clazz, const char * name, const char * sig) {
|
||||||
|
JNIEnv * env = jniGetThreadEnv();
|
||||||
|
DJINNI_ASSERT(clazz, env);
|
||||||
|
DJINNI_ASSERT(name, env);
|
||||||
|
DJINNI_ASSERT(sig, env);
|
||||||
|
jmethodID id = env->GetStaticMethodID(clazz, name, sig);
|
||||||
|
jniExceptionCheck(env);
|
||||||
|
if (!id) {
|
||||||
|
jniThrowAssertionError(env, __FILE__, __LINE__, "GetStaticMethodID returned null");
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
jmethodID jniGetMethodID(jclass clazz, const char * name, const char * sig) {
|
||||||
|
JNIEnv * env = jniGetThreadEnv();
|
||||||
|
DJINNI_ASSERT(clazz, env);
|
||||||
|
DJINNI_ASSERT(name, env);
|
||||||
|
DJINNI_ASSERT(sig, env);
|
||||||
|
jmethodID id = env->GetMethodID(clazz, name, sig);
|
||||||
|
jniExceptionCheck(env);
|
||||||
|
if (!id) {
|
||||||
|
jniThrowAssertionError(env, __FILE__, __LINE__, "GetMethodID returned null");
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
jfieldID jniGetFieldID(jclass clazz, const char * name, const char * sig) {
|
||||||
|
JNIEnv * env = jniGetThreadEnv();
|
||||||
|
DJINNI_ASSERT(clazz, env);
|
||||||
|
DJINNI_ASSERT(name, env);
|
||||||
|
DJINNI_ASSERT(sig, env);
|
||||||
|
jfieldID id = env->GetFieldID(clazz, name, sig);
|
||||||
|
jniExceptionCheck(env);
|
||||||
|
if (!id) {
|
||||||
|
jniThrowAssertionError(env, __FILE__, __LINE__, "GetFieldID returned null");
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
JniEnum::JniEnum(const std::string & name)
|
||||||
|
: m_clazz { jniFindClass(name.c_str()) },
|
||||||
|
m_staticmethValues { jniGetStaticMethodID(m_clazz.get(), "values", ("()[L" + name + ";").c_str()) },
|
||||||
|
m_methOrdinal { jniGetMethodID(m_clazz.get(), "ordinal", "()I") }
|
||||||
|
{}
|
||||||
|
|
||||||
|
jint JniEnum::ordinal(JNIEnv * env, jobject obj) const {
|
||||||
|
DJINNI_ASSERT(obj, env);
|
||||||
|
const jint res = env->CallIntMethod(obj, m_methOrdinal);
|
||||||
|
jniExceptionCheck(env);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalRef<jobject> JniEnum::create(JNIEnv * env, jint value) const {
|
||||||
|
LocalRef<jobject> values(env, env->CallStaticObjectMethod(m_clazz.get(), m_staticmethValues));
|
||||||
|
jniExceptionCheck(env);
|
||||||
|
DJINNI_ASSERT(values, env);
|
||||||
|
LocalRef<jobject> result(env,
|
||||||
|
env->GetObjectArrayElement(static_cast<jobjectArray>(values.get()),
|
||||||
|
value));
|
||||||
|
jniExceptionCheck(env);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
JniFlags::JniFlags(const std::string & name)
|
||||||
|
: JniEnum { name }
|
||||||
|
{}
|
||||||
|
|
||||||
|
unsigned JniFlags::flags(JNIEnv * env, jobject obj) const {
|
||||||
|
DJINNI_ASSERT(obj && env->IsInstanceOf(obj, m_clazz.get()), env);
|
||||||
|
auto size = env->CallIntMethod(obj, m_methSize);
|
||||||
|
jniExceptionCheck(env);
|
||||||
|
unsigned flags = 0;
|
||||||
|
auto it = LocalRef<jobject>(env, env->CallObjectMethod(obj, m_methIterator));
|
||||||
|
jniExceptionCheck(env);
|
||||||
|
for(jint i = 0; i < size; ++i) {
|
||||||
|
auto jf = LocalRef<jobject>(env, env->CallObjectMethod(it, m_iterator.methNext));
|
||||||
|
jniExceptionCheck(env);
|
||||||
|
flags |= (1u << static_cast<unsigned>(ordinal(env, jf)));
|
||||||
|
}
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalRef<jobject> JniFlags::create(JNIEnv * env, unsigned flags, int bits) const {
|
||||||
|
auto j = LocalRef<jobject>(env, env->CallStaticObjectMethod(m_clazz.get(), m_methNoneOf, enumClass()));
|
||||||
|
jniExceptionCheck(env);
|
||||||
|
unsigned mask = 1;
|
||||||
|
for(int i = 0; i < bits; ++i, mask <<= 1) {
|
||||||
|
if((flags & mask) != 0) {
|
||||||
|
auto jf = create(env, static_cast<jint>(i));
|
||||||
|
jniExceptionCheck(env);
|
||||||
|
env->CallBooleanMethod(j, m_methAdd, jf.get());
|
||||||
|
jniExceptionCheck(env);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return j;
|
||||||
|
}
|
||||||
|
|
||||||
|
JniLocalScope::JniLocalScope(JNIEnv* p_env, jint capacity, bool throwOnError)
|
||||||
|
: m_env(p_env)
|
||||||
|
, m_success(_pushLocalFrame(m_env, capacity)) {
|
||||||
|
if (throwOnError) {
|
||||||
|
DJINNI_ASSERT(m_success, m_env);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
JniLocalScope::~JniLocalScope() {
|
||||||
|
if (m_success) {
|
||||||
|
_popLocalFrame(m_env, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool JniLocalScope::_pushLocalFrame(JNIEnv* const env, jint capacity) {
|
||||||
|
DJINNI_ASSERT(capacity >= 0, env);
|
||||||
|
const jint push_res = env->PushLocalFrame(capacity);
|
||||||
|
return 0 == push_res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JniLocalScope::_popLocalFrame(JNIEnv* const env, jobject returnRef) {
|
||||||
|
env->PopLocalFrame(returnRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* UTF-8 and UTF-16 conversion functions from miniutf: https://github.com/dropbox/miniutf
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct offset_pt {
|
||||||
|
int offset;
|
||||||
|
char32_t pt;
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr const offset_pt invalid_pt = { -1, 0 };
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Decode a codepoint starting at str[i], and return the number of code units (bytes, for
|
||||||
|
* UTF-8) consumed and the result. If no valid codepoint is at str[i], return invalid_pt.
|
||||||
|
*/
|
||||||
|
static offset_pt utf8_decode_check(const std::string & str, std::string::size_type i) {
|
||||||
|
uint32_t b0, b1, b2, b3;
|
||||||
|
|
||||||
|
b0 = static_cast<unsigned char>(str[i]);
|
||||||
|
|
||||||
|
if (b0 < 0x80) {
|
||||||
|
// 1-byte character
|
||||||
|
return { 1, b0 };
|
||||||
|
} else if (b0 < 0xC0) {
|
||||||
|
// Unexpected continuation byte
|
||||||
|
return invalid_pt;
|
||||||
|
} else if (b0 < 0xE0) {
|
||||||
|
// 2-byte character
|
||||||
|
if (((b1 = str[i+1]) & 0xC0) != 0x80)
|
||||||
|
return invalid_pt;
|
||||||
|
|
||||||
|
char32_t pt = (b0 & 0x1F) << 6 | (b1 & 0x3F);
|
||||||
|
if (pt < 0x80)
|
||||||
|
return invalid_pt;
|
||||||
|
|
||||||
|
return { 2, pt };
|
||||||
|
} else if (b0 < 0xF0) {
|
||||||
|
// 3-byte character
|
||||||
|
if (((b1 = str[i+1]) & 0xC0) != 0x80)
|
||||||
|
return invalid_pt;
|
||||||
|
if (((b2 = str[i+2]) & 0xC0) != 0x80)
|
||||||
|
return invalid_pt;
|
||||||
|
|
||||||
|
char32_t pt = (b0 & 0x0F) << 12 | (b1 & 0x3F) << 6 | (b2 & 0x3F);
|
||||||
|
if (pt < 0x800)
|
||||||
|
return invalid_pt;
|
||||||
|
|
||||||
|
return { 3, pt };
|
||||||
|
} else if (b0 < 0xF8) {
|
||||||
|
// 4-byte character
|
||||||
|
if (((b1 = str[i+1]) & 0xC0) != 0x80)
|
||||||
|
return invalid_pt;
|
||||||
|
if (((b2 = str[i+2]) & 0xC0) != 0x80)
|
||||||
|
return invalid_pt;
|
||||||
|
if (((b3 = str[i+3]) & 0xC0) != 0x80)
|
||||||
|
return invalid_pt;
|
||||||
|
|
||||||
|
char32_t pt = (b0 & 0x0F) << 18 | (b1 & 0x3F) << 12
|
||||||
|
| (b2 & 0x3F) << 6 | (b3 & 0x3F);
|
||||||
|
if (pt < 0x10000 || pt >= 0x110000)
|
||||||
|
return invalid_pt;
|
||||||
|
|
||||||
|
return { 4, pt };
|
||||||
|
} else {
|
||||||
|
// Codepoint out of range
|
||||||
|
return invalid_pt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static char32_t utf8_decode(const std::string & str, std::string::size_type & i) {
|
||||||
|
offset_pt res = utf8_decode_check(str, i);
|
||||||
|
if (res.offset < 0) {
|
||||||
|
i += 1;
|
||||||
|
return 0xFFFD;
|
||||||
|
} else {
|
||||||
|
i += res.offset;
|
||||||
|
return res.pt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void utf16_encode(char32_t pt, std::u16string & out) {
|
||||||
|
if (pt < 0x10000) {
|
||||||
|
out += static_cast<char16_t>(pt);
|
||||||
|
} else if (pt < 0x110000) {
|
||||||
|
out += { static_cast<char16_t>(((pt - 0x10000) >> 10) + 0xD800),
|
||||||
|
static_cast<char16_t>((pt & 0x3FF) + 0xDC00) };
|
||||||
|
} else {
|
||||||
|
out += 0xFFFD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jstring jniStringFromUTF8(JNIEnv * env, const std::string & str) {
|
||||||
|
|
||||||
|
std::u16string utf16;
|
||||||
|
utf16.reserve(str.length()); // likely overallocate
|
||||||
|
for (std::string::size_type i = 0; i < str.length(); )
|
||||||
|
utf16_encode(utf8_decode(str, i), utf16);
|
||||||
|
|
||||||
|
jstring res = env->NewString(
|
||||||
|
reinterpret_cast<const jchar *>(utf16.data()), jsize(utf16.length()));
|
||||||
|
DJINNI_ASSERT(res, env);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int wcharTypeSize>
|
||||||
|
static std::u16string implWStringToUTF16(std::wstring::const_iterator, std::wstring::const_iterator)
|
||||||
|
{
|
||||||
|
static_assert(wcharTypeSize == 2 || wcharTypeSize == 4, "wchar_t must be represented by UTF-16 or UTF-32 encoding");
|
||||||
|
return {}; // unreachable
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline std::u16string implWStringToUTF16<2>(std::wstring::const_iterator begin, std::wstring::const_iterator end) {
|
||||||
|
// case when wchar_t is represented by utf-16 encoding
|
||||||
|
return std::u16string(begin, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline std::u16string implWStringToUTF16<4>(std::wstring::const_iterator begin, std::wstring::const_iterator end) {
|
||||||
|
// case when wchar_t is represented by utf-32 encoding
|
||||||
|
std::u16string utf16;
|
||||||
|
utf16.reserve(std::distance(begin, end));
|
||||||
|
for(; begin != end; ++begin)
|
||||||
|
utf16_encode(static_cast<char32_t>(*begin), utf16);
|
||||||
|
return utf16;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::u16string wstringToUTF16(const std::wstring & str) {
|
||||||
|
// hide "defined but not used" warnings
|
||||||
|
(void)implWStringToUTF16<2>;
|
||||||
|
(void)implWStringToUTF16<4>;
|
||||||
|
// Note: The template helper operates on iterators to work around a compiler issue we saw on Mac.
|
||||||
|
// It triggered undefined symbols if wstring methods were called directly in the template function.
|
||||||
|
return implWStringToUTF16<sizeof(wchar_t)>(str.cbegin(), str.cend());
|
||||||
|
}
|
||||||
|
|
||||||
|
jstring jniStringFromWString(JNIEnv * env, const std::wstring & str) {
|
||||||
|
std::u16string utf16 = wstringToUTF16(str);
|
||||||
|
jstring res = env->NewString(
|
||||||
|
reinterpret_cast<const jchar *>(utf16.data()), utf16.length());
|
||||||
|
DJINNI_ASSERT(res, env);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// UTF-16 decode helpers.
|
||||||
|
static inline bool is_high_surrogate(char16_t c) { return (c >= 0xD800) && (c < 0xDC00); }
|
||||||
|
static inline bool is_low_surrogate(char16_t c) { return (c >= 0xDC00) && (c < 0xE000); }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Like utf8_decode_check, but for UTF-16.
|
||||||
|
*/
|
||||||
|
static offset_pt utf16_decode_check(const char16_t * str, std::u16string::size_type i) {
|
||||||
|
if (is_high_surrogate(str[i]) && is_low_surrogate(str[i+1])) {
|
||||||
|
// High surrogate followed by low surrogate
|
||||||
|
char32_t pt = (((str[i] - 0xD800) << 10) | (str[i+1] - 0xDC00)) + 0x10000;
|
||||||
|
return { 2, pt };
|
||||||
|
} else if (is_high_surrogate(str[i]) || is_low_surrogate(str[i])) {
|
||||||
|
// High surrogate *not* followed by low surrogate, or unpaired low surrogate
|
||||||
|
return invalid_pt;
|
||||||
|
} else {
|
||||||
|
return { 1, str[i] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static char32_t utf16_decode(const char16_t * str, std::u16string::size_type & i) {
|
||||||
|
offset_pt res = utf16_decode_check(str, i);
|
||||||
|
if (res.offset < 0) {
|
||||||
|
i += 1;
|
||||||
|
return 0xFFFD;
|
||||||
|
} else {
|
||||||
|
i += res.offset;
|
||||||
|
return res.pt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void utf8_encode(char32_t pt, std::string & out) {
|
||||||
|
if (pt < 0x80) {
|
||||||
|
out += static_cast<char>(pt);
|
||||||
|
} else if (pt < 0x800) {
|
||||||
|
out += { static_cast<char>((pt >> 6) | 0xC0),
|
||||||
|
static_cast<char>((pt & 0x3F) | 0x80) };
|
||||||
|
} else if (pt < 0x10000) {
|
||||||
|
out += { static_cast<char>((pt >> 12) | 0xE0),
|
||||||
|
static_cast<char>(((pt >> 6) & 0x3F) | 0x80),
|
||||||
|
static_cast<char>((pt & 0x3F) | 0x80) };
|
||||||
|
} else if (pt < 0x110000) {
|
||||||
|
out += { static_cast<char>((pt >> 18) | 0xF0),
|
||||||
|
static_cast<char>(((pt >> 12) & 0x3F) | 0x80),
|
||||||
|
static_cast<char>(((pt >> 6) & 0x3F) | 0x80),
|
||||||
|
static_cast<char>((pt & 0x3F) | 0x80) };
|
||||||
|
} else {
|
||||||
|
out += { static_cast<char>(0xEF),
|
||||||
|
static_cast<char>(0xBF),
|
||||||
|
static_cast<char>(0xBD) }; // U+FFFD
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string jniUTF8FromString(JNIEnv * env, const jstring jstr) {
|
||||||
|
DJINNI_ASSERT(jstr, env);
|
||||||
|
const jsize length = env->GetStringLength(jstr);
|
||||||
|
jniExceptionCheck(env);
|
||||||
|
|
||||||
|
const auto deleter = [env, jstr] (const jchar * c) { env->ReleaseStringChars(jstr, c); };
|
||||||
|
std::unique_ptr<const jchar, decltype(deleter)> ptr(env->GetStringChars(jstr, nullptr),
|
||||||
|
deleter);
|
||||||
|
|
||||||
|
std::u16string str(reinterpret_cast<const char16_t *>(ptr.get()), length);
|
||||||
|
std::string out;
|
||||||
|
out.reserve(str.length() * 3 / 2); // estimate
|
||||||
|
for (std::u16string::size_type i = 0; i < str.length(); )
|
||||||
|
utf8_encode(utf16_decode(str.data(), i), out);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<int wcharTypeSize>
|
||||||
|
static std::wstring implUTF16ToWString(const char16_t * /*data*/, size_t /*length*/)
|
||||||
|
{
|
||||||
|
static_assert(wcharTypeSize == 2 || wcharTypeSize == 4, "wchar_t must be represented by UTF-16 or UTF-32 encoding");
|
||||||
|
return {}; // unreachable
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline std::wstring implUTF16ToWString<2>(const char16_t * data, size_t length) {
|
||||||
|
// case when wchar_t is represented by utf-16 encoding
|
||||||
|
return std::wstring(data, data + length);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline std::wstring implUTF16ToWString<4>(const char16_t * data, size_t length) {
|
||||||
|
// case when wchar_t is represented by utf-32 encoding
|
||||||
|
std::wstring result;
|
||||||
|
result.reserve(length);
|
||||||
|
for (size_t i = 0; i < length; )
|
||||||
|
result += static_cast<wchar_t>(utf16_decode(data, i));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline std::wstring UTF16ToWString(const char16_t * data, size_t length) {
|
||||||
|
// hide "defined but not used" warnings
|
||||||
|
(void)implUTF16ToWString<2>;
|
||||||
|
(void)implUTF16ToWString<4>;
|
||||||
|
return implUTF16ToWString<sizeof(wchar_t)>(data, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring jniWStringFromString(JNIEnv * env, const jstring jstr) {
|
||||||
|
DJINNI_ASSERT(jstr, env);
|
||||||
|
const jsize length = env->GetStringLength(jstr);
|
||||||
|
jniExceptionCheck(env);
|
||||||
|
|
||||||
|
const auto deleter = [env, jstr] (const jchar * c) { env->ReleaseStringChars(jstr, c); };
|
||||||
|
std::unique_ptr<const jchar, decltype(deleter)> ptr(env->GetStringChars(jstr, nullptr),
|
||||||
|
deleter);
|
||||||
|
const char16_t * data = reinterpret_cast<const char16_t *>(ptr.get());
|
||||||
|
return UTF16ToWString(data, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
DJINNI_WEAK_DEFINITION
|
||||||
|
void jniSetPendingFromCurrent(JNIEnv * env, const char * ctx) noexcept {
|
||||||
|
jniDefaultSetPendingFromCurrent(env, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void jniDefaultSetPendingFromCurrentImpl(JNIEnv * env) {
|
||||||
|
assert(env);
|
||||||
|
try {
|
||||||
|
throw;
|
||||||
|
} catch (const jni_exception & e) {
|
||||||
|
e.set_as_pending(env);
|
||||||
|
return;
|
||||||
|
} catch (const std::exception & e) {
|
||||||
|
env->ThrowNew(env->FindClass("java/lang/RuntimeException"), e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void jniDefaultSetPendingFromCurrent(JNIEnv * env, const char * /*ctx*/) noexcept {
|
||||||
|
|
||||||
|
/* It is necessary to go through a layer of indirection here because this
|
||||||
|
function is marked noexcept, but the implementation may still throw.
|
||||||
|
Any exceptions which are not caught (i.e. exceptions which aren't
|
||||||
|
std::exception subclasses) will result in a call to terminate() since this
|
||||||
|
function is marked noexcept */
|
||||||
|
|
||||||
|
jniDefaultSetPendingFromCurrentImpl(env);
|
||||||
|
}
|
||||||
|
|
||||||
|
template class ProxyCache<JavaProxyCacheTraits>;
|
||||||
|
|
||||||
|
CppProxyClassInfo::CppProxyClassInfo(const char * className)
|
||||||
|
: clazz(jniFindClass(className)),
|
||||||
|
constructor(jniGetMethodID(clazz.get(), "<init>", "(J)V")),
|
||||||
|
idField(jniGetFieldID(clazz.get(), "nativeRef", "J")) {
|
||||||
|
}
|
||||||
|
|
||||||
|
CppProxyClassInfo::CppProxyClassInfo() : constructor{}, idField{} {
|
||||||
|
}
|
||||||
|
|
||||||
|
CppProxyClassInfo::~CppProxyClassInfo() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wrapper around Java WeakReference objects. (We can't use JNI NewWeakGlobalRef() because
|
||||||
|
* it doesn't have the right semantics - see comment in djinni_support.hpp.)
|
||||||
|
*/
|
||||||
|
class JavaWeakRef {
|
||||||
|
private:
|
||||||
|
struct JniInfo {
|
||||||
|
public:
|
||||||
|
const GlobalRef<jclass> clazz { jniFindClass("java/lang/ref/WeakReference") };
|
||||||
|
const jmethodID constructor { jniGetMethodID(clazz.get(), "<init>", "(Ljava/lang/Object;)V") };
|
||||||
|
const jmethodID method_get { jniGetMethodID(clazz.get(), "get", "()Ljava/lang/Object;") };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper used by constructor
|
||||||
|
static GlobalRef<jobject> create(JNIEnv * jniEnv, jobject obj) {
|
||||||
|
const JniInfo & weakRefClass = JniClass<JniInfo>::get();
|
||||||
|
LocalRef<jobject> weakRef(jniEnv, jniEnv->NewObject(weakRefClass.clazz.get(), weakRefClass.constructor, obj));
|
||||||
|
// DJINNI_ASSERT performs an exception check before anything else, so we don't need
|
||||||
|
// a separate jniExceptionCheck call.
|
||||||
|
DJINNI_ASSERT(weakRef, jniEnv);
|
||||||
|
return GlobalRef<jobject>(jniEnv, weakRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Constructor
|
||||||
|
JavaWeakRef(jobject obj) : JavaWeakRef(jniGetThreadEnv(), obj) {}
|
||||||
|
JavaWeakRef(JNIEnv * jniEnv, jobject obj) : m_weakRef(create(jniEnv, obj)) {}
|
||||||
|
|
||||||
|
// Get the object pointed to if it's still strongly reachable or, return null if not.
|
||||||
|
// (Analogous to weak_ptr::lock.) Returns a local reference.
|
||||||
|
jobject lock() const {
|
||||||
|
const auto & jniEnv = jniGetThreadEnv();
|
||||||
|
const JniInfo & weakRefClass = JniClass<JniInfo>::get();
|
||||||
|
LocalRef<jobject> javaObj(jniEnv->CallObjectMethod(m_weakRef.get(), weakRefClass.method_get));
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
return javaObj.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Java WeakReference objects don't have a way to check whether they're expired except
|
||||||
|
// by upgrading them to a strong ref.
|
||||||
|
bool expired() const {
|
||||||
|
LocalRef<jobject> javaObj { lock() };
|
||||||
|
return !javaObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
GlobalRef<jobject> m_weakRef;
|
||||||
|
};
|
||||||
|
|
||||||
|
template class ProxyCache<JniCppProxyCacheTraits>;
|
||||||
|
|
||||||
|
} // namespace djinni
|
658
wrappers/android/mynteye/third_party/djinni/support-lib/jni/djinni_support.hpp
vendored
Normal file
658
wrappers/android/mynteye/third_party/djinni/support-lib/jni/djinni_support.hpp
vendored
Normal file
|
@ -0,0 +1,658 @@
|
||||||
|
//
|
||||||
|
// Copyright 2014 Dropbox, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <exception>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "../proxy_cache_interface.hpp"
|
||||||
|
#include "../djinni_common.hpp"
|
||||||
|
#include <jni.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Djinni support library
|
||||||
|
*/
|
||||||
|
|
||||||
|
// jni.h should really put extern "C" in JNIEXPORT, but it doesn't. :(
|
||||||
|
#define CJNIEXPORT extern "C" JNIEXPORT
|
||||||
|
|
||||||
|
namespace djinni {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Global initialization and shutdown. Call these from JNI_OnLoad and JNI_OnUnload.
|
||||||
|
*/
|
||||||
|
void jniInit(JavaVM * jvm);
|
||||||
|
void jniShutdown();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the JNIEnv for the invoking thread. Should only be called on Java-created threads.
|
||||||
|
*/
|
||||||
|
JNIEnv * jniGetThreadEnv();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Global and local reference guard objects.
|
||||||
|
*
|
||||||
|
* A GlobalRef<T> is constructed with a local reference; the constructor upgrades the local
|
||||||
|
* reference to a global reference, and the destructor deletes the local ref.
|
||||||
|
*
|
||||||
|
* A LocalRef<T> should be constructed with a new local reference. The local reference will
|
||||||
|
* be deleted when the LocalRef is deleted.
|
||||||
|
*/
|
||||||
|
struct GlobalRefDeleter { void operator() (jobject globalRef) noexcept; };
|
||||||
|
|
||||||
|
template <typename PointerType>
|
||||||
|
class GlobalRef : public std::unique_ptr<typename std::remove_pointer<PointerType>::type,
|
||||||
|
GlobalRefDeleter> {
|
||||||
|
public:
|
||||||
|
GlobalRef() {}
|
||||||
|
GlobalRef(GlobalRef && obj)
|
||||||
|
: std::unique_ptr<typename std::remove_pointer<PointerType>::type, ::djinni::GlobalRefDeleter>(
|
||||||
|
std::move(obj)
|
||||||
|
) {}
|
||||||
|
GlobalRef(JNIEnv * env, PointerType localRef)
|
||||||
|
: std::unique_ptr<typename std::remove_pointer<PointerType>::type, ::djinni::GlobalRefDeleter>(
|
||||||
|
static_cast<PointerType>(env->NewGlobalRef(localRef)),
|
||||||
|
::djinni::GlobalRefDeleter{}
|
||||||
|
) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct LocalRefDeleter { void operator() (jobject localRef) noexcept; };
|
||||||
|
|
||||||
|
template <typename PointerType>
|
||||||
|
class LocalRef : public std::unique_ptr<typename std::remove_pointer<PointerType>::type,
|
||||||
|
LocalRefDeleter> {
|
||||||
|
public:
|
||||||
|
LocalRef() {}
|
||||||
|
LocalRef(JNIEnv * /*env*/, PointerType localRef)
|
||||||
|
: std::unique_ptr<typename std::remove_pointer<PointerType>::type, ::djinni::LocalRefDeleter>(
|
||||||
|
localRef) {}
|
||||||
|
explicit LocalRef(PointerType localRef)
|
||||||
|
: std::unique_ptr<typename std::remove_pointer<PointerType>::type, LocalRefDeleter>(
|
||||||
|
localRef) {}
|
||||||
|
// Allow implicit conversion to PointerType so it can be passed
|
||||||
|
// as argument to JNI functions expecting PointerType.
|
||||||
|
// All functions creating new local references should return LocalRef instead of PointerType
|
||||||
|
operator PointerType() const & { return this->get(); }
|
||||||
|
operator PointerType() && = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
const T& get(const T& x) noexcept { return x; }
|
||||||
|
template<class T>
|
||||||
|
typename LocalRef<T>::pointer get(const LocalRef<T>& x) noexcept { return x.get(); }
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
const T& release(const T& x) noexcept { return x; }
|
||||||
|
template<class T>
|
||||||
|
typename LocalRef<T>::pointer release(LocalRef<T>& x) noexcept { return x.release(); }
|
||||||
|
template<class T>
|
||||||
|
typename LocalRef<T>::pointer release(LocalRef<T>&& x) noexcept { return x.release(); }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Exception to indicate that a Java exception is pending in the JVM.
|
||||||
|
*/
|
||||||
|
class jni_exception : public std::exception {
|
||||||
|
GlobalRef<jthrowable> m_java_exception;
|
||||||
|
public:
|
||||||
|
jni_exception(JNIEnv * env, jthrowable java_exception)
|
||||||
|
: m_java_exception(env, java_exception) {
|
||||||
|
assert(java_exception);
|
||||||
|
}
|
||||||
|
jthrowable java_exception() const { return m_java_exception.get(); }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Sets the pending JNI exception using this Java exception.
|
||||||
|
*/
|
||||||
|
void set_as_pending(JNIEnv * env) const noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Throw if any Java exception is pending in the JVM.
|
||||||
|
*
|
||||||
|
* If an exception is pending, this function will clear the
|
||||||
|
* pending state, and pass the exception to
|
||||||
|
* jniThrowCppFromJavaException().
|
||||||
|
*/
|
||||||
|
void jniExceptionCheck(JNIEnv * env);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Throws a C++ exception based on the given Java exception.
|
||||||
|
*
|
||||||
|
* java_exception is a local reference to a Java throwable, which
|
||||||
|
* must not be null, and should no longer set as "pending" in the JVM.
|
||||||
|
* This is called to handle errors in other JNI processing, including
|
||||||
|
* by jniExceptionCheck().
|
||||||
|
*
|
||||||
|
* The default implementation is defined with __attribute__((weak)) so you
|
||||||
|
* can replace it by defining your own version. The default implementation
|
||||||
|
* will throw a jni_exception containing the given jthrowable.
|
||||||
|
*/
|
||||||
|
DJINNI_NORETURN_DEFINITION
|
||||||
|
void jniThrowCppFromJavaException(JNIEnv * env, jthrowable java_exception);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set an AssertionError in env with message message, and then throw via jniExceptionCheck.
|
||||||
|
*/
|
||||||
|
DJINNI_NORETURN_DEFINITION
|
||||||
|
void jniThrowAssertionError(JNIEnv * env, const char * file, int line, const char * check);
|
||||||
|
|
||||||
|
#define DJINNI_ASSERT_MSG(check, env, message) \
|
||||||
|
do { \
|
||||||
|
::djinni::jniExceptionCheck(env); \
|
||||||
|
const bool check__res = bool(check); \
|
||||||
|
::djinni::jniExceptionCheck(env); \
|
||||||
|
if (!check__res) { \
|
||||||
|
::djinni::jniThrowAssertionError(env, __FILE__, __LINE__, message); \
|
||||||
|
} \
|
||||||
|
} while(false)
|
||||||
|
#define DJINNI_ASSERT(check, env) DJINNI_ASSERT_MSG(check, env, #check)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper for JniClass. (This can't be a subclass because it needs to not be templatized.)
|
||||||
|
*/
|
||||||
|
class JniClassInitializer {
|
||||||
|
|
||||||
|
using registration_vec = std::vector<std::function<void()>>;
|
||||||
|
static registration_vec get_all();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
JniClassInitializer(std::function<void()> init);
|
||||||
|
|
||||||
|
template <class C> friend class JniClass;
|
||||||
|
friend void jniInit(JavaVM *);
|
||||||
|
|
||||||
|
static registration_vec & get_vec();
|
||||||
|
static std::mutex & get_mutex();
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Each instantiation of this template produces a singleton object of type C which
|
||||||
|
* will be initialized by djinni::jniInit(). For example:
|
||||||
|
*
|
||||||
|
* struct JavaFooInfo {
|
||||||
|
* jmethodID foo;
|
||||||
|
* JavaFooInfo() // initialize clazz and foo from jniGetThreadEnv
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* To use this in a JNI function or callback, invoke:
|
||||||
|
*
|
||||||
|
* CallVoidMethod(object, JniClass<JavaFooInfo>::get().foo, ...);
|
||||||
|
*
|
||||||
|
* This uses C++'s template instantiation behavior to guarantee that any T for which
|
||||||
|
* JniClass<T>::get() is *used* anywhere in the program will be *initialized* by init_all().
|
||||||
|
* Therefore, it's always safe to compile in wrappers for all known Java types - the library
|
||||||
|
* will only depend on the presence of those actually needed.
|
||||||
|
*/
|
||||||
|
template <class C>
|
||||||
|
class JniClass {
|
||||||
|
public:
|
||||||
|
static const C & get() {
|
||||||
|
(void)s_initializer; // ensure that initializer is actually instantiated
|
||||||
|
assert(s_singleton);
|
||||||
|
return *s_singleton;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const JniClassInitializer s_initializer;
|
||||||
|
static std::unique_ptr<C> s_singleton;
|
||||||
|
|
||||||
|
static void allocate() {
|
||||||
|
// We can't use make_unique here, because C will have a private constructor and
|
||||||
|
// list JniClass as a friend; so we have to allocate it by hand.
|
||||||
|
s_singleton = std::unique_ptr<C>(new C());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class C>
|
||||||
|
const JniClassInitializer JniClass<C>::s_initializer ( allocate );
|
||||||
|
|
||||||
|
template <class C>
|
||||||
|
std::unique_ptr<C> JniClass<C>::s_singleton;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Exception-checking helpers. These will throw if an exception is pending.
|
||||||
|
*/
|
||||||
|
GlobalRef<jclass> jniFindClass(const char * name);
|
||||||
|
jmethodID jniGetStaticMethodID(jclass clazz, const char * name, const char * sig);
|
||||||
|
jmethodID jniGetMethodID(jclass clazz, const char * name, const char * sig);
|
||||||
|
jfieldID jniGetFieldID(jclass clazz, const char * name, const char * sig);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper for maintaining shared_ptrs to wrapped Java objects.
|
||||||
|
*
|
||||||
|
* This is used for automatically wrapping a Java object that exposes some interface
|
||||||
|
* with a C++ object that calls back into the JVM, such as a listener. Calling
|
||||||
|
* get_java_proxy<T>(obj) the first time will construct a T and return a shared_ptr to it, and
|
||||||
|
* also save a weak_ptr to the new object internally. The constructed T contains a strong
|
||||||
|
* GlobalRef to jobj. As long as something in C++ maintains a strong reference to the wrapper,
|
||||||
|
* future calls to get(jobj) will return the *same* wrapper object.
|
||||||
|
*
|
||||||
|
* Java | C++
|
||||||
|
* | ________________________ ___________
|
||||||
|
* _____________ | | | | |
|
||||||
|
* | | | | JniImplFooListener | <=========== | Foo |
|
||||||
|
* | FooListener | <============ | : public FooListener, | shared_ptr |___________|
|
||||||
|
* |_____________| GlobalRef | JavaProxyCacheEntry |
|
||||||
|
* | |________________________|
|
||||||
|
* | ^ ______________________
|
||||||
|
* | \ | |
|
||||||
|
* | - - - - - - | JavaProxyCache |
|
||||||
|
* | weak_ptr |______________________|
|
||||||
|
*
|
||||||
|
* As long as the C++ FooListener has references, the Java FooListener is kept alive.
|
||||||
|
*
|
||||||
|
* We use a custom unordered_map with Java objects (jobject) as keys, and JNI object
|
||||||
|
* identity and hashing functions. This means that as long as a key is in the map,
|
||||||
|
* we must have some other GlobalRef keeping it alive. To ensure safety, the Entry
|
||||||
|
* destructor removes *itself* from the map - destruction order guarantees that this
|
||||||
|
* will happen before the contained global reference becomes invalid (by destruction of
|
||||||
|
* the GlobalRef).
|
||||||
|
*/
|
||||||
|
struct JavaIdentityHash;
|
||||||
|
struct JavaIdentityEquals;
|
||||||
|
struct JavaProxyCacheTraits {
|
||||||
|
using UnowningImplPointer = jobject;
|
||||||
|
using OwningImplPointer = jobject;
|
||||||
|
using OwningProxyPointer = std::shared_ptr<void>;
|
||||||
|
using WeakProxyPointer = std::weak_ptr<void>;
|
||||||
|
using UnowningImplPointerHash = JavaIdentityHash;
|
||||||
|
using UnowningImplPointerEqual = JavaIdentityEquals;
|
||||||
|
};
|
||||||
|
extern template class ProxyCache<JavaProxyCacheTraits>;
|
||||||
|
using JavaProxyCache = ProxyCache<JavaProxyCacheTraits>;
|
||||||
|
template <typename T> using JavaProxyHandle = JavaProxyCache::Handle<GlobalRef<jobject>, T>;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cache for CppProxy objects. This is the inverse of the JavaProxyCache mechanism above,
|
||||||
|
* ensuring that each time we pass an interface from Java to C++, we get the *same* CppProxy
|
||||||
|
* object on the Java side:
|
||||||
|
*
|
||||||
|
* Java | C++
|
||||||
|
* |
|
||||||
|
* ______________ | ________________ ___________
|
||||||
|
* | | | | | | |
|
||||||
|
* | Foo.CppProxy | ------------> | CppProxyHandle | =============> | Foo |
|
||||||
|
* |______________| (jlong) | <Foo> | (shared_ptr) |___________|
|
||||||
|
* ^ | |________________|
|
||||||
|
* \ |
|
||||||
|
* _________ | __________________
|
||||||
|
* | | | | |
|
||||||
|
* | WeakRef | <------------------------- | jniCppProxyCache |
|
||||||
|
* |_________| (GlobalRef) |__________________|
|
||||||
|
* |
|
||||||
|
*
|
||||||
|
* We don't use JNI WeakGlobalRef objects, because they last longer than is safe - a
|
||||||
|
* WeakGlobalRef can still be upgraded to a strong reference even during finalization, which
|
||||||
|
* leads to use-after-free. Java WeakRefs provide the right lifetime guarantee.
|
||||||
|
*/
|
||||||
|
class JavaWeakRef;
|
||||||
|
struct JniCppProxyCacheTraits {
|
||||||
|
using UnowningImplPointer = void *;
|
||||||
|
using OwningImplPointer = std::shared_ptr<void>;
|
||||||
|
using OwningProxyPointer = jobject;
|
||||||
|
using WeakProxyPointer = JavaWeakRef;
|
||||||
|
using UnowningImplPointerHash = std::hash<void *>;
|
||||||
|
using UnowningImplPointerEqual = std::equal_to<void *>;
|
||||||
|
};
|
||||||
|
extern template class ProxyCache<JniCppProxyCacheTraits>;
|
||||||
|
using JniCppProxyCache = ProxyCache<JniCppProxyCacheTraits>;
|
||||||
|
template <class T> using CppProxyHandle = JniCppProxyCache::Handle<std::shared_ptr<T>>;
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
static const std::shared_ptr<T> & objectFromHandleAddress(jlong handle) {
|
||||||
|
assert(handle);
|
||||||
|
assert(handle > 4096);
|
||||||
|
// Below line segfaults gcc-4.8. Using a temporary variable hides the bug.
|
||||||
|
//const auto & ret = reinterpret_cast<const CppProxyHandle<T> *>(handle)->get();
|
||||||
|
const CppProxyHandle<T> *proxy_handle =
|
||||||
|
reinterpret_cast<const CppProxyHandle<T> *>(handle);
|
||||||
|
const auto & ret = proxy_handle->get();
|
||||||
|
assert(ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Information needed to use a CppProxy class.
|
||||||
|
*
|
||||||
|
* In an ideal world, this object would be properly always-valid RAII, and we'd use an
|
||||||
|
* optional<CppProxyClassInfo> where needed. Unfortunately we don't want to depend on optional
|
||||||
|
* here, so this object has an invalid state and default constructor.
|
||||||
|
*/
|
||||||
|
struct CppProxyClassInfo {
|
||||||
|
const GlobalRef<jclass> clazz;
|
||||||
|
const jmethodID constructor;
|
||||||
|
const jfieldID idField;
|
||||||
|
|
||||||
|
CppProxyClassInfo(const char * className);
|
||||||
|
CppProxyClassInfo();
|
||||||
|
~CppProxyClassInfo();
|
||||||
|
|
||||||
|
// Validity check
|
||||||
|
explicit operator bool() const { return bool(clazz); }
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Base class for Java <-> C++ interface adapters.
|
||||||
|
*
|
||||||
|
* I is the C++ base class (interface) being adapted; Self is the interface adapter class
|
||||||
|
* derived from JniInterface (using CRTP). For example:
|
||||||
|
*
|
||||||
|
* class NativeToken final : djinni::JniInterface<Token, NativeToken> { ... }
|
||||||
|
*/
|
||||||
|
template <class I, class Self>
|
||||||
|
class JniInterface {
|
||||||
|
public:
|
||||||
|
/*
|
||||||
|
* Given a C++ object, find or create a Java version. The cases here are:
|
||||||
|
* 1. Null
|
||||||
|
* 2. The provided C++ object is actually a JavaProxy (C++-side proxy for Java impl)
|
||||||
|
* 3. The provided C++ object has an existing CppProxy (Java-side proxy for C++ impl)
|
||||||
|
* 4. The provided C++ object needs a new CppProxy allocated
|
||||||
|
*/
|
||||||
|
jobject _toJava(JNIEnv* jniEnv, const std::shared_ptr<I> & c) const {
|
||||||
|
// Case 1 - null
|
||||||
|
if (!c) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 2 - already a JavaProxy. Only possible if Self::JavaProxy exists.
|
||||||
|
if (jobject impl = _unwrapJavaProxy<Self>(&c)) {
|
||||||
|
return jniEnv->NewLocalRef(impl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cases 3 and 4.
|
||||||
|
assert(m_cppProxyClass);
|
||||||
|
return JniCppProxyCache::get(typeid(c), c, &newCppProxy);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Given a Java object, find or create a C++ version. The cases here are:
|
||||||
|
* 1. Null
|
||||||
|
* 2. The provided Java object is actually a CppProxy (Java-side proxy for a C++ impl)
|
||||||
|
* 3. The provided Java object has an existing JavaProxy (C++-side proxy for a Java impl)
|
||||||
|
* 4. The provided Java object needs a new JavaProxy allocated
|
||||||
|
*/
|
||||||
|
std::shared_ptr<I> _fromJava(JNIEnv* jniEnv, jobject j) const {
|
||||||
|
// Case 1 - null
|
||||||
|
if (!j) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 2 - already a Java proxy; we just need to pull the C++ impl out. (This case
|
||||||
|
// is only possible if we were constructed with a cppProxyClassName parameter.)
|
||||||
|
if (m_cppProxyClass
|
||||||
|
&& jniEnv->IsSameObject(jniEnv->GetObjectClass(j), m_cppProxyClass.clazz.get())) {
|
||||||
|
jlong handle = jniEnv->GetLongField(j, m_cppProxyClass.idField);
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
return objectFromHandleAddress<I>(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cases 3 and 4 - see _getJavaProxy helper below. JavaProxyCache is responsible for
|
||||||
|
// distinguishing between the two cases. Only possible if Self::JavaProxy exists.
|
||||||
|
return _getJavaProxy<Self>(j);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constructor for interfaces for which a Java-side CppProxy class exists
|
||||||
|
JniInterface(const char * cppProxyClassName) : m_cppProxyClass(cppProxyClassName) {}
|
||||||
|
|
||||||
|
// Constructor for interfaces without a Java proxy class
|
||||||
|
JniInterface() : m_cppProxyClass{} {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/*
|
||||||
|
* Helpers for _toJava above. The possibility that an object is already a C++-side proxy
|
||||||
|
* only exists if the code generator emitted one (if Self::JavaProxy exists).
|
||||||
|
*/
|
||||||
|
template <typename S, typename JavaProxy = typename S::JavaProxy>
|
||||||
|
jobject _unwrapJavaProxy(const std::shared_ptr<I> * c) const {
|
||||||
|
if (auto proxy = dynamic_cast<JavaProxy *>(c->get())) {
|
||||||
|
return proxy->JavaProxyHandle<JavaProxy>::get().get();
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename S>
|
||||||
|
jobject _unwrapJavaProxy(...) const {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper for _toJava above: given a C++ object, allocate a CppProxy on the Java side for
|
||||||
|
* it. This is actually called by jniCppProxyCacheGet, which holds a lock on the global
|
||||||
|
* C++-to-Java proxy map object.
|
||||||
|
*/
|
||||||
|
static std::pair<jobject, void*> newCppProxy(const std::shared_ptr<void> & cppObj) {
|
||||||
|
const auto & data = JniClass<Self>::get();
|
||||||
|
const auto & jniEnv = jniGetThreadEnv();
|
||||||
|
std::unique_ptr<CppProxyHandle<I>> to_encapsulate(
|
||||||
|
new CppProxyHandle<I>(std::static_pointer_cast<I>(cppObj)));
|
||||||
|
jlong handle = static_cast<jlong>(reinterpret_cast<uintptr_t>(to_encapsulate.get()));
|
||||||
|
jobject cppProxy = jniEnv->NewObject(data.m_cppProxyClass.clazz.get(),
|
||||||
|
data.m_cppProxyClass.constructor,
|
||||||
|
handle);
|
||||||
|
jniExceptionCheck(jniEnv);
|
||||||
|
to_encapsulate.release();
|
||||||
|
return { cppProxy, cppObj.get() };
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helpers for _fromJava above. We can only produce a C++-side proxy if the code generator
|
||||||
|
* emitted one (if Self::JavaProxy exists).
|
||||||
|
*/
|
||||||
|
template <typename S, typename JavaProxy = typename S::JavaProxy>
|
||||||
|
std::shared_ptr<I> _getJavaProxy(jobject j) const {
|
||||||
|
static_assert(std::is_base_of<JavaProxyHandle<JavaProxy>, JavaProxy>::value,
|
||||||
|
"JavaProxy must derive from JavaProxyCacheEntry");
|
||||||
|
|
||||||
|
return std::static_pointer_cast<JavaProxy>(JavaProxyCache::get(
|
||||||
|
typeid(JavaProxy), j,
|
||||||
|
[] (const jobject & obj) -> std::pair<std::shared_ptr<void>, jobject> {
|
||||||
|
auto ret = std::make_shared<JavaProxy>(obj);
|
||||||
|
return { ret, ret->JavaProxyHandle<JavaProxy>::get().get() };
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename S>
|
||||||
|
std::shared_ptr<I> _getJavaProxy(...) const {
|
||||||
|
assert(false);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CppProxyClassInfo m_cppProxyClass;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Guard object which automatically begins and ends a JNI local frame when
|
||||||
|
* it is created and destroyed, using PushLocalFrame and PopLocalFrame.
|
||||||
|
*
|
||||||
|
* Local frame creation can fail. The throwOnError parameter specifies how
|
||||||
|
* errors are reported:
|
||||||
|
* - true (default): throws on failure
|
||||||
|
* - false: queues a JNI exception on failure; the user must call checkSuccess()
|
||||||
|
*
|
||||||
|
* The JNIEnv given at construction is expected to still be valid at
|
||||||
|
* destruction, so this class isn't suitable for use across threads.
|
||||||
|
* It is intended for use on the stack.
|
||||||
|
*
|
||||||
|
* All JNI local references created within the defined scope will be
|
||||||
|
* released at the end of the scope. This class doesn't support
|
||||||
|
* the jobject return value supported by PopLocalFrame(), because
|
||||||
|
* the destructor cannot return the new reference value for the parent
|
||||||
|
* frame.
|
||||||
|
*/
|
||||||
|
class JniLocalScope {
|
||||||
|
public:
|
||||||
|
/*
|
||||||
|
* Create the guard object and begin the local frame.
|
||||||
|
*
|
||||||
|
* @param p_env the JNIEnv for the current thread.
|
||||||
|
* @param capacity the initial number of local references to
|
||||||
|
* allocate.
|
||||||
|
*/
|
||||||
|
JniLocalScope(JNIEnv* p_env, jint capacity, bool throwOnError = true);
|
||||||
|
bool checkSuccess() const { return m_success; }
|
||||||
|
~JniLocalScope();
|
||||||
|
private:
|
||||||
|
JniLocalScope(const JniLocalScope& other);
|
||||||
|
JniLocalScope& operator=(const JniLocalScope& other);
|
||||||
|
|
||||||
|
static bool _pushLocalFrame(JNIEnv* const env, jint capacity);
|
||||||
|
static void _popLocalFrame(JNIEnv* const env, jobject returnRef);
|
||||||
|
|
||||||
|
JNIEnv* const m_env;
|
||||||
|
const bool m_success;
|
||||||
|
};
|
||||||
|
|
||||||
|
jstring jniStringFromUTF8(JNIEnv * env, const std::string & str);
|
||||||
|
std::string jniUTF8FromString(JNIEnv * env, const jstring jstr);
|
||||||
|
|
||||||
|
jstring jniStringFromWString(JNIEnv * env, const std::wstring & str);
|
||||||
|
std::wstring jniWStringFromString(JNIEnv * env, const jstring jstr);
|
||||||
|
|
||||||
|
class JniEnum {
|
||||||
|
public:
|
||||||
|
/*
|
||||||
|
* Given a Java object, find its numeric value. This returns a jint, which the caller can
|
||||||
|
* static_cast<> into the necessary C++ enum type.
|
||||||
|
*/
|
||||||
|
jint ordinal(JNIEnv * env, jobject obj) const;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a Java value of the wrapped class with the given value.
|
||||||
|
*/
|
||||||
|
LocalRef<jobject> create(JNIEnv * env, jint value) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
JniEnum(const std::string & name);
|
||||||
|
jclass enumClass() const { return m_clazz.get(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const GlobalRef<jclass> m_clazz;
|
||||||
|
const jmethodID m_staticmethValues;
|
||||||
|
const jmethodID m_methOrdinal;
|
||||||
|
};
|
||||||
|
|
||||||
|
class JniFlags : private JniEnum {
|
||||||
|
public:
|
||||||
|
/*
|
||||||
|
* Given a Java EnumSet convert it to the corresponding bit pattern
|
||||||
|
* which can then be static_cast<> to the actual enum.
|
||||||
|
*/
|
||||||
|
unsigned flags(JNIEnv * env, jobject obj) const;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Create a Java EnumSet of the specified flags considering the given number of active bits.
|
||||||
|
*/
|
||||||
|
LocalRef<jobject> create(JNIEnv * env, unsigned flags, int bits) const;
|
||||||
|
|
||||||
|
using JniEnum::create;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
JniFlags(const std::string & name);
|
||||||
|
|
||||||
|
private:
|
||||||
|
const GlobalRef<jclass> m_clazz { jniFindClass("java/util/EnumSet") };
|
||||||
|
const jmethodID m_methNoneOf { jniGetStaticMethodID(m_clazz.get(), "noneOf", "(Ljava/lang/Class;)Ljava/util/EnumSet;") };
|
||||||
|
const jmethodID m_methAdd { jniGetMethodID(m_clazz.get(), "add", "(Ljava/lang/Object;)Z") };
|
||||||
|
const jmethodID m_methIterator { jniGetMethodID(m_clazz.get(), "iterator", "()Ljava/util/Iterator;") };
|
||||||
|
const jmethodID m_methSize { jniGetMethodID(m_clazz.get(), "size", "()I") };
|
||||||
|
|
||||||
|
struct {
|
||||||
|
const GlobalRef<jclass> clazz { jniFindClass("java/util/Iterator") };
|
||||||
|
const jmethodID methNext { jniGetMethodID(clazz.get(), "next", "()Ljava/lang/Object;") };
|
||||||
|
} m_iterator;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define DJINNI_FUNCTION_PROLOGUE0(env_)
|
||||||
|
#define DJINNI_FUNCTION_PROLOGUE1(env_, arg1_)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper for JNI_TRANSLATE_EXCEPTIONS_RETURN.
|
||||||
|
*
|
||||||
|
* Must be called in a catch block. Responsible for setting the pending
|
||||||
|
* exception in JNI based on the current C++ exception.
|
||||||
|
*
|
||||||
|
* The default implementation is defined with __attribute__((weak)) so you
|
||||||
|
* can replace it by defining your own version. The default implementation
|
||||||
|
* will call jniDefaultSetPendingFromCurrent(), which will propagate a
|
||||||
|
* jni_exception directly into Java, or throw a RuntimeException for any
|
||||||
|
* other std::exception.
|
||||||
|
*/
|
||||||
|
void jniSetPendingFromCurrent(JNIEnv * env, const char * ctx) noexcept;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper for JNI_TRANSLATE_EXCEPTIONS_RETURN.
|
||||||
|
*
|
||||||
|
* Must be called in a catch block. Responsible for setting the pending
|
||||||
|
* exception in JNI based on the current C++ exception.
|
||||||
|
*
|
||||||
|
* This will call jniSetPendingFrom(env, jni_exception) if the current exception
|
||||||
|
* is a jni_exception, or otherwise will set a RuntimeException from any
|
||||||
|
* other std::exception. Any non-std::exception will result in a call
|
||||||
|
* to terminate().
|
||||||
|
*
|
||||||
|
* This is called by the default implementation of jniSetPendingFromCurrent.
|
||||||
|
*/
|
||||||
|
void jniDefaultSetPendingFromCurrent(JNIEnv * env, const char * ctx) noexcept;
|
||||||
|
|
||||||
|
/* Catch C++ exceptions and translate them to Java exceptions.
|
||||||
|
*
|
||||||
|
* All functions called by Java must be fully wrapped by an outer try...catch block like so:
|
||||||
|
*
|
||||||
|
* try {
|
||||||
|
* ...
|
||||||
|
* } JNI_TRANSLATE_EXCEPTIONS_RETURN(env, 0)
|
||||||
|
* ... or JNI_TRANSLATE_EXCEPTIONS_RETURN(env, ) for functions returning void
|
||||||
|
*
|
||||||
|
* The second parameter is a default return value to be used if an exception is caught and
|
||||||
|
* converted. (For JNI outer-layer calls, this result will always be ignored by JNI, so
|
||||||
|
* it can safely be 0 for any function with a non-void return value.)
|
||||||
|
*/
|
||||||
|
#define JNI_TRANSLATE_EXCEPTIONS_RETURN(env, ret) \
|
||||||
|
catch (const std::exception &) { \
|
||||||
|
::djinni::jniSetPendingFromCurrent(env, __func__); \
|
||||||
|
return ret; \
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Catch jni_exception and translate it back to a Java exception, without catching
|
||||||
|
* any other C++ exceptions. Can be used to wrap code which might cause JNI
|
||||||
|
* exceptions like so:
|
||||||
|
*
|
||||||
|
* try {
|
||||||
|
* ...
|
||||||
|
* } JNI_TRANSLATE_JAVA_EXCEPTIONS_RETURN(env, 0)
|
||||||
|
* ... or JNI_TRANSLATE_JAVA_EXCEPTIONS_RETURN(env, ) for functions returning void
|
||||||
|
*
|
||||||
|
* The second parameter is a default return value to be used if an exception is caught and
|
||||||
|
* converted. (For JNI outer-layer calls, this result will always be ignored by JNI, so
|
||||||
|
* it can safely be 0 for any function with a non-void return value.)
|
||||||
|
*/
|
||||||
|
#define JNI_TRANSLATE_JNI_EXCEPTIONS_RETURN(env, ret) \
|
||||||
|
catch (const ::djinni::jni_exception & e) { \
|
||||||
|
e.set_as_pending(env); \
|
||||||
|
return ret; \
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace djinni
|
176
wrappers/android/mynteye/third_party/djinni/support-lib/proxy_cache_impl.hpp
vendored
Normal file
176
wrappers/android/mynteye/third_party/djinni/support-lib/proxy_cache_impl.hpp
vendored
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
//
|
||||||
|
// Copyright 2015 Dropbox, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "proxy_cache_interface.hpp"
|
||||||
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
// """
|
||||||
|
// This place is not a place of honor.
|
||||||
|
// No highly esteemed deed is commemorated here.
|
||||||
|
// Nothing valued is here.
|
||||||
|
// This place is a message and part of a system of messages.
|
||||||
|
// Pay attention to it!
|
||||||
|
// Sending this message was important to us.
|
||||||
|
// We considered ourselves to be a powerful culture.
|
||||||
|
// """
|
||||||
|
//
|
||||||
|
// From "Expert Judgment on Markers to Deter Inadvertent Human Intrusion into the Waste
|
||||||
|
// Isolation Pilot Plant", Sandia National Laboratories report SAND92-1382 / UC-721, p. F-49
|
||||||
|
|
||||||
|
namespace djinni {
|
||||||
|
|
||||||
|
// See comment on `get_unowning()` in proxy_cache_interface.hpp.
|
||||||
|
template <typename T> static inline auto upgrade_weak(const T & ptr) -> decltype(ptr.lock()) {
|
||||||
|
return ptr.lock();
|
||||||
|
}
|
||||||
|
template <typename T> static inline T * upgrade_weak(T* ptr) { return ptr; }
|
||||||
|
template <typename T> static inline bool is_expired(const T & ptr) { return ptr.expired(); }
|
||||||
|
template <typename T> static inline bool is_expired(T* ptr) { return !ptr; }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Generic proxy cache.
|
||||||
|
*
|
||||||
|
* This provides a general-purpose mechanism for proxies to be re-used. When we pass an object
|
||||||
|
* across the language boundary from A to B, we must create a proxy object within language B
|
||||||
|
* that passes calls back to language A. For example, if have a C++ object that is passed into
|
||||||
|
* Java, we would create a Java object that owns a `shared_ptr` and has a set of native methods
|
||||||
|
* that call in to C++.
|
||||||
|
*
|
||||||
|
* When we create such an object, we also want to cache a weak reference to it, so that if we
|
||||||
|
* later pass the *same* object across the boundary, the same proxy will be returned. This is
|
||||||
|
* necessary for correctness in some situations: for example, in the case of an `add_listener`
|
||||||
|
* and `remove_listener` pattern.
|
||||||
|
*
|
||||||
|
* To reduce code size, only one GenericProxyCache need be instantiated for each language
|
||||||
|
* boundary direction. The pointer types passed to this function can be generic, e.g. `id`,
|
||||||
|
* `shared_ptr<void>`, `jobject`, etc.
|
||||||
|
*
|
||||||
|
* In the types below, "Impl" refers to some interface that is being wrapped, and Proxy refers
|
||||||
|
* to the generated other-language object that wraps it.
|
||||||
|
*/
|
||||||
|
template <typename Traits>
|
||||||
|
class ProxyCache<Traits>::Pimpl {
|
||||||
|
using Key = std::pair<std::type_index, UnowningImplPointer>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
/*
|
||||||
|
* Look up an object in the proxy cache, and create a new one if not found.
|
||||||
|
*
|
||||||
|
* This takes a function pointer, not an arbitrary functor, because we want to minimize
|
||||||
|
* code size: this function should only be instantiated *once* per langauge direction.
|
||||||
|
*/
|
||||||
|
OwningProxyPointer get(const std::type_index & tag,
|
||||||
|
const OwningImplPointer & impl,
|
||||||
|
AllocatorFunction * alloc) {
|
||||||
|
std::unique_lock<std::mutex> lock(m_mutex);
|
||||||
|
UnowningImplPointer ptr = get_unowning(impl);
|
||||||
|
auto existing_proxy_iter = m_mapping.find({tag, ptr});
|
||||||
|
if (existing_proxy_iter != m_mapping.end()) {
|
||||||
|
OwningProxyPointer existing_proxy = upgrade_weak(existing_proxy_iter->second);
|
||||||
|
if (existing_proxy) {
|
||||||
|
return existing_proxy;
|
||||||
|
} else {
|
||||||
|
// The weak reference is expired, so prune it from the map eagerly.
|
||||||
|
m_mapping.erase(existing_proxy_iter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto alloc_result = alloc(impl);
|
||||||
|
m_mapping.emplace(Key{tag, alloc_result.second}, alloc_result.first);
|
||||||
|
return alloc_result.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Erase an object from the proxy cache.
|
||||||
|
*/
|
||||||
|
void remove(const std::type_index & tag, const UnowningImplPointer & impl_unowning) {
|
||||||
|
std::unique_lock<std::mutex> lock(m_mutex);
|
||||||
|
auto it = m_mapping.find({tag, impl_unowning});
|
||||||
|
if (it != m_mapping.end()) {
|
||||||
|
// The entry in the map should already be expired: this is called from Handle's
|
||||||
|
// destructor, so the proxy must already be gone. However, remove() does not
|
||||||
|
// happen atomically with the proxy object becoming weakly reachable. It's
|
||||||
|
// possible that during the window between when the weak-ref holding this proxy
|
||||||
|
// expires and when we enter remove() and take m_mutex, another thread could have
|
||||||
|
// created a new proxy for the same original object and added it to the map. In
|
||||||
|
// that case, `it->second` will contain a live pointer to a different proxy object,
|
||||||
|
// not an expired weak pointer to the Handle currently being destructed. We only
|
||||||
|
// remove the map entry if its pointer is already expired.
|
||||||
|
if (is_expired(it->second)) {
|
||||||
|
m_mapping.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct KeyHash {
|
||||||
|
std::size_t operator()(const Key & k) const {
|
||||||
|
return k.first.hash_code() ^ UnowningImplPointerHash{}(k.second);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct KeyEqual {
|
||||||
|
bool operator()(const Key & lhs, const Key & rhs) const {
|
||||||
|
return lhs.first == rhs.first
|
||||||
|
&& UnowningImplPointerEqual{}(lhs.second, rhs.second);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unordered_map<Key, WeakProxyPointer, KeyHash, KeyEqual> m_mapping;
|
||||||
|
std::mutex m_mutex;
|
||||||
|
|
||||||
|
// Only ProxyCache<Traits>::get_base() can allocate these objects.
|
||||||
|
Pimpl() = default;
|
||||||
|
friend class ProxyCache<Traits>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Traits>
|
||||||
|
void ProxyCache<Traits>::cleanup(const std::shared_ptr<Pimpl> & base,
|
||||||
|
const std::type_index & tag,
|
||||||
|
UnowningImplPointer ptr) {
|
||||||
|
base->remove(tag, ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Magic-static singleton.
|
||||||
|
*
|
||||||
|
* It's possible for someone to hold Djinni-static objects in a global (like a shared_ptr
|
||||||
|
* at namespace scope), which can cause problems at static destruction time: if the proxy
|
||||||
|
* cache itself is destroyed before the other global, use-of-destroyed-object will result.
|
||||||
|
* To fix this, we make it possible to take a shared_ptr to the GenericProxyCache instance,
|
||||||
|
* so it will only be destroyed once all references are gone.
|
||||||
|
*/
|
||||||
|
template <typename Traits>
|
||||||
|
auto ProxyCache<Traits>::get_base() -> const std::shared_ptr<Pimpl> & {
|
||||||
|
static const std::shared_ptr<Pimpl> instance(new Pimpl);
|
||||||
|
// Return by const-ref. This is safe to call any time except during static destruction.
|
||||||
|
// Returning by reference lets us avoid touching the refcount unless needed.
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Traits>
|
||||||
|
auto ProxyCache<Traits>::get(const std::type_index & tag,
|
||||||
|
const OwningImplPointer & impl,
|
||||||
|
AllocatorFunction * alloc)
|
||||||
|
-> OwningProxyPointer {
|
||||||
|
return get_base()->get(tag, impl, alloc);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace djinni
|
185
wrappers/android/mynteye/third_party/djinni/support-lib/proxy_cache_interface.hpp
vendored
Normal file
185
wrappers/android/mynteye/third_party/djinni/support-lib/proxy_cache_interface.hpp
vendored
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
//
|
||||||
|
// Copyright 2015 Dropbox, Inc.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <functional>
|
||||||
|
#include <typeindex>
|
||||||
|
|
||||||
|
namespace djinni {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The template parameters we receive here can be a number of different types: C++ smart
|
||||||
|
* pointers, custom wrappers, or language-specific types (like ObjC's `id` / `__weak id`).
|
||||||
|
* If custom wrapper types are used, like the `JavaWeakRef` type in the JNI library, then
|
||||||
|
* they must implement `.get()` or `.lock()` by analogy with C++'s smart pointers.
|
||||||
|
*
|
||||||
|
* We assume that built-in types are pointer-compatible. This is the case with ObjC: for
|
||||||
|
* example, __weak id pointers can be implicitly converted to __strong id, and so on.
|
||||||
|
*
|
||||||
|
* (The helper for .lock() is only used by proxy_cache_impl.hpp, so it's defined there.)
|
||||||
|
*/
|
||||||
|
template <typename T> static inline auto get_unowning(const T & ptr) -> decltype(ptr.get()) {
|
||||||
|
return ptr.get();
|
||||||
|
}
|
||||||
|
template <typename T> static inline T * get_unowning(T * ptr) { return ptr; }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ProxyCache provides a mechanism for re-using proxy objects generated in one language
|
||||||
|
* that wrap around implementations in a different language. This is for correctness, not
|
||||||
|
* just performance: when we pass the same object across a language boundary twice, we want
|
||||||
|
* to get the same proxy object on the other side each time, so that identity semantics
|
||||||
|
* behave as expected.
|
||||||
|
*
|
||||||
|
* ProxyCache is instantiated with a Traits class that must contain the following typedefs.
|
||||||
|
* Examples refer to the use of ProxyCache to cache C++ wrappers around ObjC objects.
|
||||||
|
* ProxyCache itself is generic (type-erased), though Handle is not, so we use e.g. `id`
|
||||||
|
* and `shared_ptr<void>` rather than any specific types.
|
||||||
|
*
|
||||||
|
* - UnowningImplPointer:
|
||||||
|
* a non-owning pointer to an object being wrapped, e.g. __unsafe_unretained id
|
||||||
|
* - OwningImplPointer:
|
||||||
|
* a strong owning pointer to an object being wrapped, e.g. __strong id
|
||||||
|
* - OwningProxyPointer:
|
||||||
|
* a strong owning pointer to a wrapper, e.g. std::shared_ptr<void>
|
||||||
|
* - WeakProxyPointer:
|
||||||
|
* a safe weak pointer to a wrapper, e.g. std::weak_ptr<void>
|
||||||
|
* - UnowningImplPointerHash:
|
||||||
|
* a hasher for UnowningImplPointer, usually std::hash<UnowningImplPointer>, unless
|
||||||
|
* std::hash doesn't work with UnowningImplPointer in which case a custom type can be
|
||||||
|
* provided.
|
||||||
|
* - UnowningImplPointerEqual:
|
||||||
|
* an equality predicate for UnowningImplPointer, like std::equal_to<UnowningImplPointer>.
|
||||||
|
* In some cases (e.g. Java) a custom equality predicate may be needed.
|
||||||
|
*
|
||||||
|
* Generally, ProxyCache will be explicitly instantiated in one source file with C++11's
|
||||||
|
* `extern template` mechanism. The WeakProxyPointer, UnowningImplPointerHash, and
|
||||||
|
* UnowningImplPointerEqual types can be incomplete except for where the explicit
|
||||||
|
* instantiation is actually defined.
|
||||||
|
*
|
||||||
|
* Here's an overview of the structure:
|
||||||
|
*
|
||||||
|
* ______________ std::pair<ImplType,
|
||||||
|
* WeakProxyPonter | | UnowningImplPointer>
|
||||||
|
* - - - - - - - -| ProxyCache |- - - - - - - - - -
|
||||||
|
* | | | |
|
||||||
|
* | |______________| |
|
||||||
|
* | |
|
||||||
|
* ____v____ ______________ ______________v__________
|
||||||
|
* | | | | | |
|
||||||
|
* | (Proxy | ===> | ProxyCache:: | =====> | (Impl object providing |
|
||||||
|
* | object) | ^ | Handle<T> | T | actual functionality) |
|
||||||
|
* |_________| . |______________| ^ |_________________________|
|
||||||
|
* . .
|
||||||
|
* ( can be member, base, ) ( T is a generally a specific )
|
||||||
|
* ( or cross-language ) ( owning type like id<Foo>, )
|
||||||
|
* ( reference like jlong ) ( shared_ptr<Foo>, or GlobalRef )
|
||||||
|
*
|
||||||
|
* The cache contains a map from pair<ImplType, UnowningImplPointer>
|
||||||
|
* to WeakProxyPointer, allowing it to answer the question: "given this
|
||||||
|
* impl, do we already have a proxy in existence?"
|
||||||
|
*
|
||||||
|
* We use one map for all translated types, rather than a separate one for each type,
|
||||||
|
* to minimize duplication of code and make it so the unordered_map is as contained as
|
||||||
|
* possible.
|
||||||
|
*/
|
||||||
|
template <typename Traits>
|
||||||
|
class ProxyCache {
|
||||||
|
public:
|
||||||
|
using UnowningImplPointer = typename Traits::UnowningImplPointer;
|
||||||
|
using OwningImplPointer = typename Traits::OwningImplPointer;
|
||||||
|
using OwningProxyPointer = typename Traits::OwningProxyPointer;
|
||||||
|
using WeakProxyPointer = typename Traits::WeakProxyPointer;
|
||||||
|
using UnowningImplPointerHash = typename Traits::UnowningImplPointerHash;
|
||||||
|
using UnowningImplPointerEqual = typename Traits::UnowningImplPointerEqual;
|
||||||
|
class Pimpl;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Each proxy object must own a Handle. The Handle carries a strong reference to whatever
|
||||||
|
* the proxy wraps. When `ProxyCache::get()` creates a proxy, it also adds the proxy to
|
||||||
|
* the global proxy cache; Handle::~Handle() removes the reference from the cache.
|
||||||
|
*
|
||||||
|
* The Handle can be held by the proxy in any of a number of ways: as a C++ member or
|
||||||
|
* base, as an ObjC instance variable, or across an FFI boundary (a Java object might
|
||||||
|
* contain the address of a Handle as a `long` and delete it in the destructor.)
|
||||||
|
*
|
||||||
|
* T is generally a more-specialized version of OwningImplPointer. For example, when
|
||||||
|
* managing C++ proxies for ObjC objects, OwningImplPointer would be `id`, and the C++
|
||||||
|
* proxy class `MyInterface` which wraps `@protocol DBMyInterface` would contain a
|
||||||
|
* `Handle<id<DBMyInterface>>`.
|
||||||
|
*
|
||||||
|
* TagType should be the same type that was passed in to `get()` when this handle was
|
||||||
|
* created. Normally this is the same as T (a specialized OwningImplPointer), but in
|
||||||
|
* cases like Java where all object types are uniformly represented as `jobject` in C++,
|
||||||
|
* another type may be used.
|
||||||
|
*/
|
||||||
|
template <typename T, typename TagType = T>
|
||||||
|
class Handle {
|
||||||
|
public:
|
||||||
|
template <typename... Args> Handle(Args &&... args)
|
||||||
|
: m_cache(get_base()), m_obj(std::forward<Args>(args)...) {}
|
||||||
|
Handle(const Handle &) = delete;
|
||||||
|
Handle & operator=(const Handle &) = delete;
|
||||||
|
~Handle() { if (m_obj) cleanup(m_cache, typeid(TagType), get_unowning(m_obj)); }
|
||||||
|
|
||||||
|
void assign(const T & obj) { m_obj = obj; }
|
||||||
|
|
||||||
|
const T & get() const & noexcept { return m_obj; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const std::shared_ptr<Pimpl> m_cache;
|
||||||
|
T m_obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Function typedef for helpers passed in to allocate new objects.
|
||||||
|
* To reduce code size, the proxy cache type-erases the objects inside it.
|
||||||
|
*
|
||||||
|
* An allocator function takes an OwningImplPointer to the source language object,
|
||||||
|
* and returns a newly-created proxy.
|
||||||
|
*
|
||||||
|
* In Java, an OwningImplPointer does not provide the same identity semantics as the
|
||||||
|
* underlying object. (A JNI 'jobject' can be one of a few different types of reference,
|
||||||
|
* and JNI is structured to allow copying GCs, so an object's address might change over
|
||||||
|
* time.) This is why ProxyCache takes hasher and comparator paramters. In particular,
|
||||||
|
* the OwningImplPointer passed into the allocator might be a JNI local reference. The
|
||||||
|
* allocator will create a GlobalRef to the impl object and store it in the returned proxy.
|
||||||
|
*
|
||||||
|
* Because we don't constrain how Handle objects are held, there's no generic way for the
|
||||||
|
* proxy cache to get the GlobalRef out of the returned proxy object, so AllocatorFunction
|
||||||
|
* returns a pair: the first element is the newly created proxy, and the second is an
|
||||||
|
* UnowningImplPointer that will be used as a key in the map.
|
||||||
|
*/
|
||||||
|
using AllocatorFunction =
|
||||||
|
std::pair<OwningProxyPointer, UnowningImplPointer>(const OwningImplPointer &);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the existing proxy for `impl`, if any. If not, create one by calling `alloc`,
|
||||||
|
* store a weak reference to it in the proxy cache, and return it.
|
||||||
|
*/
|
||||||
|
static OwningProxyPointer get(const std::type_index &,
|
||||||
|
const OwningImplPointer & impl,
|
||||||
|
AllocatorFunction * alloc);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void cleanup(const std::shared_ptr<Pimpl> &,
|
||||||
|
const std::type_index &,
|
||||||
|
UnowningImplPointer);
|
||||||
|
static const std::shared_ptr<Pimpl> & get_base();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace djinni
|
Loading…
Reference in New Issue
Block a user