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

系列目录:https://www.cnblogs.com/wunaozai/p/17853962.html

posted @ 2023-11-24 16:09  无脑仔的小明  阅读(776)  评论(0编辑  收藏  举报