diff --git a/src/jni/Android.mk b/src/jni/Android.mk index 24aa0c346..ad5a2592d 100644 --- a/src/jni/Android.mk +++ b/src/jni/Android.mk @@ -64,9 +64,9 @@ LOCAL_CPPFLAGS += -std=c++11 LOCAL_MODULE := NativeScript LOCAL_SRC_FILES := com_tns_AssetExtractor.cpp AssetExtractor.cpp\ com_tns_Platform.cpp NativePlatform.cpp \ - com_tns_JsDebugger.cpp \ + com_tns_JsDebugger.cpp com_tns_DirectBufferFactory.cpp \ JEnv.cpp DirectBuffer.cpp NativeScriptException.cpp \ - JsDebugger.cpp SimpleAllocator.cpp \ + JsDebugger.cpp SimpleAllocator.cpp ArrayBufferHelper.cpp \ NativeScriptRuntime.cpp MetadataNode.cpp MetadataTreeNode.cpp MetadataReader.cpp \ MethodCache.cpp JavaObjectArrayCache.cpp \ JniSignatureParser.cpp \ diff --git a/src/jni/ArrayBufferHelper.cpp b/src/jni/ArrayBufferHelper.cpp new file mode 100644 index 000000000..4b562bde3 --- /dev/null +++ b/src/jni/ArrayBufferHelper.cpp @@ -0,0 +1,116 @@ +#include "ArrayBufferHelper.h" +#include "JEnv.h" +#include "V8GlobalHelpers.h" +#include "NativeScriptException.h" +#include + + +using namespace v8; +using namespace tns; + +ArrayBufferHelper::ArrayBufferHelper() +{ +} + + +void ArrayBufferHelper::CreateConvertFunctions(ObjectManager *objectManager, const Local& global) +{ + s_objectManager = objectManager; + auto isolate = Isolate::GetCurrent(); + auto fromFunc = FunctionTemplate::New(isolate, CreateFromCallback)->GetFunction(); + auto ctx = isolate->GetCurrentContext(); + auto arrBufferCtorFunc = global->Get(ConvertToV8String("ArrayBuffer")).As(); + arrBufferCtorFunc->Set(ctx, ConvertToV8String("from"), fromFunc); +} + +void ArrayBufferHelper::CreateFromCallback(const FunctionCallbackInfo& info) +{ + try + { + 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 = s_objectManager->GetJavaObjectByJsObject(argObj); + + if (obj.IsNull()) + { + throw NativeScriptException("Wrong type of argument (object expected)"); + } + + JEnv env; + + if (s_ByteBufferClass == nullptr) + { + s_ByteBufferClass = env.FindClass("java/nio/ByteBuffer"); + assert(s_ByteBufferClass != nullptr); + } + + auto isByteBuffer = env.IsInstanceOf(obj, s_ByteBufferClass); + + if (!isByteBuffer) + { + throw NativeScriptException("Wrong type of argument (ByteBuffer expected)"); + } + + if (s_isDirectMethodID == nullptr) + { + s_isDirectMethodID = env.GetMethodID(s_ByteBufferClass, "isDirect", "()Z"); + assert(s_isDirectMethodID != nullptr); + } + + auto jbool = env.CallBooleanMethod(obj, s_isDirectMethodID); + + auto isDirectBuffer = jbool == 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); +} + +ObjectManager* ArrayBufferHelper::s_objectManager = nullptr; +jclass ArrayBufferHelper::s_ByteBufferClass = nullptr; +jmethodID ArrayBufferHelper::s_isDirectMethodID = nullptr; diff --git a/src/jni/ArrayBufferHelper.h b/src/jni/ArrayBufferHelper.h new file mode 100644 index 000000000..0500d0f70 --- /dev/null +++ b/src/jni/ArrayBufferHelper.h @@ -0,0 +1,30 @@ +#ifndef ARRAYBUFFERHELPER_H_ +#define ARRAYBUFFERHELPER_H_ + +#include "v8.h" +#include "ObjectManager.h" + +namespace tns +{ + class ArrayBufferHelper + { + public: + static void CreateConvertFunctions(ObjectManager *objectManager, const v8::Local& global); + + private: + ArrayBufferHelper(); + + static void CreateFromCallback(const v8::FunctionCallbackInfo& info); + + static void CreateFromCallbackImpl(const v8::FunctionCallbackInfo& info); + + static ObjectManager *s_objectManager; + + static jclass s_ByteBufferClass; + + static jmethodID s_isDirectMethodID; + }; +} + + +#endif /* ARRAYBUFFERHELPER_H_ */ diff --git a/src/jni/NativePlatform.cpp b/src/jni/NativePlatform.cpp index 2c688d7c4..1475861e2 100644 --- a/src/jni/NativePlatform.cpp +++ b/src/jni/NativePlatform.cpp @@ -24,6 +24,7 @@ #include "NativeScriptException.h" #include "NativePlatform.h" #include "ArrayHelper.h" +#include "ArrayBufferHelper.h" #include #include #include @@ -382,6 +383,8 @@ Isolate* NativePlatform::PrepareV8Runtime(JEnv& env, const string& filesPath, js ArrayHelper::Init(g_objectManager, context); + ArrayBufferHelper::CreateConvertFunctions(g_objectManager, global); + return isolate; } @@ -439,4 +442,25 @@ void NativePlatform::PrepareExtendFunction(Isolate *isolate, jstring filesPath) DEBUG_WRITE("Executed prepareExtend.js script"); } +jobject NativePlatform::AllocateByteBuffer(JNIEnv *_env, jint capacity, jlongArray address) +{ + JEnv env(_env); + jobject buffer = nullptr; + void *data = malloc(capacity); + if (data != nullptr) + { + buffer = env.NewDirectByteBuffer(data, capacity); + } + jlong addr = reinterpret_cast(data); + env.SetLongArrayRegion(address, 0, 1, &addr); + return buffer; +} + +void NativePlatform::FreeByteBuffer(JNIEnv *_env, jlong address) +{ + void *data = reinterpret_cast(address); + free(data); +} + Isolate* NativePlatform::s_isolate = nullptr; + diff --git a/src/jni/NativePlatform.h b/src/jni/NativePlatform.h index f0380a036..005adcff4 100644 --- a/src/jni/NativePlatform.h +++ b/src/jni/NativePlatform.h @@ -22,6 +22,8 @@ namespace tns 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); + static jobject AllocateByteBuffer(JNIEnv *_env, jint capacity, jlongArray address); + static void FreeByteBuffer(JNIEnv *_env, jlong address); bool LogEnabled = true; private: diff --git a/src/jni/com_tns_DirectBufferFactory.cpp b/src/jni/com_tns_DirectBufferFactory.cpp new file mode 100644 index 000000000..113933942 --- /dev/null +++ b/src/jni/com_tns_DirectBufferFactory.cpp @@ -0,0 +1,53 @@ +#include "jni.h" +#include "NativePlatform.h" +#include "NativeScriptException.h" +#include + +using namespace std; +using namespace tns; + +extern "C" jobject Java_com_tns_DirectBufferFactory_allocateByteBuffer(JNIEnv *env, jobject obj, jint capacity, jlongArray address) +{ + jobject buffer = nullptr; + try + { + buffer = NativePlatform::AllocateByteBuffer(env, capacity, address); + } + catch (NativeScriptException& e) + { + e.ReThrowToJava(); + } + catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToJava(); + } + catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToJava(); + } + return buffer; +} + +extern "C" void Java_com_tns_DirectBufferFactory_freeByteBuffer(JNIEnv *env, jobject obj, jlong address) +{ + try + { + NativePlatform::FreeByteBuffer(env, address); + } + catch (NativeScriptException& e) + { + e.ReThrowToJava(); + } + catch (std::exception e) { + stringstream ss; + ss << "Error: c++ exception: " << e.what() << endl; + NativeScriptException nsEx(ss.str()); + nsEx.ReThrowToJava(); + } + catch (...) { + NativeScriptException nsEx(std::string("Error: c++ exception!")); + nsEx.ReThrowToJava(); + } +} diff --git a/src/src/com/tns/DirectBufferFactory.java b/src/src/com/tns/DirectBufferFactory.java new file mode 100644 index 000000000..9e4cc3883 --- /dev/null +++ b/src/src/com/tns/DirectBufferFactory.java @@ -0,0 +1,87 @@ +package com.tns; + +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; +import java.nio.ByteBuffer; +import java.util.HashSet; +import java.util.Set; + +public class DirectBufferFactory +{ + private class DirectBufferWeakReference extends PhantomReference + { + public long address; + + public DirectBufferWeakReference(T ref, ReferenceQueue queue, long address) + { + super(ref, queue); + this.address = address; + } + } + + private class ReferenceQueueListener implements Runnable + { + public ReferenceQueueListener() + { + } + + @Override + public void run() + { + while (true) + { + try + { + @SuppressWarnings("unchecked") + DirectBufferWeakReference ref = (DirectBufferWeakReference) DirectBufferFactory.this.queue.remove(); + DirectBufferFactory.this.enqueueBuffers.remove(ref); + freeByteBuffer(ref.address); + } + catch (Throwable t) + { + DirectBufferFactory.this.logger.write(t.getMessage()); + } + } + } + } + + private final Logger logger; + + private final ReferenceQueue queue; + + private final ReferenceQueueListener listener; + + private final Set> enqueueBuffers; + + private final Thread workerThread; + + public DirectBufferFactory(Logger logger) + { + this.logger = logger; + this.queue = new ReferenceQueue(); + this.listener = new ReferenceQueueListener(); + this.enqueueBuffers = new HashSet>(); + this.workerThread = new Thread(this.listener, "My DirectBuffer Clean Thread"); + } + + public void startListener() + { + this.workerThread.start(); + } + + public ByteBuffer create(int capacity) + { + long[] address = new long[1]; + ByteBuffer buffer = allocateByteBuffer(capacity, address); + if (buffer != null) + { + DirectBufferWeakReference ref = new DirectBufferWeakReference(buffer, this.queue, address[0]); + enqueueBuffers.add(ref); + } + return buffer; + } + + private static native ByteBuffer allocateByteBuffer(int capacity, long[] address); + + private static native void freeByteBuffer(long address); +} diff --git a/src/src/com/tns/Platform.java b/src/src/com/tns/Platform.java index ca19c0548..ef8204d14 100644 --- a/src/src/com/tns/Platform.java +++ b/src/src/com/tns/Platform.java @@ -76,6 +76,8 @@ public class Platform private static JsDebugger jsDebugger; private static DexFactory dexFactory; + + private static DirectBufferFactory directBufferFactory; private final static Comparator methodComparator = new Comparator() { @@ -148,7 +150,7 @@ public static void init(Application application, ThreadScheduler threadScheduler Date lastModDate = new Date(f.lastModified()); logger.write("init time=" + (d.getTime() - lastModDate.getTime())); } - + initialized = true; } @@ -911,6 +913,17 @@ private static Object createArrayHelper(String arrayClassName, int size) throws return arr; } + + public synchronized static ByteBuffer allocateByteBuffer(int capacity) + { + if (directBufferFactory == null) + { + directBufferFactory = new DirectBufferFactory(logger); + directBufferFactory.startListener(); + } + ByteBuffer buffer = directBufferFactory.create(capacity); + return buffer; + } @RuntimeCallable private static boolean useGlobalRefs() diff --git a/test-app/assets/app/mainpage.js b/test-app/assets/app/mainpage.js index 82921ec7b..097128f37 100644 --- a/test-app/assets/app/mainpage.js +++ b/test-app/assets/app/mainpage.js @@ -19,18 +19,18 @@ require("./shared"); require("./tests/testMetadata"); require("./tests/testAsserts"); -require("./tests/testWeakRef"); +require("./tests/testWeakRef"); require("./tests/tests"); require("./tests/testMethodResolution"); require("./tests/testArrays"); require("./tests/testsForRuntimeBindingGenerator"); require("./tests/testPrimitiveTypeConversion"); -require("./tests/numericConversionTests"); -require("./tests/inheritanceChainResolutionTest"); +require("./tests/numericConversionTests"); +require("./tests/inheritanceChainResolutionTest"); require("./tests/exceptionHandlingTests"); require("./tests/dispatchCallbacksOnUiThreadTests"); require("./tests/stringConversionTests"); -require("./tests/testsForTypescript"); +require("./tests/testsForTypescript"); require("./tests/testGC"); require("./tests/testsMemoryManagement"); require("./tests/testFieldGetSet"); @@ -41,3 +41,4 @@ require("./tests/testNativeModules"); require("./tests/requireExceptionTests"); require("./tests/java-array-test"); require("./tests/field-access-test"); +require("./tests/byte-buffer-test"); 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..11bc12094 --- /dev/null +++ b/test-app/assets/app/tests/byte-buffer-test.js @@ -0,0 +1,37 @@ +describe("Tests mapped ByteBuffer conversion", function () { + it("should convert ByteBuffer to ArrayBuffer", function () { + var bb = com.tns.Platform.allocateByteBuffer(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 keep original ByteBuffer after conversion", function () { + var bb = com.tns.Platform.allocateByteBuffer(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 diff --git a/test-app/assets/metadata/treeNodeStream.dat b/test-app/assets/metadata/treeNodeStream.dat index 9ed66acc9..197e3e1aa 100644 Binary files a/test-app/assets/metadata/treeNodeStream.dat and b/test-app/assets/metadata/treeNodeStream.dat differ diff --git a/test-app/assets/metadata/treeStringsStream.dat b/test-app/assets/metadata/treeStringsStream.dat index ca418721c..ab6565559 100644 Binary files a/test-app/assets/metadata/treeStringsStream.dat and b/test-app/assets/metadata/treeStringsStream.dat differ diff --git a/test-app/assets/metadata/treeValueStream.dat b/test-app/assets/metadata/treeValueStream.dat index dbb33b107..5c9a7cd09 100644 Binary files a/test-app/assets/metadata/treeValueStream.dat and b/test-app/assets/metadata/treeValueStream.dat differ