diff --git a/src/jni/Android.mk b/src/jni/Android.mk index 8faa8b232..0cece263d 100644 --- a/src/jni/Android.mk +++ b/src/jni/Android.mk @@ -65,7 +65,7 @@ LOCAL_MODULE := NativeScript LOCAL_SRC_FILES := com_tns_AssetExtractor.cpp AssetExtractor.cpp\ com_tns_Runtime.cpp Runtime.cpp \ com_tns_JsDebugger.cpp \ - JEnv.cpp DirectBuffer.cpp NativeScriptException.cpp \ + JEnv.cpp DirectBuffer.cpp ArrayBufferHelper.cpp NativeScriptException.cpp \ JsDebugger.cpp SimpleAllocator.cpp \ CallbackHandlers.cpp MetadataNode.cpp MetadataTreeNode.cpp MetadataReader.cpp \ MethodCache.cpp JavaObjectArrayCache.cpp \ diff --git a/src/jni/ArrayBufferHelper.cpp b/src/jni/ArrayBufferHelper.cpp new file mode 100644 index 000000000..bc8b0ec54 --- /dev/null +++ b/src/jni/ArrayBufferHelper.cpp @@ -0,0 +1,115 @@ +#include "ArrayBufferHelper.h" +#include "JEnv.h" +#include "V8GlobalHelpers.h" +#include "NativeScriptException.h" +#include + + +using namespace v8; +using namespace tns; + +ArrayBufferHelper::ArrayBufferHelper() + : m_objectManager(nullptr), m_ByteBufferClass(nullptr), m_isDirectMethodID(nullptr) +{ +} + +void ArrayBufferHelper::CreateConvertFunctions(Isolate *isolate, const Local& global, ObjectManager *objectManager) +{ + m_objectManager = objectManager; + + auto extData = External::New(isolate, this); + auto fromFunc = FunctionTemplate::New(isolate, CreateFromCallbackStatic, extData)->GetFunction(); + auto ctx = isolate->GetCurrentContext(); + auto arrBufferCtorFunc = global->Get(ConvertToV8String("ArrayBuffer")).As(); + arrBufferCtorFunc->Set(ctx, ConvertToV8String("from"), fromFunc); +} + +void ArrayBufferHelper::CreateFromCallbackStatic(const FunctionCallbackInfo& info) +{ + try + { + auto extData = info.Data().As(); + auto thiz = reinterpret_cast(extData->Value()); + thiz->CreateFromCallbackImpl(info); + } + catch (NativeScriptException& e) + { + e.ReThrowToV8(); + } + catch (std::exception e) { + std::stringstream ss; + ss << "Error: c++ exception: " << e.what() << std::endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToV8(); + } + catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToV8(); + } +} + +void ArrayBufferHelper::CreateFromCallbackImpl(const FunctionCallbackInfo& info) +{ + auto isolate = info.GetIsolate(); + auto len = info.Length(); + + if (len != 1) + { + throw NativeScriptException("Wrong number of arguments (1 expected)"); + } + + auto arg = info[0]; + + if (!arg->IsObject()) + { + throw NativeScriptException("Wrong type of argument (object expected)"); + } + + auto argObj = arg.As(); + + auto obj = m_objectManager->GetJavaObjectByJsObject(argObj); + + if (obj.IsNull()) + { + throw NativeScriptException("Wrong type of argument (object expected)"); + } + + JEnv env; + + if (m_ByteBufferClass == nullptr) + { + m_ByteBufferClass = env.FindClass("java/nio/ByteBuffer"); + assert(m_ByteBufferClass != nullptr); + } + + auto isByteBuffer = env.IsInstanceOf(obj, m_ByteBufferClass); + + if (!isByteBuffer) + { + throw NativeScriptException("Wrong type of argument (ByteBuffer expected)"); + } + + if (m_isDirectMethodID == nullptr) + { + m_isDirectMethodID = env.GetMethodID(m_ByteBufferClass, "isDirect", "()Z"); + assert(m_isDirectMethodID != nullptr); + } + + auto ret = env.CallBooleanMethod(obj, m_isDirectMethodID); + + auto isDirectBuffer = ret == JNI_TRUE; + + if (!isDirectBuffer) + { + throw NativeScriptException("Direct ByteBuffer expected)"); + } + + auto data = env.GetDirectBufferAddress(obj); + auto size = env.GetDirectBufferCapacity(obj); + + auto arrayBuffer = ArrayBuffer::New(isolate, data, size); + auto ctx = isolate->GetCurrentContext(); + arrayBuffer->Set(ctx, ConvertToV8String("nativeObject"), argObj); + + info.GetReturnValue().Set(arrayBuffer); +} diff --git a/src/jni/ArrayBufferHelper.h b/src/jni/ArrayBufferHelper.h new file mode 100644 index 000000000..a5ffde6d4 --- /dev/null +++ b/src/jni/ArrayBufferHelper.h @@ -0,0 +1,31 @@ +#ifndef ARRAYBUFFERHELPER_H_ +#define ARRAYBUFFERHELPER_H_ + +#include "v8.h" +#include "ObjectManager.h" + +namespace tns +{ + class ArrayBufferHelper + { + public: + ArrayBufferHelper(); + + void CreateConvertFunctions(v8::Isolate *isolate, const v8::Local& global, ObjectManager *objectManager); + + private: + + static void CreateFromCallbackStatic(const v8::FunctionCallbackInfo& info); + + void CreateFromCallbackImpl(const v8::FunctionCallbackInfo& info); + + ObjectManager *m_objectManager; + + jclass m_ByteBufferClass; + + jmethodID m_isDirectMethodID; + }; +} + + +#endif /* ARRAYBUFFERHELPER_H_ */ diff --git a/src/jni/NativePlatform.h b/src/jni/NativePlatform.h new file mode 100644 index 000000000..4b77fb323 --- /dev/null +++ b/src/jni/NativePlatform.h @@ -0,0 +1,39 @@ +#ifndef NATIVEPLATFORM_H_ +#define NATIVEPLATFORM_H_ + +#include "v8.h" +#include "JniLocalRef.h" +#include "ObjectManager.h" +#include "SimpleAllocator.h" +#include "ArrayBufferHelper.h" + +jobject ConvertJsValueToJavaObject(tns::JEnv& env, const v8::Local& value, int classReturnType); + +namespace tns +{ + class NativePlatform + { + public: + static void Init(JavaVM *vm, void *reserved); + static v8::Isolate* InitNativeScript(JNIEnv *_env, jobject obj, jstring filesPath, jboolean verboseLoggingEnabled, jstring packageName, jobjectArray args, jobject jsDebugger); + static void RunModule(JNIEnv *_env, jobject obj, jstring scriptFile); + static jobject RunScript(JNIEnv *_env, jobject obj, jstring scriptFile); + static jobject CallJSMethodNative(JNIEnv *_env, jobject obj, jint javaObjectID, jstring methodName, jint retType, jboolean isConstructor, jobjectArray packagedArgs); + static void CreateJSInstanceNative(JNIEnv *_env, jobject obj, jobject javaObject, jint javaObjectID, jstring className); + static jint GenerateNewObjectId(JNIEnv *env, jobject obj); + static void AdjustAmountOfExternalAllocatedMemoryNative(JNIEnv *env, jobject obj, jlong usedMemory); + static void PassUncaughtExceptionToJsNative(JNIEnv *env, jobject obj, jthrowable exception, jstring stackTrace); + + bool LogEnabled = true; + private: + + static v8::Isolate* PrepareV8Runtime(JEnv& env, const std::string& filesPath, jstring packageName, jobject jsDebugger); + static jobject ConvertJsValueToJavaObject(JEnv& env, const v8::Local& value, int classReturnType); + + static v8::Isolate *s_isolate; + + static ArrayBufferHelper s_arrayBufferHeper; + }; +} + +#endif /*#ifndef NATIVEPLATFORM_H_*/ diff --git a/src/jni/Runtime.cpp b/src/jni/Runtime.cpp index 865bd7847..3c1befb3b 100644 --- a/src/jni/Runtime.cpp +++ b/src/jni/Runtime.cpp @@ -434,6 +434,8 @@ Isolate* Runtime::PrepareV8Runtime(const string& filesPath, jstring packageName, ArrayHelper::Init(context); + m_arrayBufferHelper.CreateConvertFunctions(isolate, global, m_objectManager); + return isolate; } @@ -494,4 +496,3 @@ void Runtime::PrepareExtendFunction(Isolate *isolate, jstring filesPath) JavaVM* Runtime::s_jvm = nullptr; map Runtime::s_id2RuntimeCache; map Runtime::s_isolate2RuntimesCache; - diff --git a/src/jni/Runtime.h b/src/jni/Runtime.h index 906079434..2eb16bece 100644 --- a/src/jni/Runtime.h +++ b/src/jni/Runtime.h @@ -6,6 +6,7 @@ #include "ObjectManager.h" #include "SimpleAllocator.h" #include "WeakRef.h" +#include "ArrayBufferHelper.h" #include "Profiler.h" jobject ConvertJsValueToJavaObject(tns::JEnv& env, const v8::Local& value, int classReturnType); @@ -50,6 +51,8 @@ namespace tns ObjectManager *m_objectManager; + ArrayBufferHelper m_arrayBufferHelper; + WeakRef m_weakRef; Profiler m_profiler; diff --git a/test-app/assets/app/mainpage.js b/test-app/assets/app/mainpage.js index 15c6ee6b1..15900da8e 100644 --- a/test-app/assets/app/mainpage.js +++ b/test-app/assets/app/mainpage.js @@ -42,4 +42,5 @@ require("./tests/testNativeModules"); require("./tests/requireExceptionTests"); require("./tests/java-array-test"); require("./tests/field-access-test"); +require("./tests/byte-buffer-test"); require("./tests/dex-interface-implementation"); diff --git a/test-app/assets/app/tests/byte-buffer-test.js b/test-app/assets/app/tests/byte-buffer-test.js new file mode 100644 index 000000000..64236d881 --- /dev/null +++ b/test-app/assets/app/tests/byte-buffer-test.js @@ -0,0 +1,52 @@ +describe("Tests mapped ByteBuffer conversion", function () { + it("should convert ByteBuffer to ArrayBuffer", function () { + var bb = java.nio.ByteBuffer.allocateDirect(12); + var ab = ArrayBuffer.from(bb); + var int8arr = new Int8Array(ab); + expect(int8arr.length).toBe(12); + var int32arr = new Int32Array(ab); + expect(int32arr.length).toBe(3); + }); + + it("should share the same memory of all typed arrays", function () { + var bb = java.nio.ByteBuffer.allocateDirect(12); + var ab = ArrayBuffer.from(bb); + var int8arr = new Int8Array(ab); + expect(int8arr.length).toBe(12); + var int32arr = new Int32Array(ab); + expect(int32arr.length).toBe(3); + int8arr[0] = 0x11; + int8arr[1] = 0x22; + int8arr[2] = 0x33; + int8arr[3] = 0x44; + var value = int32arr[0]; + expect(value).toBe(0x44332211); + }); + + it("should keep original ByteBuffer after conversion", function () { + var bb = java.nio.ByteBuffer.allocateDirect(12); + var ab = ArrayBuffer.from(bb); + var same = bb === ab.nativeObject; + expect(same).toBe(true); + }); + + it("should throw exception when ArrayBuffer.from is called with wrong number of arguments", function () { + var exceptionCaught = false; + try { + var ab = ArrayBuffer.from(1, 2); + } catch(e) { + exceptionCaught = true + } + expect(exceptionCaught).toBe(true); + }); + + it("should throw exception when ArrayBuffer.from is called with wrong argument type", function () { + var exceptionCaught = false; + try { + var ab = ArrayBuffer.from({}); + } catch(e) { + exceptionCaught = true + } + expect(exceptionCaught).toBe(true); + }); +}); \ No newline at end of file