/* * Copyright (c) Facebook, Inc. and its affiliates. * * 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 #include #include using namespace facebook::jni; class TestException : public std::runtime_error { public: TestException() : std::runtime_error("fail") {} }; // The C++ half of a hybrid class must extend HybridClass like this. // HybridClass is default constructible. class TestHybridClass : public facebook::jni::HybridClass { public: static constexpr const char* const kJavaDescriptor = "Lcom/facebook/jni/HybridTests$TestHybridClass;"; TestHybridClass() {} TestHybridClass(int i, std::string s, bool b) : i_(i), s_(std::move(s)), b_(b) {} // The C++ implementation of initHybrid must be a static C++ method, // since it has no C++ instance (it creates it). But it's a normal // java method, because it gets a java this passed in (which is // often unused). It must call makeCxxInstance and return its // result. static jhybriddata initHybrid(alias_ref, jint i, jstring s, jboolean b) { // The arguments will be forwarded to the ctor, and the result // will be saved in mHybridPointer in the java object. return makeCxxInstance(i, wrap_alias(s)->toStdString(), b).release(); } static local_ref initHybrid2(alias_ref) { return makeCxxInstance(); } // It's also ok to make initHybrid work like a factory, which // eliminates the need to define a ctor and reduces boilerplate // code. This is a member, so fields can be initialized directly. static local_ref initHybrid3(alias_ref, std::string s, int i, bool b) { auto cxxPart = std::unique_ptr(new TestHybridClass); cxxPart->s_ = std::move(s); cxxPart->i_ = i; cxxPart->b_ = b; return makeHybridData(std::move(cxxPart)); } jint getInt() { return i_; } std::string getString() { return s_; } const char* getCharString() { return s_.c_str(); } void setBoth(int i, std::string s) { i_ = i; s_ = s; } bool copy1(alias_ref other) { i_ = cthis(other)->i_; s_ = cthis(other)->s_; return true; } // This returns a boolean to test more code paths, not for any // functional reason. jboolean copy2(TestHybridClass* other) { i_ = other->i_; s_ = other->s_; return true; } void oops() { throw TestException(); } void setGlobal(alias_ref str) { global_ = make_global(str); } global_ref getGlobal1() { return global_; } const global_ref& getGlobal2() { return global_; } static jhybridobject makeWithTwo(alias_ref jthis) { // The args are passed to java, with autoconversion. return newObjectJavaArgs(2, "two", true).release(); } static local_ref makeWithThree(alias_ref jthis) { // The args are passed to C++ initialization directly. return newObjectCxxArgs(3, "three", true); } static void mapException(std::exception_ptr ex) { try { std::rethrow_exception(ex); } catch (const TestException& ex) { throwNewJavaException("java/lang/ArrayStoreException", ""); } } static void autoconvertMany(alias_ref) { // Autoconversion to jstring creates a local ref. Make sure is is // released properly. The default local table size is 512. for (int i = 0; i < 1000; ++i) { newObjectJavaArgs(1000, "thousand", false); newObjectJavaArgs(1001, make_jstring("thousand+1"), false); } } // Declaring a register function for each class makes it easy to // manage the process. static void registerNatives() { registerHybrid({ // Test registration of static method returning void. makeNativeMethod("initHybrid", TestHybridClass::initHybrid), // overloaded members are ugly to get pointers to. Use a // different name for each C++ method to keep things readable. makeNativeMethod("initHybrid", TestHybridClass::initHybrid2), makeNativeMethod("initHybrid", TestHybridClass::initHybrid3), // C++ members can be registered directly. The C++ instance // created by makeCxxInstance will be retrieved from the // HybridData, and the method will be invoked with the // arguments passed to java. These test registration of // method pointers returning non-void. makeNativeMethod("getInt", TestHybridClass::getInt), makeNativeMethod("getString", TestHybridClass::getString), makeNativeMethod("getCharString", TestHybridClass::getCharString), makeNativeMethod("setBoth", TestHybridClass::setBoth), makeNativeMethod("copy1", TestHybridClass::copy1), makeNativeMethod("copy2", TestHybridClass::copy2), makeNativeMethod("oops", TestHybridClass::oops), makeNativeMethod("setGlobal", TestHybridClass::setGlobal), makeNativeMethod("getGlobal1", TestHybridClass::getGlobal1), makeNativeMethod("getGlobal2", TestHybridClass::getGlobal2), makeNativeMethod("makeWithTwo", TestHybridClass::makeWithTwo), makeNativeMethod("makeWithThree", TestHybridClass::makeWithThree), makeNativeMethod("autoconvertMany", TestHybridClass::autoconvertMany), }); } private: friend HybridBase; int i_ = 0; std::string s_; bool b_ = false; global_ref global_; }; class AbstractTestHybrid : public facebook::jni::HybridClass { public: static constexpr const char* const kJavaDescriptor = "Lcom/facebook/jni/HybridTests$AbstractTestHybrid;"; AbstractTestHybrid(int nn) : nativeNum_(nn) {} int nativeNum() { return nativeNum_; } // This is different than the java method! virtual int sum() = 0; static void registerNatives() { registerHybrid({ makeNativeMethod("nativeNum", AbstractTestHybrid::nativeNum), }); } private: int nativeNum_; }; class ConcreteTestHybrid : public facebook::jni:: HybridClass { public: static constexpr const char* const kJavaDescriptor = "Lcom/facebook/jni/HybridTests$ConcreteTestHybrid;"; static local_ref initHybrid(alias_ref, int nn, int cn) { return makeCxxInstance(nn, cn); } int concreteNum() { return concreteNum_; } int sum() override { return nativeNum() + concreteNum(); } static void registerNatives() { registerHybrid({ makeNativeMethod("initHybrid", ConcreteTestHybrid::initHybrid), makeNativeMethod("concreteNum", ConcreteTestHybrid::concreteNum), }); } private: friend HybridBase; ConcreteTestHybrid(int nn, int cn) : HybridBase(nn), concreteNum_(cn) {} int concreteNum_; }; static jboolean cxxTestInheritance(alias_ref, AbstractTestHybrid* ath) { bool ret = true; ret &= ath->nativeNum() == 5; ret &= ath->sum() == 11; auto cth = dynamic_cast(ath); ret &= cth != nullptr; ret &= cth->concreteNum() == 6; return ret; } static local_ref makeAbstractHybrid( alias_ref) { auto cth = ConcreteTestHybrid::newObjectJavaArgs(0, 0, 0); weak_ref wcth = make_weak(cth); weak_ref wath = wcth; local_ref ath = cth; return ConcreteTestHybrid::newObjectJavaArgs( cthis(ath)->nativeNum() + 7, 8, 9); } struct Base : public JavaClass { static constexpr const char* const kJavaDescriptor = "Lcom/facebook/jni/HybridTests$Base;"; }; class Derived : public HybridClass { public: static constexpr const char* const kJavaDescriptor = "Lcom/facebook/jni/HybridTests$Derived;"; private: friend HybridBase; Derived() {} }; struct Destroyable : public HybridClass { public: static constexpr const char* const kJavaDescriptor = "Lcom/facebook/jni/HybridTests$Destroyable;"; ~Destroyable() override { std::lock_guard lk(*mtx_); *dead_ = true; cv_->notify_one(); } using HybridBase::makeCxxInstance; private: friend HybridBase; Destroyable( std::shared_ptr mtx, std::shared_ptr dead, std::shared_ptr cv) : mtx_(mtx), dead_(dead), cv_(cv) {} std::shared_ptr mtx_; std::shared_ptr dead_; std::shared_ptr cv_; }; static jboolean cxxTestHybridDestruction(alias_ref) { auto mtx = std::make_shared(); auto dead = std::make_shared(false); auto cv = std::make_shared(); Destroyable::makeCxxInstance(mtx, dead, cv); std::unique_lock lk(*mtx); cv->wait(lk, [&] { return dead; }); return JNI_TRUE; } static jboolean cxxTestDerivedJavaClass(alias_ref) { bool ret = true; auto derivedJava = Derived::newObjectCxxArgs(); ret &= derivedJava ? true : false; ret &= derivedJava->isInstanceOf(Derived::javaClassLocal()); ret &= derivedJava->isInstanceOf(Base::javaClassLocal()); auto javaPtr = derivedJava.get(); ret &= dynamic_cast(javaPtr) != nullptr; ret &= dynamic_cast(javaPtr) != nullptr; ret &= dynamic_cast(javaPtr) != nullptr; return ret; } class TestHybridClassBase : public facebook::jni::HybridClass { public: static constexpr const char* const kJavaDescriptor = "Lcom/facebook/jni/HybridTests$TestHybridClassBase;"; TestHybridClassBase() {} explicit TestHybridClassBase(int i) : i_(i) {} static void initHybrid(alias_ref o, jint i) { // The arguments will be forwarded to the ctor, and the result // will be saved in mHybridPointer in the java object. setCxxInstance(o, i); } static void initHybrid2(alias_ref o) { setCxxInstance(o); } virtual jint getInt() { return i_; } void setInt(int i) { i_ = i; } static local_ref makeWithThree( alias_ref /* unused */) { // The args are passed to C++ initialization directly. return newObjectCxxArgs(3); } static void registerNatives() { registerHybrid({ makeNativeMethod("initHybrid", TestHybridClassBase::initHybrid), makeNativeMethod("initHybrid", TestHybridClassBase::initHybrid2), makeNativeMethod("getInt", TestHybridClassBase::getInt), makeNativeMethod("setInt", TestHybridClassBase::setInt), makeNativeMethod("makeWithThree", TestHybridClassBase::makeWithThree), }); } private: int i_ = 0; }; void RegisterTestHybridClass() { TestHybridClass::registerNatives(); AbstractTestHybrid::registerNatives(); ConcreteTestHybrid::registerNatives(); TestHybridClassBase::registerNatives(); registerNatives( "com/facebook/jni/HybridTests", { makeNativeMethod("cxxTestInheritance", cxxTestInheritance), makeNativeMethod("makeAbstractHybrid", makeAbstractHybrid), makeNativeMethod("cxxTestDerivedJavaClass", cxxTestDerivedJavaClass), makeNativeMethod( "cxxTestHybridDestruction", cxxTestHybridDestruction), }); }