refactor(android): move djinni jni files to thrid_party

This commit is contained in:
John Zhao 2019-01-17 11:58:55 +08:00
parent d6ff3470f1
commit 5eb3174edb
8 changed files with 2312 additions and 1 deletions

View File

@ -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()

View 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

View 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

View 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();
}

View 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

View 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

View 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

View 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