background
I learned about QuickJS through this article. I believe that understanding the underlying engine running mechanism of JS will be of great help to learning this language.
The QuickJS engine is written in C language, and the author provides the MakeFile of the build file under Linux. Check the company test server does not have GCC environment, online search to install a new GCC is a bit time-consuming. Poor with a Windows PC and a Redmi Android, I did some digging and found a way to compile QuickJS for Windows.
Coming from Android, I wanted to try to run QuickJS on Android, but I had hardly ever worked on a JNI project, so I had to spend two days over the weekend getting QuickJS compiled on Android.
Actually use CMake can in a variety of platforms (Linux/Windows/macos/android, etc.) build project C, just need to write CMakeLists. TXT, premise is like know CMakeLists. TXT syntax.
Proficient in various languages and frameworks (Java, Kotlin, JS, HTML, CSS, Dart…) Proficient in Android system (application layer, application framework layer, JNI, core C++ library, ART, hardware layer, Linux kernel…) , proficient in various application IDES and building tools (AS, Gradle, CMake, Webpack, Node…) Proficient in beautiful UI development (smooth and exquisite Android page, silky and shocking H5 page, beautiful and blind animation effects…) , proficient in the method of masturbation (easy to understand but not yet well-designed elegance, easy to use but not yet convenient expansion of the fullness of…) , proficient in all kinds of underlying implementation (JVM implementation, GC algorithm, Android system startup process, Android application startup process, Android multi-process communication, multi-thread concurrency, data structure, algorithm, browser kernel, JS engine, typesetting engine, rendering engine), proficient in all kinds of wheel source code implementation, proficient in all the above content Use scenarios and various optimizations and better use methods…
Here’s how to build QuickJS for Windows and Android.
Build QuickJS project under Windows
Enter here, follow the instructions to install MSYS2 and mingw toolchain and build
Pacman -s mingw-w64-x86_64-dlfcn (‘ libdl ‘is not found); pacman -s mingw-w64-x86_64-dlfcn (‘ mingw-w64-x86_64-dlfcn’)
HOST_LIBS=-lm -ldl -lpthread
LIBS=-lm
ifndef CONFIG_WIN32
LIBS+=-ldl -lpthread
Copy the code
After compiling, it generates some executable files that you can play with as instructed by the author
Build QuickJS project under Android
The purpose is simple: execute a piece of JS code by JNI calling a method provided by QuickJS
Therefore, I need crazy imagination, from which I learned to use two methods to register Native methods: static registration and dynamic registration. In actual projects, dynamic registration is preferred. Here is a Facebook wheel fbjni for easy enjoyment. This is where I learned how to build C projects using CMake on VSCode, and here, here, and here fbjni learned a little bit about writing cmakelists.txt.
In the article mentioned in the first paragraph, there is a QuickJS project for C++ and a QuickJS project for Android. I am not able to call the API provided by QuickJS, nor can I understand the various JNI operations in this Android project. Compared with C++, the project is more refreshing, so I choose it as the shoulder of giants. The details of this project are introduced below
Cmakelists.txt in the QuickJS directory
project(quickjs LANGUAGES C)
file(STRINGS VERSION version)
set(quickjs_src quickjs.c libbf.c libunicode.c libregexp.c cutils.c quickjs-libc.c)
set(quickjs_def CONFIG_VERSION="${version}" _GNU_SOURCE CONFIG_BIGNUM)
add_library(quickjs ${quickjs_src})
target_compile_definitions(quickjs PRIVATE ${quickjs_def} )
add_library(quickjs-dumpleaks ${quickjs_src})
target_compile_definitions(quickjs-dumpleaks PRIVATE ${quickjs_def} DUMP_LEAKS=1)
if(UNIX)
find_package(Threads)
target_link_libraries(quickjs ${CMAKE_DL_LIBS} m Threads::Threads)
target_link_libraries(quickjs-dumpleaks ${CMAKE_DL_LIBS} m Threads::Threads)
endif()
Copy the code
I was so confused that I finally understood the document: Declare a C (not C++) project named QuickJS, read the contents of the VERSION file as the value of the variable VERSION (2020-09-06 at the time of writing), C libbf.c libunicode.c libregexp. C cutils.c Quickjs-libc.c), set the build parameter quickjs_def (CONFIG_VERSION _GNU_SOURCE CONFIG_BIGNUM, Target_compile_definitions is what this works for), generate a static library called QuickJS for this project, generate a static library called QuickJs-Dumpleaks for this project, and end up in UNIX.
Quickjs-libc. c is a runtime library that relies on the quickjs.c engine.
Cmakelists.txt in the project root directory
Cmake_minimum_required (VERSION 3.8) Project (QuickJSPp) Set (CMAKE_CXX_STANDARD 17) #set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) if(CMAKE_COMPILER_IS_GNUCC) add_compile_options(-Wall -Wno-unused-parameter) endif() add_subdirectory(quickjs) add_executable(qjs qjs.cpp) target_link_libraries(qjs quickjs) enable_testing() add_subdirectory(test)Copy the code
The main project named quickjscpp, compiled using C++17 standard, added quickjs and test sub-projects, generated binaries using qjs.cpp as the entry and relied on quickjs library
qjs.cpp
#include "quickjspp.hpp" #include <iostream> int main(int argc, char ** argv) { JSRuntime * rt; JSContext * ctx; using namespace qjs; Runtime runtime; rt = runtime.rt; Context context(runtime); ctx = context.ctx; js_std_init_handlers(rt); /* loader for ES6 modules */ JS_SetModuleLoaderFunc(rt, nullptr, js_module_loader, nullptr); js_std_add_helpers(ctx, argc - 1, argv + 1); /* system modules */ js_init_module_std(ctx, "std"); js_init_module_os(ctx, "os"); /* make 'std' and 'os' visible to non module code */ const char * str = "import * as std from 'std'; \n" "import * as os from 'os'; \n" "globalThis.std = std; \n" "globalThis.os = os; \n"; context.eval(str, "<input>", JS_EVAL_TYPE_MODULE); try { if(argv[1]) context.evalFile(argv[1], JS_EVAL_TYPE_MODULE); } catch(exception) { //js_std_dump_error(ctx); auto exc = context.getException(); std::cerr << (exc.isError() ? "Error: " : "Throw: ") << (std::string)exc << std::endl; if((bool)exc["stack"]) std::cerr << (std::string)exc["stack"] << std::endl; js_std_free_handlers(rt); return 1; } js_std_loop(ctx); js_std_free_handlers(rt); return 0; }Copy the code
The main method creates a runtime JSRuntime and a context JSContext, using the context to call the Eval method provided by QuickJS to execute a piece of JS code.
We need to modify the two cmakelists.txt to make it more palatable to Android JNI.
Modify cmakelists.txt in quickJS directory
project(quickjs LANGUAGES C) file(STRINGS VERSION version) find_library( # Sets the name of the path variable. log-lib # Specifies the name of the NDK library that # you want CMake to locate. log) set(quickjs_src quickjs.c libbf.c libunicode.c libregexp.c cutils.c quickjs-libc.c) set(quickjs_def CONFIG_VERSION="${version}" _GNU_SOURCE CONFIG_BIGNUM) add_library(quickjs ${quickjs_src}) target_link_libraries(quickjs ${log-lib}) target_compile_definitions(quickjs PRIVATE ${quickjs_def} )Copy the code
The NDK Android Log library was added in order to enable JS console. Log logs to appear on the Android console
Modify cmakelists.txt in the project root directory
Cmake_minimum_required (VERSION 3.8) Project (QJSPP) set(CMAKE_CXX_STANDARD 17) file(GLOB libfbjni_link_DIRS "${build_DIR}/fbjni*.aar/jni/${ANDROID_ABI}") file(GLOB libfbjni_include_DIRS "${build_DIR}/fbjni-*-headers.jar/") find_library(FBJNI_LIBRARY fbjni PATHS ${libfbjni_link_DIRS} NO_CMAKE_FIND_ROOT_PATH) find_library( # Sets the name of the path variable. log-lib # Specifies the name of the NDK library that # you want CMake to locate. log) add_subdirectory(quickjs) add_library(qjs SHARED qjs.cpp) target_include_directories(qjs PRIVATE // Additional header directories here ${libfbjni_include_DIRS} ) target_link_libraries(qjs quickjs jsi ${FBJNI_LIBRARY} ${log-lib})Copy the code
In addition to the adjustment to output the QJS shared library, fBjNI (Maven library) has been added as provided by Facebook, and jSI (JSBridge abstraction layer provided by Facebook, which is convenient to replace the JS engine) has been added. Of course, fBjNI and JSI are not used here. Just learn how to refer to external libraries.
Add JNI functionality
The Java part
public class MyDemo { static { System.loadLibrary("qjs"); } public native String stringJNI(); Public String run() {// Implement native method return stringJNI(); } public void print(String MSG) {log. d("MyDemo", MSG); }}Copy the code
Qjs. so, define the native method stringJNI, and then run and print. Run is called in the MainActvity thread.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Example of a call to a native method
Thread() {
MyDemo().run()
}.start()
}
}
Copy the code
Part c + +
Modify qjs.cpp directly, replace the main method with testQjs, and add some JNI functionality
// // Created by flower on 2020/9/6. // #include "qjspp.hpp" #include <iostream> #include <jni.h> #include <fbjni/fbjni.h> #include <iostream> #include <jsi/jsi.h> #include <fbjni/detail/Hybrid *msg); void showMsg(char *msg) { LOGI("message is %s", msg); } #ifdef __cplusplus extern "C" { #endif //extern "C" JNIEXPORT jstring JNICALL //Java_com_ndk_demo_MainActivity_stringFromJNI( // JNIEnv *env, // jobject /* this */) { // std::string hello = "Hello from C++"; // // return env->NewStringUTF(hello.c_str()); //} static const char *className = "com/ndk/demo/MyDemo"; int testQjs(int argc, char **argv) { JSRuntime *rt; JSContext *ctx; using namespace qjs; Runtime runtime; rt = runtime.rt; Context context(runtime); ctx = context.ctx; js_std_init_handlers(rt); /* loader for ES6 modules */ JS_SetModuleLoaderFunc(rt, nullptr, js_module_loader, nullptr); js_std_add_helpers(ctx, argc - 1, argv + 1); /* system modules */ js_init_module_std(ctx, "std"); js_init_module_os(ctx, "os"); /* make 'std' and 'os' visible to non module code */ const char *str = "let a = 0; a++; console.log(a); console.log('qjs well')"; try { context.eval(str); } catch (exception) { //js_std_dump_error(ctx); auto exc = context.getException(); std::cerr << (exc.isError() ? "Error: " : "Throw: ") << (std::string) exc << std::endl; if ((bool) exc["stack"]) std::cerr << (std::string) exc["stack"] << std::endl; js_std_free_handlers(rt); return 1; } js_std_loop(ctx); js_std_free_handlers(rt); return 0; } static jstring stringFromJNI(JNIEnv *env, jobject jobj) { showMsg("welcome to jni"); jclass clazz = facebook::jni::detail::findClass(env, className); LOGI("java class", clazz); jmethodID mid = env->GetMethodID(clazz, "print", "(Ljava/lang/String;) V"); std::string msg = "hello, java"; env->CallVoidMethod(jobj, mid, env->NewStringUTF(msg.c_str())); LOGI("run native method stringFromJNI with param handle"); std::string hello = "hello, this is from c++"; testQjs(0, NULL); return env->NewStringUTF(hello.c_str()); } static JNINativeMethod gJni_Methods_table[] = { {"stringJNI", "()Ljava/lang/String;" , reinterpret_cast<void *>(stringFromJNI)} }; static int jniRegisterNativeMethods(JNIEnv *env, const char *className, const JNINativeMethod *gMethods, int numMethods) { jclass clazz; LOGI("register native methods for java class %s", className); clazz = env->FindClass(className); if (clazz == NULL) { LOGI("cannot find java class %s", className); return -1; } int result = 0; if ((env)->RegisterNatives(clazz, gMethods, numMethods) < 0) { LOGI("register failed"); result = -1; } (env)->DeleteLocalRef(clazz); return result; } jint JNI_OnLoad(JavaVM *vm, void *reserved) { LOGI("jni onload"); JNIEnv *env = NULL; if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) ! = JNI_OK) { return JNI_ERR; } jniRegisterNativeMethods(env, className, gJni_Methods_table, sizeof(gJni_Methods_table) / sizeof(JNINativeMethod)); return JNI_VERSION_1_6; } #ifdef __cplusplus } #endifCopy the code
Understand the code in qjs.cpp
The native method stringFromJNI is registered in a dynamic manner, corresponding to the stringJNI method defined in the MyDemo class of the Java layer. You can see that the two names are different, but they are still interoperable, which indicates that there is a relationship between Java — JNI — C.
In stringFromJNI, the code finally calls the testQjs method, which is modified from the main method, with the major changes here
const char *str = "let a = 0; a++; console.log(a); console.log('qjs well')"; context.eval(str);Copy the code
A string of JS code is executed and the value of variable A is printed along with a paragraph of text.
I don’t know the eval method yet, nor how to implement the log method of console. So I did a global searchconsole
The result is as follows
Not a smart person, I looked at the last two items and found that they fit together
JS_SetPropertyStr(ctx, console, "log",
JS_NewCFunction(ctx, js_print, "log", 1));
JS_SetPropertyStr(ctx, global_obj, "console", console);
Copy the code
I’ve figured out how to implement the log method of the global object Console in QuickJS: hang the log method of the console object on the js_print method. Take a look at the js_print method
static JSValue js_print(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { int i; const char *str; size_t len; for(i = 0; i < argc; i++) { if (i ! = 0) putchar(' '); str = JS_ToCStringLen(ctx, &len, argv[i]); if (! str) return JS_EXCEPTION; fwrite(str, 1, len, stdout); JS_FreeCString(ctx, str); } putchar('\n'); return JS_UNDEFINED; }Copy the code
Understand and modify the js_print method
I see this sentence fwrite(STR, 1, len, stdout); The stdout output should be visible only in the shell environment, not in the Android Studio console.
The NDK provides a library called log-lib. The NDK provides a library called log-lib. The NDK provides a library called log-lib. This is where I added the log-lib library to cmakelists.txt in the Quickjs directory. I added the print method in the quickjs.h header of the engine quickjs.c that quickjs-libc.c depends on at runtime
#include <android/log.h> // macro define log print #define LOGI(... __android_log_print(ANDROID_LOG_INFO, "ndk-demo",__VA_ARGS__)Copy the code
Then write fwrite(STR, 1, len, stdout) above; Replace this with LOGI(“qjs-log :%s”, STR); After finishing the masturbation, I ran the APP again and saw the log printed in Android Studio as if I had found my girlfriend
620-09-10 10:16:54.887 26639-26639/com.ndk.demo W/com.ndk.demo: Accessing hidden method Landroid/view/ view; ->computeFitSystemWindows(Landroid/graphics/Rect; Landroid/graphics/Rect;) Z (Light Greylist, Reflection) 2020-09-10 10:16:54.88 26639-26639/com.ndk.demo W/com.ndk.demo: Accessing hidden method Landroid/view/ViewGroup; ->makeOptionalFitsSystemWindows()V (light greylist, Reflection) 2020-09-10 10:16:54.929 26639-26667/com.ndk. Demo I/ ndK-demo: Jni onLOAD 2020-09-10 10:16:54.929 26639-26667/com.ndk. Demo I/ ndK-demo: Register Native Methods for Java Class com/ NDK /MyDemo 2020-09-10 10:16:54.929 26639-26667/com.ndk. Demo I/ ndK-demo: Message welcome to JNI 2020-09-10 10:16:54.929 26639-26667/com.ndk. Demo I/ ndK-demo: Java Class 2020-09-10 10:16:54.929 26639-26667/com.ndk. Demo D/MyDemo: Hello, Java 2020-09-10 10:16:54.929 26639-26667/com.ndk. Demo I/ndk-demo: Run Native Method stringFromJNI with Param Handle 2020-09-10 10:16:54.931 26639-26667/com.ndk.demo I/ ndK-demo: Qjs-log :1 2020-09-10 10:16:54.931 26639-26667/com.ndk.demo I/ ndK-demo: qjS-log: QJS wellCopy the code
The last two lines are the ones that execute the PRINT of the JS code. The basic goal has been achieved, so you can learn more about it later.