From f6141fd410855a82b9bbaa02a0b2634ed137843c Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 14 Aug 2019 16:54:11 +0200 Subject: rename library->akono --- akono/src/main/cpp/CMakeLists.txt | 42 +++ akono/src/main/cpp/akono-jni.cpp | 588 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 630 insertions(+) create mode 100644 akono/src/main/cpp/CMakeLists.txt create mode 100644 akono/src/main/cpp/akono-jni.cpp (limited to 'akono/src/main/cpp') diff --git a/akono/src/main/cpp/CMakeLists.txt b/akono/src/main/cpp/CMakeLists.txt new file mode 100644 index 00000000..10d6396f --- /dev/null +++ b/akono/src/main/cpp/CMakeLists.txt @@ -0,0 +1,42 @@ +cmake_minimum_required(VERSION 3.4.1) + +add_library( + akono-jni SHARED + akono-jni.cpp +) + +set(deps_dir ${CMAKE_CURRENT_SOURCE_DIR}/../../../../deps) + +if(NOT EXISTS ${deps_dir}) + message( FATAL_ERROR "Dependency directory does not exist") +endif() + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++14") + +include_directories( + ${deps_dir}/android-node-v8/src + ${deps_dir}/android-node-v8/deps/v8/include + ${deps_dir}/android-node-v8/deps/uv/include +) + +add_library(node SHARED IMPORTED) +set_target_properties(node PROPERTIES IMPORTED_LOCATION + ${deps_dir}/compiled/${ANDROID_ABI}/libnode.so) + +add_library(v8 STATIC IMPORTED) +set_target_properties(v8 PROPERTIES IMPORTED_LOCATION + ${deps_dir}/compiled/${ANDROID_ABI}/libv8.cr.so) + +add_library(v8_platform STATIC IMPORTED) +set_target_properties(v8_platform PROPERTIES IMPORTED_LOCATION + ${deps_dir}/compiled/${ANDROID_ABI}/libv8_libplatform.cr.so) + +# Include libraries needed for hello-jni lib +target_link_libraries(akono-jni + v8 + v8_platform + node + android + log) + + diff --git a/akono/src/main/cpp/akono-jni.cpp b/akono/src/main/cpp/akono-jni.cpp new file mode 100644 index 00000000..b9e71012 --- /dev/null +++ b/akono/src/main/cpp/akono-jni.cpp @@ -0,0 +1,588 @@ +#include +#include +#include +#include + +#define NODE_WANT_INTERNALS 1 + +#include + +#include +#include +#include +#include +#include + + + +// Provide stubs so that libnode.so links properly +namespace node { + namespace native_module { + const bool has_code_cache = false; + + void NativeModuleEnv::InitializeCodeCache() {} + } + + v8::StartupData *NodeMainInstance::GetEmbeddedSnapshotBlob() { + return nullptr; + } + + const std::vector *NodeMainInstance::GetIsolateDataIndexes() { + return nullptr; + } +} + + +static int pfd[2]; +static pthread_t thr; +static const char *tag = "myapp"; + + +static void *thread_func(void *) { + ssize_t rdsz; + char buf[128]; + while ((rdsz = read(pfd[0], buf, sizeof buf - 1)) > 0) { + if (buf[rdsz - 1] == '\n') --rdsz; + buf[rdsz] = 0; /* add null-terminator */ + __android_log_write(ANDROID_LOG_DEBUG, tag, buf); + } + return 0; +} + +static void mylog(const char *msg) { + __android_log_write(ANDROID_LOG_DEBUG, tag, msg); +} + +int start_logger(const char *app_name) { + tag = app_name; + + /* make stdout line-buffered and stderr unbuffered */ + setvbuf(stdout, 0, _IOLBF, 0); + setvbuf(stderr, 0, _IONBF, 0); + + /* create the pipe and redirect stdout and stderr */ + pipe(pfd); + dup2(pfd[1], 1); + dup2(pfd[1], 2); + + /* spawn the logging thread */ + if (pthread_create(&thr, 0, thread_func, 0) == -1) + return -1; + pthread_detach(thr); + return 0; +} + + +/** + * Helper class to manage conversion from a JNI string to a C string. + */ +class JStringValue { +private: + jstring m_jstr; + const char *m_cstr; + JNIEnv *m_env; +public: + JStringValue(JNIEnv *env, jstring s) : m_env(env), m_jstr(s) { + m_cstr = env->GetStringUTFChars(s, NULL); + } + + ~JStringValue() { + m_env->ReleaseStringUTFChars(m_jstr, m_cstr); + } + + const char *operator*() { + return m_cstr; + } +}; + + +/** + * Slightly more sane wrapper around node::Init + */ +static void InitNode(std::vector argv) { + int ret_exec_argc = 0; + int ret_argc = argv.size(); + const char **ret_exec_argv = nullptr; + + node::Init(&ret_argc, &argv[0], &ret_exec_argc, &ret_exec_argv); +} + +// Forward declarations +void notifyCb(uv_async_t *async); + +static void sendMessageCallback(const v8::FunctionCallbackInfo &args); +static void loadModuleCallback(const v8::FunctionCallbackInfo &args); +static void getDataCallback(const v8::FunctionCallbackInfo &args); + +static const char *main_code = "global.__akono_run = (x) => {" + " console.log('running code', x);" + " global.eval(x);" + "};" + "" + "global.__akono_onMessage = (x) => {" + " console.log('got __akono_onMessage', x);" + "};" + "" + "mod = require('module');" + "mod._saved_findPath = mod._findPath;" + "mod._akonoMods = {};" + "mod._findPath = (request, paths, isMain) => {" + " console.log('in _findPath');" + " const res = mod._saved_findPath(request, paths, isMain);" + " if (res !== false) return res;" + " const args = JSON.stringify({ request, paths});" + " const loadResult = JSON.parse(global.__akono_loadModule(args));" + " console.log('got loadModule result', loadResult);" + " if (!loadResult) return false;" + " mod._akonoMods[loadResult.path] = loadResult;" + " console.log('returning path', loadResult.path);" + " return loadResult.path;" + "};" + "" + "function stripBOM(content) {" + " if (content.charCodeAt(0) === 0xFEFF) {" + " content = content.slice(1);" + " }" + " return content;" + "}" + "" + "mod._saved_js_extension = mod._extensions[\".js\"];" + "mod._extensions[\".js\"] = (module, filename) => {" + " console.log('handling js extension', [module, filename]);" + " if (mod._akonoMods.hasOwnProperty(filename)) {" + " const akmod = mod._akonoMods[filename];" + " console.log('found mod', akmod);" + " const content = akmod.content;" + " return module._compile(stripBOM(content), filename);" + " }" + " console.log('falling back');" + " return mod._saved_js_extension(module, filename);" + "};"; + + +class NativeAkonoInstance { +private: + static bool logInitialized; + static bool v8Initialized; + //static std::unique_ptr platform; + static node::MultiIsolatePlatform *platform; +public: + v8::Isolate *isolate; + node::Environment *environment; + v8::Persistent globalContext; + uv_async_t async_notify; + uv_loop_t *loop; + bool breakRequested = false; + JNIEnv *currentJniEnv = nullptr; + jobject currentJniThiz = nullptr; + + NativeAkonoInstance() : globalContext() { + loop = uv_default_loop(); + uv_async_init(loop, &async_notify, notifyCb); + async_notify.data = this; + + if (!logInitialized) { + start_logger("myapp"); + logInitialized = true; + } + + if (!v8Initialized) { + //platform = v8::platform::NewDefaultPlatform(); + //v8::V8::InitializePlatform(platform.get()); + //v8::V8::Initialize(); + + // Here, only the arguments used to initialize the global node/v8 platform + // are relevant, the others are skipped. + InitNode(std::vector{"node", "-e", main_code}); + platform = node::InitializeV8Platform(10); + v8::V8::Initialize(); + + v8Initialized = true; + } + + node::ArrayBufferAllocator *allocator = node::CreateArrayBufferAllocator(); + this->isolate = node::NewIsolate(allocator, uv_default_loop()); + + + v8::Isolate::Scope isolate_scope(isolate); + v8::HandleScope handle_scope(isolate); + + node::IsolateData *isolateData = node::CreateIsolateData( + this->isolate, + uv_default_loop(), + platform, + allocator); + + + globalContext.Reset(isolate, node::NewContext(isolate)); + + v8::Local context = globalContext.Get(isolate); + + // Arguments for node itself + std::vector nodeArgv{"node", "-e", "console.log('hello world');"}; + // Arguments for the script run by node + std::vector nodeExecArgv{}; + + mylog("entering global scopt"); + + v8::Context::Scope context_scope(globalContext.Get(isolate)); + + mylog("creating environment"); + + node::Environment *environment = node::CreateEnvironment( + isolateData, + globalContext.Get(isolate), + nodeArgv.size(), + &nodeArgv[0], + nodeExecArgv.size(), + &nodeExecArgv[0]); + + + mylog("loading environment"); + + node::LoadEnvironment(environment); + + mylog("finished loading environment"); + + v8::Local dataTemplate = v8::ObjectTemplate::New(isolate); + dataTemplate->SetInternalFieldCount(1); + v8::Local dataObject = dataTemplate->NewInstance(context).ToLocalChecked(); + dataObject->SetAlignedPointerInInternalField(0, this); + + v8::Local sendMessageFunction = v8::Function::New(context, + sendMessageCallback, + dataObject).ToLocalChecked(); + + v8::Local loadModuleFunction = v8::Function::New(context, + loadModuleCallback, + dataObject).ToLocalChecked(); + + v8::Local getDataFunction = v8::Function::New(context, + getDataCallback, + dataObject).ToLocalChecked(); + + v8::Local global = context->Global(); + + global->Set(v8::String::NewFromUtf8(isolate, "__akono_sendMessage", + v8::NewStringType::kNormal).ToLocalChecked(), + sendMessageFunction); + + global->Set(v8::String::NewFromUtf8(isolate, "__akono_loadModule", + v8::NewStringType::kNormal).ToLocalChecked(), + loadModuleFunction); + + // Get data synchronously (!) from the embedder + global->Set(v8::String::NewFromUtf8(isolate, "__akono_getData", + v8::NewStringType::kNormal).ToLocalChecked(), + getDataFunction); + + } + + /** + * Process the node message loop until a break has been requested. + * + * @param env JNI env of the thread we're running in. + */ + void runNode() { + this->breakRequested = false; + while (1) { + uv_run(uv_default_loop(), UV_RUN_ONCE); + if (this->breakRequested) + break; + } + } + + /** + * Inject code into the running node instance. + * + * Must not be called from a different thread. + */ + void makeCallback(const char *code) { + mylog("in makeCallback"); + v8::Isolate::Scope isolate_scope(isolate); + v8::HandleScope handle_scope(isolate); + v8::Local context = globalContext.Get(isolate); + v8::Context::Scope context_scope(context); + v8::Local global = context->Global(); + v8::Local argv[] = { + v8::String::NewFromUtf8(isolate, code, + v8::NewStringType::kNormal).ToLocalChecked() + }; + mylog("calling node::MakeCallback"); + node::MakeCallback(isolate, global, "__akono_run", 1, argv, {0, 0}); + } + + ~NativeAkonoInstance() { + //this->isolate->Dispose(); + } + + jstring evalJs(JNIEnv *env, jstring sourceString) { + mylog("begin evalJs"); + + JStringValue jsv(env, sourceString); + + v8::Isolate::Scope isolate_scope(isolate); + + // Create a stack-allocated handle scope. + v8::HandleScope handle_scope(isolate); + + // Create a new context. + //v8::Local context = v8::Context::New(isolate); + + v8::Local context = globalContext.Get(isolate); + + // Enter the context for compiling and running the hello world script. + v8::Context::Scope context_scope(context); + + { + // Create a string containing the JavaScript source code. + v8::Local source = + v8::String::NewFromUtf8(isolate, *jsv, + v8::NewStringType::kNormal) + .ToLocalChecked(); + + // Compile the source code. + v8::Local script; + + mylog("about to compile script"); + + if (!v8::Script::Compile(context, source).ToLocal(&script)) { + return nullptr; + } + + mylog("about to run script"); + + // Run the script to get the result. + v8::Local result; + if (!script->Run(context).ToLocal(&result)) { + mylog("running script failed"); + return nullptr; + } + + mylog("converting script result value"); + + // Convert the result to an UTF8 string and print it. + v8::String::Utf8Value utf8(isolate, result); + + mylog("about to return value"); + + return env->NewStringUTF(*utf8); + } + } +}; + + +bool NativeAkonoInstance::v8Initialized = false; +bool NativeAkonoInstance::logInitialized = false; +node::MultiIsolatePlatform *NativeAkonoInstance::platform = nullptr; + + +void notifyCb(uv_async_t *async) { + NativeAkonoInstance *akono = (NativeAkonoInstance *) async->data; + mylog("async notifyCb called!"); + akono->breakRequested = true; +} + +static void sendMessageCallback(const v8::FunctionCallbackInfo &args) { + if (args.Length() < 1) return; + v8::Isolate *isolate = args.GetIsolate(); + v8::HandleScope scope(isolate); + v8::Local arg = args[0]; + v8::String::Utf8Value value(isolate, arg); + mylog("sendMessageCallback called, yay!"); + + v8::Local data = v8::Local::Cast(args.Data()); + + mylog("getting instance"); + NativeAkonoInstance *myInstance = (NativeAkonoInstance *) data->GetAlignedPointerFromInternalField(0); + + JNIEnv *env = myInstance->currentJniEnv; + + if (env == nullptr) { + mylog("FATAL: JNI env is nullptr"); + return; + } + + mylog("finding class"); + jclass clazz = env->FindClass("akono/AkonoJni"); + + if (clazz == nullptr) { + mylog("FATAL: class not found"); + return; + } + + mylog("creating strings"); + jstring jstr1 = env->NewStringUTF("message"); + jstring jstr2 = env->NewStringUTF(*value); + + mylog("getting method"); + + jmethodID meth = env->GetMethodID(clazz, "internalOnNotify", "(Ljava/lang/String;Ljava/lang/String;)V"); + + if (meth == nullptr) { + mylog("FATAL: method not found"); + return; + } + + mylog("calling method"); + + env->CallVoidMethod(myInstance->currentJniThiz, meth, jstr1, jstr2); +} + + +static void loadModuleCallback(const v8::FunctionCallbackInfo &args) { + if (args.Length() < 1) return; + v8::Isolate *isolate = args.GetIsolate(); + v8::HandleScope scope(isolate); + v8::Local arg = args[0]; + v8::String::Utf8Value value(isolate, arg); + mylog("sendMessageCallback called, yay!"); + + v8::Local data = v8::Local::Cast(args.Data()); + + mylog("getting instance"); + NativeAkonoInstance *myInstance = (NativeAkonoInstance *) data->GetAlignedPointerFromInternalField(0); + + JNIEnv *env = myInstance->currentJniEnv; + + if (env == nullptr) { + mylog("FATAL: JNI env is nullptr"); + return; + } + + mylog("finding class"); + jclass clazz = env->FindClass("akono/AkonoJni"); + + if (clazz == nullptr) { + mylog("FATAL: class not found"); + return; + } + + mylog("creating strings"); + jstring jstr1 = env->NewStringUTF(*value); + + mylog("getting method"); + + jmethodID meth = env->GetMethodID(clazz, "internalOnModuleLoad", "(Ljava/lang/String;)Ljava/lang/String;"); + + if (meth == nullptr) { + mylog("FATAL: method not found"); + return; + } + + mylog("calling method"); + + jstring jresult = (jstring) env->CallObjectMethod(myInstance->currentJniThiz, meth, jstr1); + + JStringValue resultStringValue(env, jresult); + + printf("before creating string, res %s\n", *resultStringValue); + + // Create a string containing the JavaScript source code. + v8::Local rs = + v8::String::NewFromUtf8(isolate, *resultStringValue, + v8::NewStringType::kNormal) + .ToLocalChecked(); + + args.GetReturnValue().Set(rs); +} + + +static void getDataCallback(const v8::FunctionCallbackInfo &args) { + if (args.Length() < 1) return; + v8::Isolate *isolate = args.GetIsolate(); + v8::HandleScope scope(isolate); + v8::Local arg = args[0]; + v8::String::Utf8Value value(isolate, arg); + mylog("getDataCallback called"); + + v8::Local data = v8::Local::Cast(args.Data()); + mylog("getting instance"); + NativeAkonoInstance *myInstance = (NativeAkonoInstance *) data->GetAlignedPointerFromInternalField(0); + + JNIEnv *env = myInstance->currentJniEnv; + if (env == nullptr) { + mylog("FATAL: JNI env is nullptr"); + return; + } + + mylog("finding class"); + jclass clazz = env->FindClass("akono/AkonoJni"); + + if (clazz == nullptr) { + mylog("FATAL: class not found"); + return; + } + + mylog("creating strings"); + jstring jstr1 = env->NewStringUTF(*value); + + mylog("getting method"); + + jmethodID meth = env->GetMethodID(clazz, "internalOnGetData", "(Ljava/lang/String;)Ljava/lang/String;"); + + if (meth == nullptr) { + mylog("FATAL: method not found"); + return; + } + + mylog("calling method"); + + jstring jresult = (jstring) env->CallObjectMethod(myInstance->currentJniThiz, meth, jstr1); + + JStringValue resultStringValue(env, jresult); + + printf("before creating string, res %s\n", *resultStringValue); + + // Create a string containing the JavaScript source code. + v8::Local rs = + v8::String::NewFromUtf8(isolate, *resultStringValue, + v8::NewStringType::kNormal) + .ToLocalChecked(); + + args.GetReturnValue().Set(rs); +} + + +extern "C" JNIEXPORT jobject JNICALL +Java_akono_AkonoJni_initNative(JNIEnv *env, jobject thiz) { + NativeAkonoInstance *myInstance = new NativeAkonoInstance(); + return env->NewDirectByteBuffer(myInstance, 0); +} + + +extern "C" JNIEXPORT void JNICALL +Java_akono_AkonoJni_destroyNative(JNIEnv *env, jobject thiz, jobject buf) { + NativeAkonoInstance *myInstance = (NativeAkonoInstance *) env->GetDirectBufferAddress(buf); + delete myInstance; +} + + +extern "C" JNIEXPORT jstring JNICALL +Java_akono_AkonoJni_evalJs(JNIEnv *env, jobject thiz, jstring sourceStr, jobject buf) { + NativeAkonoInstance *myInstance = (NativeAkonoInstance *) env->GetDirectBufferAddress(buf); + return myInstance->evalJs(env, sourceStr); +} + +extern "C" JNIEXPORT void JNICALL +Java_akono_AkonoJni_notifyNative(JNIEnv *env, jobject thiz, jobject buf) { + NativeAkonoInstance *myInstance = (NativeAkonoInstance *) env->GetDirectBufferAddress(buf); + uv_async_send(&myInstance->async_notify); +} + +extern "C" JNIEXPORT void JNICALL +Java_akono_AkonoJni_runNode(JNIEnv *env, jobject thiz, jobject buf) { + NativeAkonoInstance *myInstance = (NativeAkonoInstance *) env->GetDirectBufferAddress(buf); + myInstance->currentJniEnv = env; + myInstance->currentJniThiz = thiz; + myInstance->runNode(); +} + +extern "C" JNIEXPORT void JNICALL +Java_akono_AkonoJni_makeCallbackNative(JNIEnv *env, jobject thiz, jstring sourceStr, jobject buf) { + JStringValue jsv(env, sourceStr); + NativeAkonoInstance *myInstance = (NativeAkonoInstance *) env->GetDirectBufferAddress(buf); + myInstance->currentJniEnv = env; + myInstance->currentJniThiz = thiz; + return myInstance->makeCallback(*jsv); +} -- cgit v1.2.3