quickjs在嵌入式中的应用
零、前言
之前搞过在嵌入式中引入Lua作为脚本,以实现动态执行效果。详见(https://www.cnblogs.com/wunaozai/p/14087370.html)但是众所周知原因,其实Lua远远没有JS好,一方面是目前前端的如日中天,加之前端开源的库很多。很多都可以复用。
在选型用哪个JS引擎时,参考了网上的资料,最终在JerryScript和QuickJS中间选择,最后选择QuickJS作为本次嵌入到C/C++程序中。主要原因是,虽然QuickJS占用的资源会比JerryScript多一些。但是QuickJS比较新,而且支持ES6以上语法,这样,一些NodeJS的库是可以直接用到这里的。性能相比也跟强大一些。
一、安装、编译动态库
这一步,没什么好说的,就是从github上下载代码,然后进行编译,我是用mingw进行编译的,说几个注意点
1. 源码中的Makefile需要修改为window编译,打开文件取消 CONFIG_WIN32=y 注释
2. 把DOCS相关的编译去掉,因为系统没有texi2pdf, makeinfo命令来生成文档
3. 如果在编译过程中出现 -ldl 包未找到, 可以到 https://github.com/pepstack/dlfcn-win32 编译一个 libdl.dll,然后放到编译器的lib目录下
编译后产生以下几个文件,其中libquickjs是静态链接库,用于链接到c环境实现集成。
qjs.exe就是quickjs的运行环境, qjsc.exe就是可以将js文件编译成c源码,然后把c源码编译成exe。
> qjsc -o hello helloworld.js #通过qjsc编译器,直接将js编译成可执行文件
> qjsc -e -o helloworld.c helloworld.js #将js文件编译转成c文件,上面的编译成可执行文件就是通过该代码实现
> qjsc -c -o helloworld.c helloworld.js #将js文件编译成二进制文件,类似java的字节码文件,然后可以跑到jsc虚拟机中即可,在不方便编译成so动态库的情况下,编译成字节码字节码文件也是一种不错的选择
二、测试验证
普通js测试
1 console.log("Hello World");
加载js模块
fib_module.js
1 export function fib(n) 2 { 3 if (n <= 0) 4 return 0; 5 else if (n == 1) 6 return 1; 7 else 8 return fib(n - 1) + fib(n - 2); 9 }
hello_module.js
1 import { fib } from "./fib_module.js"; 2 3 console.log("Hello World"); 4 console.log("fib(10)=", fib(10));
qjs hello_module.js
拓展 Externd
拓展,表示通过其他语言,拓展JS的能力。比如,纯JS文件,JS调用C/C++写的module。下面分为两种情况分别在Linux和Windows下如何加载
加载so模块(Linux/Unix)
加载so模块,需要在Linux环境测试、一般嵌入式设备也是Linux环境
将官方示例中的fib.c 手动编译成so动态库
1 #include "../quickjs.h" 2 3 #define countof(x) (sizeof(x) / sizeof((x)[0])) 4 5 static int fib(int n) 6 { 7 if (n <= 0) 8 return 0; 9 else if (n == 1) 10 return 1; 11 else 12 return fib(n - 1) + fib(n - 2); 13 } 14 15 static JSValue js_fib(JSContext *ctx, JSValueConst this_val, 16 int argc, JSValueConst *argv) 17 { 18 int n, res; 19 if (JS_ToInt32(ctx, &n, argv[0])) 20 return JS_EXCEPTION; 21 res = fib(n); 22 return JS_NewInt32(ctx, res); 23 } 24 25 static const JSCFunctionListEntry js_fib_funcs[] = { 26 JS_CFUNC_DEF("fib", 1, js_fib ), 27 }; 28 29 static int js_fib_init(JSContext *ctx, JSModuleDef *m) 30 { 31 return JS_SetModuleExportList(ctx, m, js_fib_funcs, 32 countof(js_fib_funcs)); 33 } 34 35 #ifdef JS_SHARED_LIBRARY 36 #define JS_INIT_MODULE js_init_module 37 #else 38 #define JS_INIT_MODULE js_init_module_fib 39 #endif 40 41 JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name) 42 { 43 JSModuleDef *m; 44 m = JS_NewCModule(ctx, module_name, js_fib_init); 45 if (!m) 46 return NULL; 47 JS_AddModuleExportList(ctx, m, js_fib_funcs, countof(js_fib_funcs)); 48 return m; 49 }
编译so库命令行
1 gcc fib.c -shared -DJS_SHARED_LIBRARY -o fib.so
如果没有加上 -DJS_SHARED_LIBRARY,那么在import引用so库时,会提示 ReferenceError: could not load module filename 'fib.so': js_init_module not found
js加载so库例子
1 /* example of JS module importing a C module */ 2 3 import { fib } from "./fib.so"; 4 5 console.log("Hello World"); 6 console.log("fib(10)=", fib(10));
加载so模块(Window)
除了Linux环境,有时候仿真需要在Windows上测试,因此需要能支持加载dll模块才行。 可以参考这篇文章。https://zhuanlan.zhihu.com/p/623863082
主要的修改点如下,可以打开quickjs-libc.c 这个文件,搜索 js_module_loader_so函数,就会发现 在 #if defined(_WIN32)
1 static JSModuleDef *js_module_loader_so(JSContext *ctx, 2 const char *module_name) 3 { 4 JS_ThrowReferenceError(ctx, "shared library modules are not supported yet"); 5 return NULL; 6 }
需要将上面的代码改成下面的代码,使其能加载so或者dll
1 static JSModuleDef *js_module_loader_so(JSContext *ctx, 2 const char *module_name) 3 { 4 //实现支持window dll动态库 5 JSModuleDef *m; 6 void *hd; 7 JSInitModuleFunc *init; 8 char *filename; 9 10 if (!strchr(module_name, '/')) { 11 /* must add a '/' so that the DLL is not searched in the 12 system library paths */ 13 filename = js_malloc(ctx, strlen(module_name) + 2 + 1); 14 if (!filename) 15 return NULL; 16 strcpy(filename, "./"); 17 strcpy(filename + 2, module_name); 18 } else { 19 filename = (char *)module_name; 20 } 21 22 /* C module */ 23 hd = LoadLibrary(filename); 24 if (filename != module_name) 25 js_free(ctx, filename); 26 if (!hd) { 27 JS_ThrowReferenceError(ctx, "could not load module filename '%s' as shared library", 28 module_name); 29 goto fail; 30 } 31 32 init = (JSInitModuleFunc*)GetProcAddress(hd, "js_init_module"); 33 if (!init) { 34 JS_ThrowReferenceError(ctx, "could not load module filename '%s': js_init_module not found", 35 module_name); 36 goto fail; 37 } 38 39 m = init(ctx, module_name); 40 if (!m) { 41 JS_ThrowReferenceError(ctx, "could not load module filename '%s': initialization error", 42 module_name); 43 fail: 44 if (hd) 45 FreeLibrary(hd); 46 return NULL; 47 } 48 return m; 49 }
原理也比较简单,就是Linux和Windows上的动态库操作函数不一样而已。
1 主要的区别是以下部分: 2 3 Windows使用LoadLibrary函数加载动态库 4 Windows使用FreeLibrary释放动态库 5 Windows使用GetProcAddress查找动态库提供的函数 6 而对于Linux/macos 7 8 unix-like使用dlopen函数加载动态库 9 unix-like使用dlclose释放动态库 10 unix-like使用dlsym查找动态库提供的函数。
编译成so库
1 gcc fib.c ..\libquickjs.a -shared -DJS_SHARED_LIBRARY -o fib.so
调用so库
1 D:\test\quickjs\examples>..\qjs.exe test_fib.js 2 Hello World 3 fib(10)= 55
嵌入 Embed
嵌入,表示通过在其他编程语言中,嵌入JS的能力,使其能执行JS脚本。比如,在C/C++程序中,能执行JS脚本,并能进行交互。
使用文档真不好找,先要试用默认的std、os库,总是提示不存在。
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #include "quickjs-libc.h" 5 6 int main(int argc, char *argv[]) 7 { 8 printf("HELLO\n"); 9 10 JSRuntime *rt = JS_NewRuntime(); 11 js_std_init_handlers(rt); 12 JSContext *ctx = JS_NewContext(rt); 13 /* system modules */ 14 js_init_module_std(ctx, "std"); 15 js_init_module_os(ctx, "os"); 16 const char *str = 17 "import * as std from 'std';\n" 18 "import * as os from 'os';\n" 19 "globalThis.std = std;\n" 20 "globalThis.os = os;\n" 21 "var console = {};\n" 22 "console.log = value => std.printf(value);\n"; 23 JSValue init_compile = 24 JS_Eval(ctx, str, strlen(str), "<input>", JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); 25 26 js_module_set_import_meta(ctx, init_compile, 1, 1); 27 JSValue init_run = JS_EvalFunction(ctx, init_compile); 28 29 const char *test_val = R"( 30 var console = {}; 31 console.log = value => globalThis.std.printf(value + "\n"); 32 console.log('ok') 33 for(var key in globalThis){ 34 console.log("--->" + key) 35 } 36 for(var key in std){ 37 console.log("--->" + key) 38 } 39 for(var key in os){ 40 console.log("--->" + key) 41 } 42 globalThis.std.printf('hello_world\n'); 43 globalThis.std.printf(globalThis + "\n"); 44 var a = false; 45 os.setTimeout(()=>{std.printf('AAB\n')}, 2000) 46 globalThis.std.printf(a + "\n"); 47 )"; 48 JSValue result = JS_Eval(ctx, test_val, strlen(test_val), "test", JS_EVAL_TYPE_MODULE); 49 printf("loop\n"); 50 js_std_loop(ctx); 51 52 int32_t len; 53 int to_int_32; 54 if ((to_int_32 = JS_ToInt32(ctx, &len, result)) != 0) { 55 js_std_dump_error(ctx); 56 57 } 58 59 JS_FreeContext(ctx); 60 JS_FreeRuntime(rt); 61 return 0; 62 }
编译
1 Linux: gcc test.c -o main -L . -I . -lm -lquickjs -ldl -lpthread 2 Window: gcc test.c libquickjs.a -o main.exe -I .
参考资料:
https://github.com/quickjs-zh/QuickJS/
https://github.com/saghul/txiki.js/
https://github.com/bellard/quickjs
https://bellard.org/quickjs/quickjs.html#Quick-start
https://zhuanlan.zhihu.com/p/379697068
https://zhuanlan.zhihu.com/p/382296206
https://fuchsia.googlesource.com/third_party/quickjs/+/refs/heads/main/basic_test.cc
libdl.dll 库 https://files.cnblogs.com/files/wunaozai/libdl.zip
本文地址:https://www.cnblogs.com/wunaozai/p/17850789.html
作者:无脑仔的小明 出处:http://www.cnblogs.com/wunaozai/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 如果文中有什么错误,欢迎指出。以免更多的人被误导。有需要沟通的,可以站内私信,文章留言,或者关注“无脑仔的小明”公众号私信我。一定尽力回答。 |