mirror of
https://github.com/wgh136/flutter_qjs.git
synced 2025-09-27 05:27:23 +00:00
ffi android
This commit is contained in:
@@ -42,7 +42,7 @@ android {
|
|||||||
}
|
}
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
path "src/main/jni/CMakeLists.txt"
|
path "src/main/cxx/CMakeLists.txt"
|
||||||
version "3.10.2"
|
version "3.10.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,13 +4,15 @@
|
|||||||
# Sets the minimum version of CMake required to build the native library.
|
# Sets the minimum version of CMake required to build the native library.
|
||||||
|
|
||||||
cmake_minimum_required(VERSION 3.4.1)
|
cmake_minimum_required(VERSION 3.4.1)
|
||||||
set(JNI_LIB_NAME libjsengine)
|
set(JNI_LIB_NAME qjs)
|
||||||
|
|
||||||
# Creates and names a library, sets it as either STATIC
|
# Creates and names a library, sets it as either STATIC
|
||||||
# or SHARED, and provides the relative paths to its source code.
|
# or SHARED, and provides the relative paths to its source code.
|
||||||
# You can define multiple libraries, and CMake builds them for you.
|
# You can define multiple libraries, and CMake builds them for you.
|
||||||
# Gradle automatically packages shared libraries with your APK.
|
# Gradle automatically packages shared libraries with your APK.
|
||||||
|
|
||||||
|
set(CXX_LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../../cxx)
|
||||||
|
|
||||||
add_library( # Sets the name of the library.
|
add_library( # Sets the name of the library.
|
||||||
${JNI_LIB_NAME}
|
${JNI_LIB_NAME}
|
||||||
|
|
||||||
@@ -18,10 +20,10 @@ add_library( # Sets the name of the library.
|
|||||||
SHARED
|
SHARED
|
||||||
|
|
||||||
# Provides a relative path to your source file(s).
|
# Provides a relative path to your source file(s).
|
||||||
native-lib.cpp )
|
${CXX_LIB_DIR}/ffi.cpp )
|
||||||
|
|
||||||
# quickjs
|
# quickjs
|
||||||
set(QUICK_JS_LIB_DIR ../../../../cxx/quickjs)
|
set(QUICK_JS_LIB_DIR ${CXX_LIB_DIR}/quickjs)
|
||||||
file (STRINGS "${QUICK_JS_LIB_DIR}/VERSION" QUICKJS_VERSION)
|
file (STRINGS "${QUICK_JS_LIB_DIR}/VERSION" QUICKJS_VERSION)
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DDUMP_LEAKS -DCONFIG_VERSION=\\\"${QUICKJS_VERSION}\\\"")
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DDUMP_LEAKS -DCONFIG_VERSION=\\\"${QUICKJS_VERSION}\\\"")
|
||||||
target_sources(${JNI_LIB_NAME} PUBLIC
|
target_sources(${JNI_LIB_NAME} PUBLIC
|
@@ -1,214 +0,0 @@
|
|||||||
/*
|
|
||||||
* @Description:
|
|
||||||
* @Author: ekibun
|
|
||||||
* @Date: 2020-08-16 11:08:23
|
|
||||||
* @LastEditors: ekibun
|
|
||||||
* @LastEditTime: 2020-08-25 18:06:08
|
|
||||||
*/
|
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include "jni_helper.hpp"
|
|
||||||
#include "../../../../cxx/js_engine.hpp"
|
|
||||||
|
|
||||||
namespace qjs
|
|
||||||
{
|
|
||||||
|
|
||||||
JSValue javaToJs(JSContext *ctx, JNIEnv *env, jobject val, std::unordered_map<jobject, JSValue> cache = std::unordered_map<jobject, JSValue>())
|
|
||||||
{
|
|
||||||
if (val == nullptr)
|
|
||||||
return JS_UNDEFINED;
|
|
||||||
if (cache.find(val) != cache.end())
|
|
||||||
return cache[val];
|
|
||||||
jclass objclass = env->GetObjectClass(val);
|
|
||||||
jclass classclass = env->GetObjectClass((jobject)objclass);
|
|
||||||
jmethodID mid = env->GetMethodID(classclass, "getName", "()Ljava/lang/String;");
|
|
||||||
jobject clsObj = env->CallObjectMethod(objclass, mid);
|
|
||||||
std::string className(env->GetStringUTFChars((jstring)clsObj, 0));
|
|
||||||
if (className.compare("[B") == 0)
|
|
||||||
{
|
|
||||||
jsize len = env->GetArrayLength((jbyteArray)val);
|
|
||||||
return JS_NewArrayBufferCopy(ctx, (uint8_t *)env->GetByteArrayElements((jbyteArray)val, 0), len);
|
|
||||||
}
|
|
||||||
else if (className.compare("[I") == 0)
|
|
||||||
{
|
|
||||||
jsize len = env->GetArrayLength((jintArray)val);
|
|
||||||
return JS_NewArrayBufferCopy(ctx, (uint8_t *)env->GetIntArrayElements((jintArray)val, 0), len * 4);
|
|
||||||
}
|
|
||||||
else if (className.compare("[J") == 0)
|
|
||||||
{
|
|
||||||
jsize len = env->GetArrayLength((jlongArray)val);
|
|
||||||
return JS_NewArrayBufferCopy(ctx, (uint8_t *)env->GetLongArrayElements((jlongArray)val, 0), len * 8);
|
|
||||||
}
|
|
||||||
else if (className.compare("[D") == 0)
|
|
||||||
{
|
|
||||||
jsize size = env->GetArrayLength((jdoubleArray)val);
|
|
||||||
JSValue array = JS_NewArray(ctx);
|
|
||||||
auto buf = env->GetDoubleArrayElements((jdoubleArray)val, 0);
|
|
||||||
for (uint32_t i = 0; i < size; i++)
|
|
||||||
{
|
|
||||||
auto atom = JS_NewAtomUInt32(ctx, i);
|
|
||||||
JS_DefinePropertyValue(
|
|
||||||
ctx, array, atom,
|
|
||||||
JS_NewFloat64(ctx, buf[i]),
|
|
||||||
JS_PROP_C_W_E);
|
|
||||||
JS_FreeAtom(ctx, atom);
|
|
||||||
}
|
|
||||||
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
else if (className.compare("java.lang.Boolean") == 0)
|
|
||||||
{
|
|
||||||
jmethodID getVal = env->GetMethodID(objclass, "booleanValue", "()Z");
|
|
||||||
return JS_NewBool(ctx, env->CallBooleanMethod(val, getVal));
|
|
||||||
}
|
|
||||||
else if (className.compare("java.lang.Integer") == 0)
|
|
||||||
{
|
|
||||||
jmethodID getVal = env->GetMethodID(objclass, "intValue", "()I");
|
|
||||||
return JS_NewInt32(ctx, env->CallIntMethod(val, getVal));
|
|
||||||
}
|
|
||||||
else if (className.compare("java.lang.Long") == 0)
|
|
||||||
{
|
|
||||||
jmethodID getVal = env->GetMethodID(objclass, "longValue", "()J");
|
|
||||||
return JS_NewInt64(ctx, env->CallLongMethod(val, getVal));
|
|
||||||
}
|
|
||||||
else if (className.compare("java.lang.Double") == 0)
|
|
||||||
{
|
|
||||||
jmethodID getVal = env->GetMethodID(objclass, "doubleValue", "()D");
|
|
||||||
return JS_NewFloat64(ctx, env->CallDoubleMethod(val, getVal));
|
|
||||||
}
|
|
||||||
else if (className.compare("java.lang.String") == 0)
|
|
||||||
{
|
|
||||||
return JS_NewString(ctx, env->GetStringUTFChars((jstring)val, 0));
|
|
||||||
}
|
|
||||||
else if (className.compare("java.util.ArrayList") == 0)
|
|
||||||
{
|
|
||||||
jobjectArray list = jniToArray(env, val);
|
|
||||||
jsize size = env->GetArrayLength(list);
|
|
||||||
JSValue array = JS_NewArray(ctx);
|
|
||||||
cache[val] = array;
|
|
||||||
for (uint32_t i = 0; i < size; i++)
|
|
||||||
{
|
|
||||||
auto atom = JS_NewAtomUInt32(ctx, i);
|
|
||||||
JS_DefinePropertyValue(
|
|
||||||
ctx, array, atom,
|
|
||||||
javaToJs(ctx, env, env->GetObjectArrayElement(list, i), cache),
|
|
||||||
JS_PROP_C_W_E);
|
|
||||||
JS_FreeAtom(ctx, atom);
|
|
||||||
}
|
|
||||||
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
else if (className.compare("java.util.HashMap") == 0)
|
|
||||||
{
|
|
||||||
// 获取HashMap类entrySet()方法ID
|
|
||||||
jmethodID entrySetMID = env->GetMethodID(objclass, "entrySet", "()Ljava/util/Set;");
|
|
||||||
// 调用entrySet()方法获取Set对象
|
|
||||||
jobject setObj = env->CallObjectMethod(val, entrySetMID);
|
|
||||||
// 获取Set类中iterator()方法ID
|
|
||||||
jclass setClass = env->FindClass("java/util/Set");
|
|
||||||
jmethodID iteratorMID = env->GetMethodID(setClass, "iterator", "()Ljava/util/Iterator;");
|
|
||||||
// 调用iterator()方法获取Iterator对象
|
|
||||||
jobject iteratorObj = env->CallObjectMethod(setObj, iteratorMID);
|
|
||||||
// 获取Iterator类中hasNext()方法ID
|
|
||||||
// 用于while循环判断HashMap中是否还有数据
|
|
||||||
jclass iteratorClass = env->FindClass("java/util/Iterator");
|
|
||||||
jmethodID hasNextMID = env->GetMethodID(iteratorClass, "hasNext", "()Z");
|
|
||||||
// 获取Iterator类中next()方法ID
|
|
||||||
// 用于读取HashMap中的每一条数据
|
|
||||||
jmethodID nextMID = env->GetMethodID(iteratorClass, "next", "()Ljava/lang/Object;");
|
|
||||||
// 获取Map.Entry类中getKey()和getValue()的方法ID
|
|
||||||
// 用于读取“K-V”键值对,注意:内部类使用$符号表示
|
|
||||||
jclass entryClass = env->FindClass("java/util/Map$Entry");
|
|
||||||
jmethodID getKeyMID = env->GetMethodID(entryClass, "getKey", "()Ljava/lang/Object;");
|
|
||||||
jmethodID getValueMID = env->GetMethodID(entryClass, "getValue", "()Ljava/lang/Object;");
|
|
||||||
// JSObject
|
|
||||||
JSValue obj = JS_NewObject(ctx);
|
|
||||||
cache[val] = obj;
|
|
||||||
// 循环检测HashMap中是否还有数据
|
|
||||||
while (env->CallBooleanMethod(iteratorObj, hasNextMID))
|
|
||||||
{
|
|
||||||
// 读取一条数据
|
|
||||||
jobject entryObj = env->CallObjectMethod(iteratorObj, nextMID);
|
|
||||||
auto atomvalue = javaToJs(ctx, env, env->CallObjectMethod(entryObj, getKeyMID), cache);
|
|
||||||
auto atom = JS_ValueToAtom(ctx, atomvalue);
|
|
||||||
JS_DefinePropertyValue(
|
|
||||||
ctx, obj, atom,
|
|
||||||
javaToJs(ctx, env, env->CallObjectMethod(entryObj, getValueMID), cache),
|
|
||||||
JS_PROP_C_W_E);
|
|
||||||
JS_FreeAtom(ctx, atom);
|
|
||||||
JS_FreeValue(ctx, atomvalue);
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
return JS_UNDEFINED;
|
|
||||||
}
|
|
||||||
|
|
||||||
jobject jsToJava(JNIEnv *env, qjs::Value val, std::unordered_map<Value, jobject> cache = std::unordered_map<Value, jobject>())
|
|
||||||
{
|
|
||||||
int tag = JS_VALUE_GET_TAG(val.v);
|
|
||||||
if (JS_TAG_IS_FLOAT64(tag))
|
|
||||||
return jniWrapPrimity<jdouble>(env, (double)val);
|
|
||||||
switch (tag)
|
|
||||||
{
|
|
||||||
case JS_TAG_BOOL:
|
|
||||||
return jniWrapPrimity<jboolean>(env, (bool)val);
|
|
||||||
case JS_TAG_INT:
|
|
||||||
return jniWrapPrimity<jlong>(env, (int64_t)val);
|
|
||||||
case JS_TAG_STRING:
|
|
||||||
return env->NewStringUTF(((std::string)val).c_str());
|
|
||||||
case JS_TAG_OBJECT:
|
|
||||||
{ // ArrayBuffer
|
|
||||||
size_t size;
|
|
||||||
uint8_t *buf = JS_GetArrayBuffer(val.ctx, &size, val.v);
|
|
||||||
if (buf)
|
|
||||||
{
|
|
||||||
jbyteArray arr = env->NewByteArray(size);
|
|
||||||
env->SetByteArrayRegion(arr, 0, size, (int8_t *)buf);
|
|
||||||
return arr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (cache.find(val) != cache.end())
|
|
||||||
return cache[val];
|
|
||||||
if (JS_IsFunction(val.ctx, val.v))
|
|
||||||
{
|
|
||||||
std::map<jobject, jobject> retMap;
|
|
||||||
retMap[env->NewStringUTF("__js_function__")] = jniWrapPrimity<jlong>(env, (int64_t) new JSValue{js_add_ref(val)});
|
|
||||||
return jniWrapMap(env, retMap);
|
|
||||||
}
|
|
||||||
else if (JS_IsArray(val.ctx, val.v) > 0)
|
|
||||||
{
|
|
||||||
uint32_t arrlen = (uint32_t)val["length"];
|
|
||||||
jclass class_arraylist = env->FindClass("java/util/ArrayList");
|
|
||||||
jmethodID arraylist_init = env->GetMethodID(class_arraylist, "<init>", "()V");
|
|
||||||
jobject list = env->NewObject(class_arraylist, arraylist_init);
|
|
||||||
jmethodID arraylist_add = env->GetMethodID(class_arraylist, "add", "(Ljava/lang/Object;)Z");
|
|
||||||
for (uint32_t i = 0; i < arrlen; i++)
|
|
||||||
{
|
|
||||||
env->CallBooleanMethod(list, arraylist_add, jsToJava(env, val[i], cache));
|
|
||||||
}
|
|
||||||
cache[val] = list;
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
qjs::JSPropertyEnum *ptab;
|
|
||||||
uint32_t plen;
|
|
||||||
if (JS_GetOwnPropertyNames(val.ctx, &ptab, &plen, val.v, -1))
|
|
||||||
return nullptr;
|
|
||||||
std::map<jobject, jobject> retMap;
|
|
||||||
for (uint32_t i = 0; i < plen; i++)
|
|
||||||
{
|
|
||||||
retMap[jsToJava(env, {val.ctx, JS_AtomToValue(val.ctx, ptab[i].atom)}, cache)] =
|
|
||||||
jsToJava(env, {val.ctx, JS_GetProperty(val.ctx, val.v, ptab[i].atom)}, cache);
|
|
||||||
JS_FreeAtom(val.ctx, ptab[i].atom);
|
|
||||||
}
|
|
||||||
js_free(val.ctx, ptab);
|
|
||||||
jobject ret = jniWrapMap(env, retMap);
|
|
||||||
cache[val] = ret;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} // namespace qjs
|
|
@@ -1,62 +0,0 @@
|
|||||||
/*
|
|
||||||
* @Description:
|
|
||||||
* @Author: ekibun
|
|
||||||
* @Date: 2020-08-16 13:20:03
|
|
||||||
* @LastEditors: ekibun
|
|
||||||
* @LastEditTime: 2020-08-16 17:56:52
|
|
||||||
*/
|
|
||||||
#include <jni.h>
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
jclass jniInstanceOf(JNIEnv *env, jobject obj, const char *className)
|
|
||||||
{
|
|
||||||
jclass jclazz = env->FindClass(className);
|
|
||||||
return env->IsInstanceOf(obj, jclazz) ? jclazz : nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
jobjectArray jniToArray(JNIEnv *env, jobject obj)
|
|
||||||
{
|
|
||||||
jmethodID mToArray = env->GetMethodID(env->GetObjectClass(obj), "toArray", "()[Ljava/lang/Object;");
|
|
||||||
return (jobjectArray)env->CallObjectMethod(obj, mToArray);
|
|
||||||
}
|
|
||||||
|
|
||||||
jobject jniWrapMap(JNIEnv *env, std::map<jobject, jobject> val)
|
|
||||||
{
|
|
||||||
jclass class_hashmap = env->FindClass("java/util/HashMap");
|
|
||||||
jmethodID hashmap_init = env->GetMethodID(class_hashmap, "<init>", "()V");
|
|
||||||
jobject map = env->NewObject(class_hashmap, hashmap_init);
|
|
||||||
jmethodID hashMap_put = env->GetMethodID(class_hashmap, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
|
|
||||||
for (auto it = val.begin(); it != val.end(); ++it)
|
|
||||||
{
|
|
||||||
env->CallObjectMethod(map, hashMap_put, it->first, it->second);
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
jobject jniWrapPrimity(JNIEnv *env, T obj);
|
|
||||||
|
|
||||||
template <>
|
|
||||||
jobject jniWrapPrimity(JNIEnv *env, jlong obj)
|
|
||||||
{
|
|
||||||
jclass jclazz = env->FindClass("java/lang/Long");
|
|
||||||
jmethodID jmethod = env->GetMethodID(jclazz, "<init>", "(J)V");
|
|
||||||
return env->NewObject(jclazz, jmethod, obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
jobject jniWrapPrimity(JNIEnv *env, jboolean obj)
|
|
||||||
{
|
|
||||||
// TODO see https://github.com/flutter/flutter/issues/45066
|
|
||||||
std::map<jobject, jobject> retMap;
|
|
||||||
retMap[env->NewStringUTF("__js_boolean__")] = jniWrapPrimity<jlong>(env, (int64_t)obj);
|
|
||||||
return jniWrapMap(env, retMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <>
|
|
||||||
jobject jniWrapPrimity(JNIEnv *env, jdouble obj)
|
|
||||||
{
|
|
||||||
jclass jclazz = env->FindClass("java/lang/Double");
|
|
||||||
jmethodID jmethod = env->GetMethodID(jclazz, "<init>", "(D)V");
|
|
||||||
return env->NewObject(jclazz, jmethod, obj);
|
|
||||||
}
|
|
@@ -1,184 +0,0 @@
|
|||||||
/*
|
|
||||||
* @Description:
|
|
||||||
* @Author: ekibun
|
|
||||||
* @Date: 2020-08-09 18:16:11
|
|
||||||
* @LastEditors: ekibun
|
|
||||||
* @LastEditTime: 2020-08-25 16:00:46
|
|
||||||
*/
|
|
||||||
#include "java_js_wrapper.hpp"
|
|
||||||
#include "android/log.h"
|
|
||||||
|
|
||||||
JNIEnv *getEnv(JavaVM *gJvm)
|
|
||||||
{
|
|
||||||
JNIEnv *env;
|
|
||||||
int status = gJvm->GetEnv((void **)&env, JNI_VERSION_1_6);
|
|
||||||
if (status < 0)
|
|
||||||
{
|
|
||||||
status = gJvm->AttachCurrentThread(&env, NULL);
|
|
||||||
if (status < 0)
|
|
||||||
{
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return env;
|
|
||||||
}
|
|
||||||
|
|
||||||
void jniResultResolve(JNIEnv *env, jobject result, jobject data)
|
|
||||||
{
|
|
||||||
jclass jclazz = env->GetObjectClass(result);
|
|
||||||
jmethodID jmethod = env->GetMethodID(jclazz, "success", "(Ljava/lang/Object;)V");
|
|
||||||
env->CallVoidMethod(result, jmethod, data);
|
|
||||||
env->DeleteLocalRef(data);
|
|
||||||
env->DeleteGlobalRef(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
void jniResultReject(JNIEnv *env, jobject result, std::string reason)
|
|
||||||
{
|
|
||||||
jclass jclazz = env->GetObjectClass(result);
|
|
||||||
jmethodID jmethod = env->GetMethodID(jclazz, "error", "(Ljava/lang/String;)V");
|
|
||||||
jstring jreason = env->NewStringUTF(reason.c_str());
|
|
||||||
env->CallVoidMethod(result, jmethod, jreason);
|
|
||||||
env->DeleteLocalRef(jreason);
|
|
||||||
env->DeleteGlobalRef(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
void jniChannelInvoke(JNIEnv *env, jobject channel, std::promise<qjs::JSFutureReturn> *promise, std::string method, qjs::Value args, qjs::Engine *engine)
|
|
||||||
{
|
|
||||||
jclass jclazz = env->GetObjectClass(channel);
|
|
||||||
jmethodID jmethod = env->GetMethodID(jclazz, "invokeMethod", "(Ljava/lang/String;Ljava/lang/Object;J)V");
|
|
||||||
jstring jstrmethod = env->NewStringUTF(method.c_str());
|
|
||||||
std::map<jobject, jobject> retMap;
|
|
||||||
retMap[env->NewStringUTF("engine")] = jniWrapPrimity<jlong>(env, (int64_t) engine);
|
|
||||||
retMap[env->NewStringUTF("args")] = qjs::jsToJava(env, args);
|
|
||||||
jobject jsargs = jniWrapMap(env, retMap);
|
|
||||||
env->CallVoidMethod(channel, jmethod, jstrmethod, jsargs, (jlong)promise);
|
|
||||||
env->DeleteLocalRef(jstrmethod);
|
|
||||||
env->DeleteLocalRef(jsargs);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" JNIEXPORT jlong JNICALL
|
|
||||||
Java_soko_ekibun_flutter_1qjs_JniBridge_createEngine(
|
|
||||||
JNIEnv *env,
|
|
||||||
jobject thiz,
|
|
||||||
jobject channel)
|
|
||||||
{
|
|
||||||
JavaVM *jvm = nullptr;
|
|
||||||
env->GetJavaVM(&jvm);
|
|
||||||
jobject gchannel = env->NewGlobalRef(channel);
|
|
||||||
qjs::Engine *engine = new qjs::Engine([jvm, gchannel](std::string name, qjs::Value args, qjs::Engine *engine) {
|
|
||||||
auto promise = new std::promise<qjs::JSFutureReturn>();
|
|
||||||
JNIEnv *env = getEnv(jvm);
|
|
||||||
jniChannelInvoke(env, gchannel, promise, name, args, engine);
|
|
||||||
jvm->DetachCurrentThread();
|
|
||||||
return promise;
|
|
||||||
});
|
|
||||||
return (jlong)engine;
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
|
||||||
Java_soko_ekibun_flutter_1qjs_JniBridge_evaluate(
|
|
||||||
JNIEnv *env,
|
|
||||||
jobject thiz,
|
|
||||||
jlong engine,
|
|
||||||
jstring script,
|
|
||||||
jstring name,
|
|
||||||
jobject result)
|
|
||||||
{
|
|
||||||
JavaVM *jvm = nullptr;
|
|
||||||
env->GetJavaVM(&jvm);
|
|
||||||
jobject gresult = env->NewGlobalRef(result);
|
|
||||||
((qjs::Engine *)engine)->commit(qjs::EngineTask{
|
|
||||||
[script = std::string(env->GetStringUTFChars(script, 0)),
|
|
||||||
name = std::string(env->GetStringUTFChars(name, 0))](qjs::Context &ctx) {
|
|
||||||
return ctx.eval(script, name.c_str(), JS_EVAL_TYPE_GLOBAL);
|
|
||||||
},
|
|
||||||
[jvm, gresult](qjs::Value resolve) {
|
|
||||||
JNIEnv *env = getEnv(jvm);
|
|
||||||
jniResultResolve(env, gresult, qjs::jsToJava(env, resolve));
|
|
||||||
jvm->DetachCurrentThread();
|
|
||||||
},
|
|
||||||
[jvm, gresult](qjs::Value reject) {
|
|
||||||
JNIEnv *env = getEnv(jvm);
|
|
||||||
jniResultReject(env, gresult, qjs::getStackTrack(reject));
|
|
||||||
jvm->DetachCurrentThread();
|
|
||||||
}});
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
|
||||||
Java_soko_ekibun_flutter_1qjs_JniBridge_close(
|
|
||||||
JNIEnv *env,
|
|
||||||
jobject thiz,
|
|
||||||
jlong engine)
|
|
||||||
{
|
|
||||||
delete ((qjs::Engine *)engine);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
|
||||||
Java_soko_ekibun_flutter_1qjs_JniBridge_call(
|
|
||||||
JNIEnv *env,
|
|
||||||
jobject thiz,
|
|
||||||
jlong engine,
|
|
||||||
jlong function,
|
|
||||||
jobject args,
|
|
||||||
jobject result)
|
|
||||||
{
|
|
||||||
JavaVM *jvm = nullptr;
|
|
||||||
env->GetJavaVM(&jvm);
|
|
||||||
jobject gresult = env->NewGlobalRef(result);
|
|
||||||
jobject gargs = env->NewGlobalRef(args);
|
|
||||||
((qjs::Engine *)engine)->commit(qjs::EngineTask{
|
|
||||||
[jvm, function = (qjs::JSValue *)function, gargs](qjs::Context &ctx) {
|
|
||||||
JNIEnv *env = getEnv(jvm);
|
|
||||||
jobjectArray array = jniToArray(env, gargs);
|
|
||||||
jsize argscount = env->GetArrayLength(array);
|
|
||||||
qjs::JSValue *callargs = new qjs::JSValue[argscount];
|
|
||||||
for (jsize i = 0; i < argscount; i++)
|
|
||||||
{
|
|
||||||
callargs[i] = qjs::javaToJs(ctx.ctx, env, env->GetObjectArrayElement(array, i));
|
|
||||||
}
|
|
||||||
jvm->DetachCurrentThread();
|
|
||||||
qjs::JSValue ret = qjs::call_handler(ctx.ctx, *function, (int)argscount, callargs);
|
|
||||||
delete[] callargs;
|
|
||||||
if (qjs::JS_IsException(ret))
|
|
||||||
throw qjs::exception{};
|
|
||||||
return qjs::Value{ctx.ctx, ret};
|
|
||||||
},
|
|
||||||
[jvm, gresult](qjs::Value resolve) {
|
|
||||||
JNIEnv *env = getEnv(jvm);
|
|
||||||
jniResultResolve(env, gresult, qjs::jsToJava(env, resolve));
|
|
||||||
jvm->DetachCurrentThread();
|
|
||||||
},
|
|
||||||
[jvm, gresult](qjs::Value reject) {
|
|
||||||
JNIEnv *env = getEnv(jvm);
|
|
||||||
jniResultReject(env, gresult, qjs::getStackTrack(reject));
|
|
||||||
jvm->DetachCurrentThread();
|
|
||||||
}});
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
|
||||||
Java_soko_ekibun_flutter_1qjs_JniBridge_resolve(
|
|
||||||
JNIEnv *env,
|
|
||||||
jobject clazz,
|
|
||||||
jlong promise, jobject data)
|
|
||||||
{
|
|
||||||
JavaVM *jvm = nullptr;
|
|
||||||
env->GetJavaVM(&jvm);
|
|
||||||
jobject gdata = env->NewGlobalRef(data);
|
|
||||||
((std::promise<qjs::JSFutureReturn> *)promise)->set_value((qjs::JSFutureReturn)[jvm, gdata](qjs::JSContext * ctx) {
|
|
||||||
JNIEnv *env = getEnv(jvm);
|
|
||||||
qjs::JSValue *ret = new qjs::JSValue{qjs::javaToJs(ctx, env, gdata)};
|
|
||||||
return qjs::JSOSFutureArgv{1, ret};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "C" JNIEXPORT void JNICALL
|
|
||||||
Java_soko_ekibun_flutter_1qjs_JniBridge_reject(
|
|
||||||
JNIEnv *env,
|
|
||||||
jobject clazz,
|
|
||||||
jlong promise, jstring data)
|
|
||||||
{
|
|
||||||
((std::promise<qjs::JSFutureReturn> *)promise)->set_value((qjs::JSFutureReturn)[data = std::string(env->GetStringUTFChars(data, 0))](qjs::JSContext * ctx) {
|
|
||||||
qjs::JSValue *ret = new qjs::JSValue{JS_NewString(ctx, data.c_str())};
|
|
||||||
return qjs::JSOSFutureArgv{-1, ret};
|
|
||||||
});
|
|
||||||
}
|
|
@@ -2,55 +2,16 @@ package soko.ekibun.flutter_qjs
|
|||||||
|
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||||
import io.flutter.plugin.common.MethodCall
|
|
||||||
import io.flutter.plugin.common.MethodChannel
|
|
||||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
|
||||||
import io.flutter.plugin.common.MethodChannel.Result
|
|
||||||
import io.flutter.plugin.common.PluginRegistry.Registrar
|
|
||||||
|
|
||||||
/** FlutterQjsPlugin */
|
/** FlutterQjsPlugin */
|
||||||
class FlutterQjsPlugin: FlutterPlugin, MethodCallHandler {
|
class FlutterQjsPlugin: FlutterPlugin {
|
||||||
/// The MethodChannel that will the communication between Flutter and native Android
|
/// The MethodChannel that will the communication between Flutter and native Android
|
||||||
///
|
///
|
||||||
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
|
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
|
||||||
/// when the Flutter Engine is detached from the Activity
|
/// when the Flutter Engine is detached from the Activity
|
||||||
private lateinit var channelwrapper : MethodChannelWrapper
|
|
||||||
private lateinit var channel : MethodChannel
|
|
||||||
private lateinit var applicationContext: android.content.Context
|
|
||||||
private val handler by lazy { Handler() }
|
|
||||||
|
|
||||||
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||||
applicationContext = flutterPluginBinding.applicationContext
|
|
||||||
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "soko.ekibun.flutter_qjs")
|
|
||||||
channel.setMethodCallHandler(this)
|
|
||||||
channelwrapper = MethodChannelWrapper(handler, channel)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onMethodCall(call: MethodCall, result: Result) {
|
|
||||||
if (call.method == "createEngine") {
|
|
||||||
val engine: Long = JniBridge.instance.createEngine(channelwrapper)
|
|
||||||
result.success(engine)
|
|
||||||
} else if (call.method == "evaluate") {
|
|
||||||
val engine: Long = call.argument<Long>("engine")!!
|
|
||||||
val script: String = call.argument<String>("script")!!
|
|
||||||
val name: String = call.argument<String>("name")!!
|
|
||||||
JniBridge.instance.evaluate(engine, script, name, ResultWrapper(handler, result))
|
|
||||||
} else if (call.method == "call") {
|
|
||||||
val engine: Long = call.argument<Long>("engine")!!
|
|
||||||
val function: Long = call.argument<Long>("function")!!
|
|
||||||
val args: List<Any> = call.argument<List<Any>>("arguments")!!
|
|
||||||
JniBridge.instance.call(engine, function, args, ResultWrapper(handler, result))
|
|
||||||
} else if (call.method == "close") {
|
|
||||||
val engine: Long = call.arguments<Long>()
|
|
||||||
JniBridge.instance.close(engine)
|
|
||||||
result.success(null)
|
|
||||||
} else {
|
|
||||||
result.notImplemented()
|
|
||||||
result.success(null)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||||
channel.setMethodCallHandler(null)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,24 +0,0 @@
|
|||||||
package soko.ekibun.flutter_qjs
|
|
||||||
|
|
||||||
class JniBridge {
|
|
||||||
companion object {
|
|
||||||
// Used to load the 'native-lib' library on application startup.
|
|
||||||
init {
|
|
||||||
System.loadLibrary("libjsengine")
|
|
||||||
}
|
|
||||||
|
|
||||||
val instance by lazy { JniBridge() }
|
|
||||||
}
|
|
||||||
|
|
||||||
external fun createEngine(channel: MethodChannelWrapper): Long
|
|
||||||
|
|
||||||
external fun evaluate(engine: Long, script: String, name: String, result: ResultWrapper)
|
|
||||||
|
|
||||||
external fun close(engine: Long)
|
|
||||||
|
|
||||||
external fun call(engine: Long, function: Long, args: List<Any>, result: ResultWrapper)
|
|
||||||
|
|
||||||
external fun reject(promise: Long, reason: String)
|
|
||||||
|
|
||||||
external fun resolve(promise: Long, data: Any?)
|
|
||||||
}
|
|
@@ -1,29 +0,0 @@
|
|||||||
package soko.ekibun.flutter_qjs
|
|
||||||
|
|
||||||
import androidx.annotation.Keep
|
|
||||||
|
|
||||||
import android.os.Handler
|
|
||||||
import io.flutter.plugin.common.MethodChannel
|
|
||||||
|
|
||||||
@Keep
|
|
||||||
class MethodChannelWrapper(private val handler: Handler, private val channel: MethodChannel) {
|
|
||||||
fun invokeMethod(method: String, arguments: Any?, promise: Long) {
|
|
||||||
handler.post {
|
|
||||||
channel.invokeMethod(method, arguments, object : MethodChannel.Result {
|
|
||||||
override fun notImplemented() {
|
|
||||||
JniBridge.instance.reject(promise, "notImplemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun error(error_code: String?, error_message: String?, error_data: Any?) {
|
|
||||||
JniBridge.instance.reject(promise, error_message ?: "undefined")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun success(data: Any?) {
|
|
||||||
JniBridge.instance.resolve(promise, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,20 +0,0 @@
|
|||||||
package soko.ekibun.flutter_qjs
|
|
||||||
|
|
||||||
import androidx.annotation.Keep
|
|
||||||
|
|
||||||
import android.os.Handler
|
|
||||||
import io.flutter.plugin.common.MethodChannel.Result
|
|
||||||
|
|
||||||
@Keep
|
|
||||||
class ResultWrapper(private val handler: Handler, private val result: Result) {
|
|
||||||
|
|
||||||
@Keep
|
|
||||||
fun success(dat: Any?) {
|
|
||||||
handler.post { result.success(dat) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Keep
|
|
||||||
fun error(error_message: String) {
|
|
||||||
handler.post { result.error("FlutterJSException", error_message, null) }
|
|
||||||
}
|
|
||||||
}
|
|
14
cxx/ffi.cpp
14
cxx/ffi.cpp
@@ -3,7 +3,7 @@
|
|||||||
* @Author: ekibun
|
* @Author: ekibun
|
||||||
* @Date: 2020-09-06 18:32:45
|
* @Date: 2020-09-06 18:32:45
|
||||||
* @LastEditors: ekibun
|
* @LastEditors: ekibun
|
||||||
* @LastEditTime: 2020-09-21 01:41:39
|
* @LastEditTime: 2020-09-21 14:09:25
|
||||||
*/
|
*/
|
||||||
#include "quickjs/quickjs.h"
|
#include "quickjs/quickjs.h"
|
||||||
#include <functional>
|
#include <functional>
|
||||||
@@ -19,6 +19,11 @@ extern "C"
|
|||||||
{
|
{
|
||||||
typedef void *JSChannel(JSContext *ctx, const char *method, void *argv);
|
typedef void *JSChannel(JSContext *ctx, const char *method, void *argv);
|
||||||
|
|
||||||
|
DLLEXPORT JSValue *jsThrowInternalError(JSContext *ctx, char *message)
|
||||||
|
{
|
||||||
|
return new JSValue{JS_ThrowInternalError(ctx, "%s", message)};
|
||||||
|
}
|
||||||
|
|
||||||
DLLEXPORT JSValue *jsEXCEPTION()
|
DLLEXPORT JSValue *jsEXCEPTION()
|
||||||
{
|
{
|
||||||
return new JSValue{JS_EXCEPTION};
|
return new JSValue{JS_EXCEPTION};
|
||||||
@@ -42,10 +47,7 @@ extern "C"
|
|||||||
JSChannel *channel = (JSChannel *)JS_GetRuntimeOpaque(rt);
|
JSChannel *channel = (JSChannel *)JS_GetRuntimeOpaque(rt);
|
||||||
const char *str = (char *)channel(ctx, (char *)0, (void *)module_name);
|
const char *str = (char *)channel(ctx, (char *)0, (void *)module_name);
|
||||||
if (str == 0)
|
if (str == 0)
|
||||||
{
|
|
||||||
JS_ThrowReferenceError(ctx, "Module Not Found");
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
|
||||||
JSValue func_val = JS_Eval(ctx, str, strlen(str), module_name, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
|
JSValue func_val = JS_Eval(ctx, str, strlen(str), module_name, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
|
||||||
if (JS_IsException(func_val))
|
if (JS_IsException(func_val))
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -70,7 +72,7 @@ extern "C"
|
|||||||
DLLEXPORT JSRuntime *jsNewRuntime(JSChannel channel)
|
DLLEXPORT JSRuntime *jsNewRuntime(JSChannel channel)
|
||||||
{
|
{
|
||||||
JSRuntime *rt = JS_NewRuntime();
|
JSRuntime *rt = JS_NewRuntime();
|
||||||
JS_SetRuntimeOpaque(rt, channel);
|
JS_SetRuntimeOpaque(rt, (void *)channel);
|
||||||
JS_SetModuleLoaderFunc(rt, nullptr, js_module_loader, nullptr);
|
JS_SetModuleLoaderFunc(rt, nullptr, js_module_loader, nullptr);
|
||||||
return rt;
|
return rt;
|
||||||
}
|
}
|
||||||
@@ -266,7 +268,7 @@ extern "C"
|
|||||||
|
|
||||||
DLLEXPORT uint32_t sizeOfJSValue()
|
DLLEXPORT uint32_t sizeOfJSValue()
|
||||||
{
|
{
|
||||||
return sizeof JSValue;
|
return sizeof (JSValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
DLLEXPORT void setJSValueList(JSValue *list, uint32_t i, JSValue *val)
|
DLLEXPORT void setJSValueList(JSValue *list, uint32_t i, JSValue *val)
|
||||||
|
@@ -1,266 +0,0 @@
|
|||||||
/*
|
|
||||||
* @Description:
|
|
||||||
* @Author: ekibun
|
|
||||||
* @Date: 2020-08-07 13:55:52
|
|
||||||
* @LastEditors: ekibun
|
|
||||||
* @LastEditTime: 2020-09-16 00:29:57
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
#include "quickjspp.hpp"
|
|
||||||
#include <future>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
namespace std
|
|
||||||
{
|
|
||||||
template <>
|
|
||||||
struct hash<qjs::Value>
|
|
||||||
{
|
|
||||||
size_t operator()(const qjs::Value &key) const
|
|
||||||
{
|
|
||||||
return (size_t)JS_VALUE_GET_PTR(key.v);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace std
|
|
||||||
|
|
||||||
namespace qjs
|
|
||||||
{
|
|
||||||
#include "quickjs/list.h"
|
|
||||||
|
|
||||||
static JSClassID js_dart_promise_class_id;
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
int count;
|
|
||||||
JSValue *argv;
|
|
||||||
} JSOSFutureArgv;
|
|
||||||
|
|
||||||
using JSFutureReturn = std::function<JSOSFutureArgv(JSContext *)>;
|
|
||||||
using DartChannel = std::function<std::promise<JSFutureReturn> *(std::string, Value)>;
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
struct list_head link;
|
|
||||||
std::promise<JSFutureReturn> *promise;
|
|
||||||
std::shared_future<JSFutureReturn> future;
|
|
||||||
JSValue resolve;
|
|
||||||
JSValue reject;
|
|
||||||
} JSOSFuture;
|
|
||||||
|
|
||||||
typedef struct
|
|
||||||
{
|
|
||||||
struct list_head link;
|
|
||||||
JSValue ref;
|
|
||||||
} JSOSRef;
|
|
||||||
|
|
||||||
typedef struct JSThreadState
|
|
||||||
{
|
|
||||||
struct list_head os_future;
|
|
||||||
struct list_head os_ref;
|
|
||||||
DartChannel channel;
|
|
||||||
} JSThreadState;
|
|
||||||
|
|
||||||
JSModuleDef *js_module_loader(
|
|
||||||
JSContext *ctx,
|
|
||||||
const char *module_name, void *opaque)
|
|
||||||
{
|
|
||||||
JSRuntime *rt = JS_GetRuntime(ctx);
|
|
||||||
JSThreadState *ts = (JSThreadState *)JS_GetRuntimeOpaque(rt);
|
|
||||||
auto promise = ts->channel("__dart_load_module__", Value{ctx, JS_NewString(ctx, module_name)});
|
|
||||||
JSOSFutureArgv argv = promise->get_future().get()(ctx);
|
|
||||||
if (argv.count > 0)
|
|
||||||
{
|
|
||||||
const char *str = JS_ToCString(ctx, argv.argv[0]);
|
|
||||||
JSValue func_val = JS_Eval(ctx, str, strlen(str), module_name, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
|
|
||||||
JS_FreeCString(ctx, str);
|
|
||||||
JS_FreeValue(ctx, argv.argv[0]);
|
|
||||||
if (JS_IsException(func_val))
|
|
||||||
return NULL;
|
|
||||||
/* the module is already referenced, so we must free it */
|
|
||||||
JSModuleDef *m = (JSModuleDef *)JS_VALUE_GET_PTR(func_val);
|
|
||||||
JS_FreeValue(ctx, func_val);
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
JS_Throw(ctx, argv.argv[0]);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
JSValue js_add_ref(Value val)
|
|
||||||
{
|
|
||||||
JSRuntime *rt = JS_GetRuntime(val.ctx);
|
|
||||||
JSThreadState *ts = (JSThreadState *)JS_GetRuntimeOpaque(rt);
|
|
||||||
JSOSRef *th;
|
|
||||||
th = (JSOSRef *)js_mallocz(val.ctx, sizeof(*th));
|
|
||||||
th->ref = JS_DupValue(val.ctx, val.v);
|
|
||||||
list_add_tail(&th->link, &ts->os_ref);
|
|
||||||
return th->ref;
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSValue js_add_future(Value resolve, Value reject, std::promise<JSFutureReturn> *promise)
|
|
||||||
{
|
|
||||||
JSRuntime *rt = JS_GetRuntime(resolve.ctx);
|
|
||||||
JSThreadState *ts = (JSThreadState *)JS_GetRuntimeOpaque(rt);
|
|
||||||
JSValueConst jsResolve, jsReject;
|
|
||||||
JSOSFuture *th;
|
|
||||||
JSValue obj;
|
|
||||||
|
|
||||||
jsResolve = resolve.v;
|
|
||||||
if (!JS_IsFunction(resolve.ctx, jsResolve))
|
|
||||||
return JS_ThrowTypeError(resolve.ctx, "resolve not a function");
|
|
||||||
jsReject = reject.v;
|
|
||||||
if (!JS_IsFunction(reject.ctx, jsReject))
|
|
||||||
return JS_ThrowTypeError(reject.ctx, "reject not a function");
|
|
||||||
obj = JS_NewObjectClass(resolve.ctx, js_dart_promise_class_id);
|
|
||||||
if (JS_IsException(obj))
|
|
||||||
return obj;
|
|
||||||
th = (JSOSFuture *)js_mallocz(resolve.ctx, sizeof(*th));
|
|
||||||
if (!th)
|
|
||||||
{
|
|
||||||
JS_FreeValue(resolve.ctx, obj);
|
|
||||||
return JS_EXCEPTION;
|
|
||||||
}
|
|
||||||
th->promise = promise;
|
|
||||||
th->future = promise->get_future();
|
|
||||||
th->resolve = JS_DupValue(resolve.ctx, jsResolve);
|
|
||||||
th->reject = JS_DupValue(reject.ctx, jsReject);
|
|
||||||
list_add_tail(&th->link, &ts->os_future);
|
|
||||||
JS_SetOpaque(obj, th);
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
JSValue js_dart_future(Value resolve, Value reject, std::string name, Value args)
|
|
||||||
{
|
|
||||||
JSRuntime *rt = JS_GetRuntime(resolve.ctx);
|
|
||||||
JSThreadState *ts = (JSThreadState *)JS_GetRuntimeOpaque(rt);
|
|
||||||
return js_add_future(resolve, reject, ts->channel(name, args));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void unlink_ref(JSRuntime *rt, JSOSRef *th)
|
|
||||||
{
|
|
||||||
if (th->link.prev)
|
|
||||||
{
|
|
||||||
list_del(&th->link);
|
|
||||||
th->link.prev = th->link.next = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void free_ref(JSRuntime *rt, JSOSRef *th)
|
|
||||||
{
|
|
||||||
JS_FreeValueRT(rt, th->ref);
|
|
||||||
js_free_rt(rt, th);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void unlink_future(JSRuntime *rt, JSOSFuture *th)
|
|
||||||
{
|
|
||||||
if (th->link.prev)
|
|
||||||
{
|
|
||||||
list_del(&th->link);
|
|
||||||
th->link.prev = th->link.next = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void free_future(JSRuntime *rt, JSOSFuture *th)
|
|
||||||
{
|
|
||||||
JS_FreeValueRT(rt, th->resolve);
|
|
||||||
JS_FreeValueRT(rt, th->reject);
|
|
||||||
js_free_rt(rt, th);
|
|
||||||
}
|
|
||||||
|
|
||||||
void js_init_handlers(JSRuntime *rt, DartChannel channel)
|
|
||||||
{
|
|
||||||
JSThreadState *ts = (JSThreadState *)malloc(sizeof(*ts));
|
|
||||||
if (!ts)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "Could not allocate memory for the worker");
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
memset(ts, 0, sizeof(*ts));
|
|
||||||
init_list_head(&ts->os_future);
|
|
||||||
init_list_head(&ts->os_ref);
|
|
||||||
ts->channel = channel;
|
|
||||||
|
|
||||||
JS_SetRuntimeOpaque(rt, ts);
|
|
||||||
}
|
|
||||||
|
|
||||||
void js_free_handlers(JSRuntime *rt)
|
|
||||||
{
|
|
||||||
JSThreadState *ts = (JSThreadState *)JS_GetRuntimeOpaque(rt);
|
|
||||||
struct list_head *el, *el1;
|
|
||||||
|
|
||||||
list_for_each_safe(el, el1, &ts->os_future)
|
|
||||||
{
|
|
||||||
JSOSFuture *th = list_entry(el, JSOSFuture, link);
|
|
||||||
th->future.get();
|
|
||||||
delete th->promise;
|
|
||||||
unlink_future(rt, th);
|
|
||||||
free_future(rt, th);
|
|
||||||
}
|
|
||||||
list_for_each_safe(el, el1, &ts->os_ref)
|
|
||||||
{
|
|
||||||
JSOSRef *th = list_entry(el, JSOSRef, link);
|
|
||||||
unlink_ref(rt, th);
|
|
||||||
free_ref(rt, th);
|
|
||||||
}
|
|
||||||
ts->channel = nullptr;
|
|
||||||
free(ts);
|
|
||||||
JS_SetRuntimeOpaque(rt, NULL); /* fail safe */
|
|
||||||
}
|
|
||||||
|
|
||||||
static JSValue call_handler(JSContext *ctx, JSValueConst func, int count, JSValue *argv)
|
|
||||||
{
|
|
||||||
JSValue ret, func1;
|
|
||||||
/* 'func' might be destroyed when calling itself (if it frees the
|
|
||||||
handler), so must take extra care */
|
|
||||||
func1 = JS_DupValue(ctx, func);
|
|
||||||
ret = JS_Call(ctx, func1, JS_UNDEFINED, count, argv);
|
|
||||||
JS_FreeValue(ctx, func1);
|
|
||||||
for (int i = 0; i < count; ++i)
|
|
||||||
JS_FreeValue(ctx, argv[i]);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int js_dart_poll(JSContext *ctx)
|
|
||||||
{
|
|
||||||
JSRuntime *rt = JS_GetRuntime(ctx);
|
|
||||||
JSThreadState *ts = (JSThreadState *)JS_GetRuntimeOpaque(rt);
|
|
||||||
struct list_head *el;
|
|
||||||
|
|
||||||
/* XXX: handle signals if useful */
|
|
||||||
|
|
||||||
if (list_empty(&ts->os_future))
|
|
||||||
return -1; /* no more events */
|
|
||||||
|
|
||||||
/* XXX: only timers and basic console input are supported */
|
|
||||||
if (!list_empty(&ts->os_future))
|
|
||||||
{
|
|
||||||
list_for_each(el, &ts->os_future)
|
|
||||||
{
|
|
||||||
JSOSFuture *th = list_entry(el, JSOSFuture, link);
|
|
||||||
auto status = th->future.wait_for(std::chrono::seconds(0));
|
|
||||||
if (status == std::future_status::ready)
|
|
||||||
{
|
|
||||||
JSOSFutureArgv argv = th->future.get()(ctx);
|
|
||||||
JSValue resolve, reject;
|
|
||||||
/* the timer expired */
|
|
||||||
resolve = th->resolve;
|
|
||||||
th->resolve = JS_UNDEFINED;
|
|
||||||
reject = th->reject;
|
|
||||||
th->reject = JS_UNDEFINED;
|
|
||||||
unlink_future(rt, th);
|
|
||||||
free_future(rt, th);
|
|
||||||
JSValue ret = call_handler(ctx, argv.count < 0 ? reject : resolve, abs(argv.count), argv.argv);
|
|
||||||
if (qjs::JS_IsException(ret))
|
|
||||||
throw qjs::exception{};
|
|
||||||
JS_FreeValue(ctx, ret);
|
|
||||||
JS_FreeValue(ctx, resolve);
|
|
||||||
JS_FreeValue(ctx, reject);
|
|
||||||
delete argv.argv;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
} // namespace qjs
|
|
@@ -1,196 +0,0 @@
|
|||||||
/*
|
|
||||||
* @Description:
|
|
||||||
* @Author: ekibun
|
|
||||||
* @Date: 2020-08-08 10:30:59
|
|
||||||
* @LastEditors: ekibun
|
|
||||||
* @LastEditTime: 2020-08-27 18:55:57
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <queue>
|
|
||||||
#include <thread>
|
|
||||||
#include <atomic>
|
|
||||||
#include <future>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include "js_dart_promise.hpp"
|
|
||||||
|
|
||||||
namespace qjs
|
|
||||||
{
|
|
||||||
struct EngineTask
|
|
||||||
{
|
|
||||||
std::function<Value(Context&)> invoke;
|
|
||||||
std::function<void(Value)> resolve;
|
|
||||||
std::function<void(Value)> reject;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct EngineTaskResolver
|
|
||||||
{
|
|
||||||
Value result;
|
|
||||||
std::function<void(Value)> resolve;
|
|
||||||
std::function<void(Value)> reject;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string getStackTrack(Value exc)
|
|
||||||
{
|
|
||||||
std::string err = (std::string)exc;
|
|
||||||
if ((bool)exc["stack"])
|
|
||||||
err += "\n" + (std::string)exc["stack"];
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Engine
|
|
||||||
{
|
|
||||||
// 引擎线程
|
|
||||||
std::thread thread;
|
|
||||||
// 任务队列
|
|
||||||
std::queue<EngineTask> tasks;
|
|
||||||
// 同步
|
|
||||||
std::mutex m_lock;
|
|
||||||
// 是否关闭提交
|
|
||||||
std::atomic<bool> stoped;
|
|
||||||
|
|
||||||
void handleException(qjs::Value exc)
|
|
||||||
{
|
|
||||||
printf("%s", getStackTrack(exc).c_str());
|
|
||||||
std::cout << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
inline Engine(std::function<std::promise<JSFutureReturn> *(std::string, Value, Engine *)> channel) : stoped{false}
|
|
||||||
{
|
|
||||||
thread = std::thread([this, channel = [this, channel](std::string method, Value args){
|
|
||||||
return channel(method, args, this);
|
|
||||||
}] {
|
|
||||||
// 创建运行环境
|
|
||||||
Runtime rt;
|
|
||||||
js_init_handlers(rt.rt, channel);
|
|
||||||
Context ctx(rt);
|
|
||||||
auto &module = ctx.addModule("__DartImpl");
|
|
||||||
module.function<&js_dart_future>("__invoke");
|
|
||||||
ctx.eval(
|
|
||||||
R"xxx(
|
|
||||||
import * as __DartImpl from "__DartImpl";
|
|
||||||
globalThis.dart = (method, ...args) => new Promise((res, rej) =>
|
|
||||||
__DartImpl.__invoke(res, rej, method, args));
|
|
||||||
)xxx",
|
|
||||||
"<dart>", JS_EVAL_TYPE_MODULE);
|
|
||||||
JS_SetModuleLoaderFunc(rt.rt, nullptr, js_module_loader, nullptr);
|
|
||||||
std::vector<EngineTaskResolver> unresolvedTask;
|
|
||||||
Value promiseWrapper = ctx.eval(
|
|
||||||
R"xxx(
|
|
||||||
(value) => {
|
|
||||||
const __ret = Promise.resolve(value)
|
|
||||||
.then(v => {
|
|
||||||
__ret.__value = v;
|
|
||||||
__ret.__resolved = true;
|
|
||||||
}).catch(e => {
|
|
||||||
__ret.__error = e;
|
|
||||||
__ret.__rejected = true;
|
|
||||||
});
|
|
||||||
return __ret;
|
|
||||||
}
|
|
||||||
)xxx",
|
|
||||||
"<PromiseWrapper>", JS_EVAL_TYPE_GLOBAL);
|
|
||||||
|
|
||||||
// 循环
|
|
||||||
while (!this->stoped)
|
|
||||||
{
|
|
||||||
// 获取待执行的task
|
|
||||||
EngineTask task;
|
|
||||||
{ // 获取一个待执行的 task
|
|
||||||
std::unique_lock<std::mutex> lock{this->m_lock}; // unique_lock 相比 lock_guard 的好处是:可以随时 unlock() 和 lock()
|
|
||||||
if (!this->tasks.empty())
|
|
||||||
{
|
|
||||||
task = this->tasks.front(); // 取一个 task
|
|
||||||
this->tasks.pop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 执行task
|
|
||||||
if (task.resolve)
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Value val = task.invoke(ctx);
|
|
||||||
Value ret = Value{ctx.ctx, JS_Call(ctx.ctx, promiseWrapper.v, ctx.global().v, 1, &(val.v))};
|
|
||||||
unresolvedTask.emplace_back(EngineTaskResolver{ret, std::move(task.resolve), std::move(task.reject)});
|
|
||||||
}
|
|
||||||
catch (exception)
|
|
||||||
{
|
|
||||||
task.reject(ctx.getException());
|
|
||||||
}
|
|
||||||
// 执行microtask
|
|
||||||
JSContext *pctx;
|
|
||||||
for (;;)
|
|
||||||
{
|
|
||||||
int err = JS_ExecutePendingJob(rt.rt, &pctx);
|
|
||||||
if (err <= 0)
|
|
||||||
{
|
|
||||||
if (err < 0)
|
|
||||||
handleException(ctx.getException());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO 检查promise状态
|
|
||||||
for (auto it = unresolvedTask.begin(); it != unresolvedTask.end();)
|
|
||||||
{
|
|
||||||
bool finished = false;
|
|
||||||
if (it->result["__resolved"])
|
|
||||||
{
|
|
||||||
it->resolve(it->result["__value"]);
|
|
||||||
finished = true;
|
|
||||||
};
|
|
||||||
if (it->result["__rejected"])
|
|
||||||
{
|
|
||||||
it->reject(it->result["__error"]);
|
|
||||||
finished = true;
|
|
||||||
};
|
|
||||||
if (finished)
|
|
||||||
it = unresolvedTask.erase(it);
|
|
||||||
else
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
// 检查dart交互
|
|
||||||
bool idle = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
idle = js_dart_poll(ctx.ctx);
|
|
||||||
}
|
|
||||||
catch (exception)
|
|
||||||
{
|
|
||||||
handleException(ctx.getException());
|
|
||||||
}
|
|
||||||
// 空闲时reject所有task
|
|
||||||
if (idle && !JS_IsJobPending(rt.rt) && !unresolvedTask.empty())
|
|
||||||
{
|
|
||||||
for (EngineTaskResolver &_task : unresolvedTask)
|
|
||||||
{
|
|
||||||
_task.reject(ctx.newValue("Promise cannot resolve"));
|
|
||||||
}
|
|
||||||
unresolvedTask.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
js_free_handlers(rt.rt);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
inline ~Engine()
|
|
||||||
{
|
|
||||||
stoped.store(true);
|
|
||||||
if (thread.joinable())
|
|
||||||
thread.join(); // 等待任务结束, 前提:线程一定会执行完
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
// 提交一个任务
|
|
||||||
void commit(EngineTask task)
|
|
||||||
{
|
|
||||||
if (stoped.load()) // stop == true ??
|
|
||||||
throw std::runtime_error("commit on stopped engine.");
|
|
||||||
{ // 添加任务到队列
|
|
||||||
std::lock_guard<std::mutex> lock{m_lock}; //对当前块的语句加锁 lock_guard 是 mutex 的 stack 封装类,构造的时候 lock(),析构的时候 unlock()
|
|
||||||
tasks.emplace(task);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace qjs
|
|
Submodule cxx/quickjs updated: b994795091...56cbb57a2e
1294
cxx/quickjspp.hpp
1294
cxx/quickjspp.hpp
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
|||||||
* @Author: ekibun
|
* @Author: ekibun
|
||||||
* @Date: 2020-08-08 08:16:51
|
* @Date: 2020-08-08 08:16:51
|
||||||
* @LastEditors: ekibun
|
* @LastEditors: ekibun
|
||||||
* @LastEditTime: 2020-09-21 01:20:17
|
* @LastEditTime: 2020-09-21 13:50:40
|
||||||
*/
|
*/
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
@@ -53,13 +53,12 @@ class _TestPageState extends State<TestPage> {
|
|||||||
_createEngine() async {
|
_createEngine() async {
|
||||||
if (engine != null) return;
|
if (engine != null) return;
|
||||||
engine = FlutterQjs();
|
engine = FlutterQjs();
|
||||||
engine.setMethodHandler((String method, List arg) async {
|
engine.setMethodHandler((String method, List arg) {
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case "http":
|
case "http":
|
||||||
Response response = await Dio().get(arg[0]);
|
return Dio().get(arg[0]).then((response) => response.data);
|
||||||
return response.data;
|
|
||||||
case "test":
|
case "test":
|
||||||
return await arg[0]([
|
return arg[0]([
|
||||||
true,
|
true,
|
||||||
1,
|
1,
|
||||||
0.5,
|
0.5,
|
||||||
@@ -72,7 +71,7 @@ class _TestPageState extends State<TestPage> {
|
|||||||
Float32List(2)
|
Float32List(2)
|
||||||
]);
|
]);
|
||||||
default:
|
default:
|
||||||
throw Exception("NotImplement");
|
throw Exception("No such method");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
engine.setModuleHandler((String module) {
|
engine.setModuleHandler((String module) {
|
||||||
|
32
lib/ffi.dart
32
lib/ffi.dart
@@ -3,7 +3,7 @@
|
|||||||
* @Author: ekibun
|
* @Author: ekibun
|
||||||
* @Date: 2020-09-19 10:29:04
|
* @Date: 2020-09-19 10:29:04
|
||||||
* @LastEditors: ekibun
|
* @LastEditors: ekibun
|
||||||
* @LastEditTime: 2020-09-21 01:30:41
|
* @LastEditTime: 2020-09-21 14:25:47
|
||||||
*/
|
*/
|
||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
@@ -53,7 +53,31 @@ final DynamicLibrary qjsLib = Platform.environment['FLUTTER_TEST'] == 'true'
|
|||||||
? (Platform.isWindows
|
? (Platform.isWindows
|
||||||
? DynamicLibrary.open("test/build/Debug/flutter_qjs.dll")
|
? DynamicLibrary.open("test/build/Debug/flutter_qjs.dll")
|
||||||
: DynamicLibrary.process())
|
: DynamicLibrary.process())
|
||||||
: (Platform.isWindows ? DynamicLibrary.open("flutter_qjs_plugin.dll") : DynamicLibrary.process());
|
: (Platform.isWindows
|
||||||
|
? DynamicLibrary.open("flutter_qjs_plugin.dll")
|
||||||
|
: Platform.isAndroid
|
||||||
|
? DynamicLibrary.open("libqjs.so")
|
||||||
|
: DynamicLibrary.process());
|
||||||
|
|
||||||
|
/// JSValue *jsThrowInternalError(JSContext *ctx, char *message)
|
||||||
|
final Pointer Function(
|
||||||
|
Pointer ctx,
|
||||||
|
Pointer<Utf8> message,
|
||||||
|
) _jsThrowInternalError = qjsLib
|
||||||
|
.lookup<
|
||||||
|
NativeFunction<
|
||||||
|
Pointer Function(
|
||||||
|
Pointer,
|
||||||
|
Pointer<Utf8>,
|
||||||
|
)>>("jsThrowInternalError")
|
||||||
|
.asFunction();
|
||||||
|
|
||||||
|
Pointer jsThrowInternalError(Pointer ctx, String message) {
|
||||||
|
var utf8message = Utf8.toUtf8(message);
|
||||||
|
var val = _jsThrowInternalError(ctx, utf8message);
|
||||||
|
free(utf8message);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
/// JSValue *jsEXCEPTION()
|
/// JSValue *jsEXCEPTION()
|
||||||
final Pointer Function() jsEXCEPTION =
|
final Pointer Function() jsEXCEPTION =
|
||||||
@@ -95,7 +119,9 @@ Pointer jsNewRuntime(
|
|||||||
ReceivePort port,
|
ReceivePort port,
|
||||||
) {
|
) {
|
||||||
var rt = _jsNewRuntime(Pointer.fromFunction(channelDispacher));
|
var rt = _jsNewRuntime(Pointer.fromFunction(channelDispacher));
|
||||||
runtimeOpaques[rt] = RuntimeOpaque()..channel = callback..port = port;
|
runtimeOpaques[rt] = RuntimeOpaque()
|
||||||
|
..channel = callback
|
||||||
|
..port = port;
|
||||||
return rt;
|
return rt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,10 +3,11 @@
|
|||||||
* @Author: ekibun
|
* @Author: ekibun
|
||||||
* @Date: 2020-08-08 08:29:09
|
* @Date: 2020-08-08 08:29:09
|
||||||
* @LastEditors: ekibun
|
* @LastEditors: ekibun
|
||||||
* @LastEditTime: 2020-09-21 01:36:30
|
* @LastEditTime: 2020-09-21 13:46:50
|
||||||
*/
|
*/
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
|
import 'dart:io';
|
||||||
import 'dart:isolate';
|
import 'dart:isolate';
|
||||||
|
|
||||||
import 'package:ffi/ffi.dart';
|
import 'package:ffi/ffi.dart';
|
||||||
@@ -29,9 +30,10 @@ class FlutterQjs {
|
|||||||
_ensureEngine() {
|
_ensureEngine() {
|
||||||
if (_rt != null) return;
|
if (_rt != null) return;
|
||||||
_rt = jsNewRuntime((ctx, method, argv) {
|
_rt = jsNewRuntime((ctx, method, argv) {
|
||||||
|
try {
|
||||||
if (method.address != 0) {
|
if (method.address != 0) {
|
||||||
var argvs = jsToDart(ctx, argv);
|
|
||||||
if (methodHandler == null) throw Exception("No MethodHandler");
|
if (methodHandler == null) throw Exception("No MethodHandler");
|
||||||
|
var argvs = jsToDart(ctx, argv);
|
||||||
return dartToJs(ctx, methodHandler(Utf8.fromUtf8(method.cast<Utf8>()), argvs));
|
return dartToJs(ctx, methodHandler(Utf8.fromUtf8(method.cast<Utf8>()), argvs));
|
||||||
}
|
}
|
||||||
if (moduleHandler == null) throw Exception("No ModuleHandler");
|
if (moduleHandler == null) throw Exception("No ModuleHandler");
|
||||||
@@ -40,6 +42,15 @@ class FlutterQjs {
|
|||||||
free(ret);
|
free(ret);
|
||||||
});
|
});
|
||||||
return ret;
|
return ret;
|
||||||
|
} catch (e, stack) {
|
||||||
|
var err = jsThrowInternalError(ctx, e.toString() + "\n" + stack.toString());
|
||||||
|
if (method.address == 0) {
|
||||||
|
jsFreeValue(ctx, err);
|
||||||
|
deleteJSValue(err);
|
||||||
|
return Pointer.fromAddress(0);
|
||||||
|
}
|
||||||
|
return err;
|
||||||
|
}
|
||||||
}, port);
|
}, port);
|
||||||
_ctx = jsNewContextWithPromsieWrapper(_rt);
|
_ctx = jsNewContextWithPromsieWrapper(_rt);
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
* @Author: ekibun
|
* @Author: ekibun
|
||||||
* @Date: 2020-09-19 22:07:47
|
* @Date: 2020-09-19 22:07:47
|
||||||
* @LastEditors: ekibun
|
* @LastEditors: ekibun
|
||||||
* @LastEditTime: 2020-09-21 01:23:06
|
* @LastEditTime: 2020-09-21 13:56:53
|
||||||
*/
|
*/
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
@@ -53,7 +53,10 @@ class JSPromise extends JSRefValue {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (status["__rejected"] == true) {
|
if (status["__rejected"] == true) {
|
||||||
completer.completeError(status["__error"] ?? "undefined");
|
completer.completeError(parseJSException(
|
||||||
|
ctx,
|
||||||
|
e: jsGetPropertyStr(ctx, val, "__error"),
|
||||||
|
));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -93,8 +96,8 @@ Pointer jsGetPropertyStr(Pointer ctx, Pointer val, String prop) {
|
|||||||
return jsProp;
|
return jsProp;
|
||||||
}
|
}
|
||||||
|
|
||||||
String parseJSException(Pointer ctx) {
|
String parseJSException(Pointer ctx, {Pointer e}) {
|
||||||
Pointer e = jsGetException(ctx);
|
e = e ?? jsGetException(ctx);
|
||||||
var err = jsToCString(ctx, e);
|
var err = jsToCString(ctx, e);
|
||||||
if (jsValueGetTag(e) == JSTag.OBJECT) {
|
if (jsValueGetTag(e) == JSTag.OBJECT) {
|
||||||
Pointer stack = jsGetPropertyStr(ctx, e, "stack");
|
Pointer stack = jsGetPropertyStr(ctx, e, "stack");
|
||||||
@@ -192,7 +195,7 @@ dynamic jsToDart(Pointer ctx, Pointer val, {Map<int, dynamic> cache}) {
|
|||||||
int size = psize.value;
|
int size = psize.value;
|
||||||
free(psize);
|
free(psize);
|
||||||
if (buf.address != 0) {
|
if (buf.address != 0) {
|
||||||
return buf.asTypedList(size);
|
return Uint8List.fromList(buf.asTypedList(size));
|
||||||
}
|
}
|
||||||
int valptr = jsValueGetPtr(val).address;
|
int valptr = jsValueGetPtr(val).address;
|
||||||
if (cache.containsKey(valptr)) {
|
if (cache.containsKey(valptr)) {
|
||||||
|
Reference in New Issue
Block a user