ffi android

This commit is contained in:
ekibun
2020-09-21 14:45:04 +08:00
parent 9175871678
commit 53671c8be0
18 changed files with 81 additions and 2366 deletions

View File

@@ -42,7 +42,7 @@ android {
}
externalNativeBuild {
cmake {
path "src/main/jni/CMakeLists.txt"
path "src/main/cxx/CMakeLists.txt"
version "3.10.2"
}
}

View File

@@ -4,13 +4,15 @@
# Sets the minimum version of CMake required to build the native library.
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
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# 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.
${JNI_LIB_NAME}
@@ -18,10 +20,10 @@ add_library( # Sets the name of the library.
SHARED
# Provides a relative path to your source file(s).
native-lib.cpp )
${CXX_LIB_DIR}/ffi.cpp )
# 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)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DDUMP_LEAKS -DCONFIG_VERSION=\\\"${QUICKJS_VERSION}\\\"")
target_sources(${JNI_LIB_NAME} PUBLIC

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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};
});
}

View File

@@ -2,55 +2,16 @@ package soko.ekibun.flutter_qjs
import android.os.Handler
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 */
class FlutterQjsPlugin: FlutterPlugin, MethodCallHandler {
class FlutterQjsPlugin: FlutterPlugin {
/// 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
/// 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) {
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) {
channel.setMethodCallHandler(null)
}
}

View File

@@ -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?)
}

View File

@@ -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)
}
})
}
}
}

View File

@@ -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) }
}
}