Bug 1192043 - Add support for proxying native calls; r=snorp

If a C++ class implements native calls that all return void, it can
choose to have those calls go through a custom proxy function by
inheriting from mozilla::jni::UsesNativeCallProxy and override the
ProxyNativeCall member.

ProxyNativeCall accepts a rvalue reference to a
functor object specific to each call, and can alter the calling
environment (e.g. dispatch the call to another thread) before issuing
the actual native call through the functor's operator().

Any JNI refs contained in the call are automatically turned into global
refs so that the call can continue to work outside of the original JNI
call.

Native call proxy will be used to implement automatic dispatching of
native calls from the main thread to run on the Gecko thread.
This commit is contained in:
Jim Chen 2015-08-25 14:52:16 -04:00
parent 32507efd58
commit dc909a59fe

View File

@ -3,10 +3,13 @@
#include <jni.h>
#include "mozilla/IndexSequence.h"
#include "mozilla/Move.h"
#include "mozilla/Tuple.h"
#include "mozilla/TypeTraits.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/WeakPtr.h"
#include "mozilla/unused.h"
#include "mozilla/jni/Accessors.h"
#include "mozilla/jni/Refs.h"
#include "mozilla/jni/Types.h"
@ -172,6 +175,230 @@ struct NativePtr<Impl, /* UseWeakPtr = */ true>
} // namespace
/**
* For C++ classes whose native methods all return void, they can choose to
* have the native calls go through a proxy by inheriting from
* mozilla::jni::UsesNativeCallProxy, and overriding the OnNativeCall member.
* Subsequently, every native call is automatically wrapped in a functor
* object, and the object is passed to OnNativeCall. The OnNativeCall
* implementation can choose to invoke the call, save it, dispatch it to a
* different thread, etc. Each copy of functor may only be invoked once.
*
* class MyClass : public MyJavaClass::Natives<MyClass>
* , public mozilla::jni::UsesNativeCallProxy
* {
* // ...
*
* template<class Functor>
* class ProxyRunnable final : public Runnable
* {
* Functor mCall;
* public:
* ProxyRunnable(Functor&& call) : mCall(mozilla::Move(call)) {}
* virtual void run() override { mCall(); }
* };
*
* public:
* template<class Functor>
* static void OnNativeCall(Functor&& call)
* {
* RunOnAnotherThread(new ProxyRunnable(mozilla::Move(call)));
* }
* };
*/
struct UsesNativeCallProxy
{
template<class Functor>
static void OnNativeCall(Functor&& call)
{
// The default behavior is to invoke the call right away.
call();
}
};
namespace {
// ProxyArg is used to handle JNI ref arguments for proxies. Because a proxied
// call may happen outside of the original JNI native call, we must save all
// JNI ref arguments as global refs to avoid the arguments going out of scope.
template<typename T>
struct ProxyArg
{
static_assert(mozilla::IsPod<T>::value, "T must be primitive type");
// Primitive types can be saved by value.
typedef T Type;
typedef typename TypeAdapter<T>::JNIType JNIType;
static void Clear(JNIEnv* env, Type&) {}
static Type From(JNIEnv* env, JNIType val)
{
return TypeAdapter<T>::ToNative(env, val);
}
};
template<class T>
struct ProxyArg<Ref<T>>
{
// Ref types need to be saved by global ref.
typedef typename T::GlobalRef Type;
typedef typename TypeAdapter<Ref<T>>::JNIType JNIType;
static void Clear(JNIEnv* env, Type& ref) { ref.Clear(env); }
static Type From(JNIEnv* env, JNIType val)
{
return Type(env, T::Ref::From(val));
}
};
template<typename T> struct ProxyArg<const T&> : ProxyArg<T> {};
template<> struct ProxyArg<Param<String>> : ProxyArg<Ref<String>> {};
// ProxyNativeCall implements the functor object that is passed to
// UsesNativeCallProxy::OnNativeCall
template<class Impl, class Owner, bool IsStatic,
bool HasThisArg /* has instance/class local ref in the call */,
typename... Args>
class ProxyNativeCall
{
template<class T, class I, class A, bool S, bool V>
friend class NativeStubImpl;
// "this arg" refers to the ClassObject::LocalRef (for static methods) or
// Owner::LocalRef (for instance methods) that we optionally (as indicated
// by HasThisArg) pass into the destination C++ function.
typedef typename mozilla::Conditional<IsStatic,
ClassObject, Owner>::Type ThisArgClass;
typedef typename mozilla::Conditional<IsStatic,
jclass, jobject>::Type ThisArgJNIType;
// Type signature of the destination C++ function, which matches the
// Method template parameter in NativeStubImpl::Wrap.
typedef typename mozilla::Conditional<IsStatic,
typename mozilla::Conditional<HasThisArg,
void (*) (const ClassObject::LocalRef&, Args...),
void (*) (Args...)>::Type,
typename mozilla::Conditional<HasThisArg,
void (Impl::*) (const typename Owner::LocalRef&, Args...),
void (Impl::*) (Args...)>::Type>::Type NativeCallType;
// Destination C++ function.
const NativeCallType mNativeCall;
// Saved this arg.
typename ThisArgClass::GlobalRef mThisArg;
// Saved arguments.
mozilla::Tuple<typename ProxyArg<Args>::Type...> mArgs;
ProxyNativeCall(NativeCallType nativeCall,
JNIEnv* env,
ThisArgJNIType thisArg,
typename ProxyArg<Args>::JNIType... args)
: mNativeCall(nativeCall)
, mThisArg(env, ThisArgClass::Ref::From(thisArg))
, mArgs(ProxyArg<Args>::From(env, args)...)
{}
// We cannot use IsStatic and HasThisArg directly (without going through
// extra hoops) because GCC complains about invalid overloads, so we use
// another pair of template parameters, Static and ThisArg.
template<bool Static, bool ThisArg, size_t... Indices>
typename mozilla::EnableIf<Static && ThisArg, void>::Type
Call(const ClassObject::LocalRef& cls, mozilla::IndexSequence<Indices...>)
{
(*mNativeCall)(cls, mozilla::Get<Indices>(mArgs)...);
}
template<bool Static, bool ThisArg, size_t... Indices>
typename mozilla::EnableIf<Static && !ThisArg, void>::Type
Call(const ClassObject::LocalRef& cls, mozilla::IndexSequence<Indices...>)
{
(*mNativeCall)(mozilla::Get<Indices>(mArgs)...);
}
template<bool Static, bool ThisArg, size_t... Indices>
typename mozilla::EnableIf<!Static && ThisArg, void>::Type
Call(const typename Owner::LocalRef& inst,
mozilla::IndexSequence<Indices...>)
{
Impl* const impl = NativePtr<Impl>::Get(inst);
HandleUncaughtException(inst.Env());
(impl->*mNativeCall)(inst, mozilla::Get<Indices>(mArgs)...);
}
template<bool Static, bool ThisArg, size_t... Indices>
typename mozilla::EnableIf<!Static && !ThisArg, void>::Type
Call(const typename Owner::LocalRef& inst,
mozilla::IndexSequence<Indices...>)
{
Impl* const impl = NativePtr<Impl>::Get(inst);
HandleUncaughtException(inst.Env());
(impl->*mNativeCall)(mozilla::Get<Indices>(mArgs)...);
}
template<size_t... Indices>
void Clear(JNIEnv* env, mozilla::IndexSequence<Indices...>)
{
int dummy[] = {
(ProxyArg<Args>::Clear(env, Get<Indices>(mArgs)), 0)...
};
mozilla::unused << dummy;
}
public:
static const bool isStatic = IsStatic;
ProxyNativeCall(ProxyNativeCall&&) = default;
ProxyNativeCall(const ProxyNativeCall&) = default;
// Get class ref for static calls or object ref for instance calls.
typename ThisArgClass::Param GetThisArg() { return mThisArg; }
// Return if target is the given function pointer / pointer-to-member.
// Because we can only compare pointers of the same type, we use a
// templated overload that is chosen only if given a different type of
// pointer than our target pointer type.
bool IsTarget(NativeCallType call) { return call == mNativeCall; }
template<typename T> bool IsTarget(T&&) { return false; }
void operator()()
{
JNIEnv* const env = GetEnvForThread();
typename ThisArgClass::LocalRef thisArg(env, mThisArg);
Call<IsStatic, HasThisArg>(
thisArg, typename IndexSequenceFor<Args...>::Type());
// Clear all saved global refs. We do this after the call is invoked,
// and not inside the destructor because we already have a JNIEnv here,
// so it's more efficient to clear out the saved args here. The
// downside is that the call can only be invoked once.
Clear(env, typename IndexSequenceFor<Args...>::Type());
}
};
// We can only use Impl::OnNativeCall when Impl is derived from
// UsesNativeCallProxy, otherwise it's a compile error. Therefore, the real
// Dispatch function is conditional on UsesNativeCallProxy being a base class
// of Impl. Otherwise, the dummy Dispatch function below that is chosen during
// overload resolution. Because Dispatch is called with an rvalue, the &&
// version is always chosen before the const& version, if possible.
template<class Impl, class O, bool S, bool V, typename... A>
typename mozilla::EnableIf<
mozilla::IsBaseOf<UsesNativeCallProxy, Impl>::value, void>::Type
Dispatch(ProxyNativeCall<Impl, O, S, V, A...>&& call)
{
Impl::OnNativeCall(mozilla::Move(call));
}
template<typename T>
void Dispatch(const T&) {}
} // namespace
template<class Cls, class Impl> class NativeImpl;
namespace detail {
@ -203,6 +430,9 @@ public:
static ReturnJNIType Wrap(JNIEnv* env, jobject instance,
typename TypeAdapter<Args>::JNIType... args)
{
static_assert(!mozilla::IsBaseOf<UsesNativeCallProxy, Impl>::value,
"Native call proxy only supports void return type");
Impl* const impl = NativePtr<Impl>::Get(env, instance);
if (!impl) {
return ReturnJNIType();
@ -216,6 +446,9 @@ public:
static ReturnJNIType Wrap(JNIEnv* env, jobject instance,
typename TypeAdapter<Args>::JNIType... args)
{
static_assert(!mozilla::IsBaseOf<UsesNativeCallProxy, Impl>::value,
"Native call proxy only supports void return type");
Impl* const impl = NativePtr<Impl>::Get(env, instance);
if (!impl) {
return ReturnJNIType();
@ -241,6 +474,12 @@ public:
static void Wrap(JNIEnv* env, jobject instance,
typename TypeAdapter<Args>::JNIType... args)
{
if (mozilla::IsBaseOf<UsesNativeCallProxy, Impl>::value) {
Dispatch(ProxyNativeCall<
Impl, Owner, /* IsStatic */ false, /* HasThisArg */ false,
Args...>(Method, env, instance, args...));
return;
}
Impl* const impl = NativePtr<Impl>::Get(env, instance);
if (!impl) {
return;
@ -253,6 +492,12 @@ public:
static void Wrap(JNIEnv* env, jobject instance,
typename TypeAdapter<Args>::JNIType... args)
{
if (mozilla::IsBaseOf<UsesNativeCallProxy, Impl>::value) {
Dispatch(ProxyNativeCall<
Impl, Owner, /* IsStatic */ false, /* HasThisArg */ true,
Args...>(Method, env, instance, args...));
return;
}
Impl* const impl = NativePtr<Impl>::Get(env, instance);
if (!impl) {
return;
@ -266,6 +511,11 @@ public:
template<void (NativeImpl<Owner, Impl>::*Method) (const typename Owner::LocalRef&)>
static void Wrap(JNIEnv* env, jobject instance)
{
if (mozilla::IsBaseOf<UsesNativeCallProxy, Impl>::value) {
Dispatch(ProxyNativeCall<Impl, Owner, /* IsStatic */ false,
/* HasThisArg */ true>(Method, env, instance));
return;
}
Impl* const impl = NativePtr<Impl>::Get(env, instance);
if (!impl) {
return;
@ -290,6 +540,9 @@ public:
static ReturnJNIType Wrap(JNIEnv* env, jclass,
typename TypeAdapter<Args>::JNIType... args)
{
static_assert(!mozilla::IsBaseOf<UsesNativeCallProxy, Impl>::value,
"Native call proxy only supports void return type");
return TypeAdapter<ReturnType>::FromNative(env,
(*Method)(TypeAdapter<Args>::ToNative(env, args)...));
}
@ -299,6 +552,9 @@ public:
static ReturnJNIType Wrap(JNIEnv* env, jclass cls,
typename TypeAdapter<Args>::JNIType... args)
{
static_assert(!mozilla::IsBaseOf<UsesNativeCallProxy, Impl>::value,
"Native call proxy only supports void return type");
auto clazz = ClassObject::LocalRef::Adopt(env, cls);
const auto res = TypeAdapter<ReturnType>::FromNative(env,
(*Method)(clazz, TypeAdapter<Args>::ToNative(env, args)...));
@ -312,12 +568,20 @@ template<class Traits, class Impl, typename... Args>
class NativeStubImpl<Traits, Impl, jni::Args<Args...>,
/* IsStatic = */ true, /* IsVoid = */ true>
{
typedef typename Traits::Owner Owner;
public:
// Static method
template<void (*Method) (Args...)>
static void Wrap(JNIEnv* env, jclass,
static void Wrap(JNIEnv* env, jclass cls,
typename TypeAdapter<Args>::JNIType... args)
{
if (mozilla::IsBaseOf<UsesNativeCallProxy, Impl>::value) {
Dispatch(ProxyNativeCall<
Impl, Owner, /* IsStatic */ true, /* HasThisArg */ false,
Args...>(Method, env, cls, args...));
return;
}
(*Method)(TypeAdapter<Args>::ToNative(env, args)...);
}
@ -326,6 +590,12 @@ public:
static void Wrap(JNIEnv* env, jclass cls,
typename TypeAdapter<Args>::JNIType... args)
{
if (mozilla::IsBaseOf<UsesNativeCallProxy, Impl>::value) {
Dispatch(ProxyNativeCall<
Impl, Owner, /* IsStatic */ true, /* HasThisArg */ true,
Args...>(Method, env, cls, args...));
return;
}
auto clazz = ClassObject::LocalRef::Adopt(env, cls);
(*Method)(clazz, TypeAdapter<Args>::ToNative(env, args)...);
clazz.Forget();