Js Engine V8 Embedding - 回调函数

目标

上一篇写处理函数参数,漏掉了 callback 函数做为参数的情况,这里给个简单的示例。

用 C++ 写一个简单 RunFunc() v8 函数传给 js 环境,使以下代码可运行:

1
2
3
4
5
6
// app.js
function fn (msg) {
console.log(msg)
}

RunFunc(fn)

代码 & 说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// callback.cc
// callback.cc
#include <cstring>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>

#include "include/libplatform/libplatform.h"
#include "include/v8.h"

using namespace v8;
using std::cout;
using std::endl;
using std::ifstream;
using std::strcpy;
using std::string;
using std::stringstream;

// run js callback
void RunCallback(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
// 调用回调函数需要 context 对象
Local<Context> context = isolate->GetCurrentContext();
Local<Function> cb = Local<Function>::Cast(args[0]);
// 准备回调函数的参数
const unsigned argc = 1;
Local<Value> argv[argc] = {String::NewFromUtf8(isolate, "hello world")};

// 调用
cb->Call(context, Null(isolate), argc, argv).ToLocalChecked();
}

// 读取 js 代码
char* ReadSourceCodeFile(const char* path) {
ifstream f(path);
stringstream buffer;
buffer << f.rdbuf();
const string str = buffer.str();
char* cstr = new char[str.length() + 1];
strcpy(cstr, str.c_str());
return cstr;
}

void ConsoleLog(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
Local<String> str = Local<String>::Cast(args[0]);
String::Utf8Value utf8(isolate, str);
cout << *utf8 << endl;
args.GetReturnValue().SetUndefined();
}

int main(int argc, char* argv[]) {
// 初始化 V8
V8::InitializeICUDefaultLocation(argv[0]);
V8::InitializeExternalStartupData(argv[0]);
std::unique_ptr<Platform> platform = platform::NewDefaultPlatform();
V8::InitializePlatform(platform.get());
V8::Initialize();

// 创建 Isolate 实例
Isolate::CreateParams create_params;
create_params.array_buffer_allocator =
ArrayBuffer::Allocator::NewDefaultAllocator();
Isolate* isolate = Isolate::New(create_params);
{
Isolate::Scope isolate_scope(isolate);

// 创建一个 HandleScope,用于管理 Handle 的生命周期
HandleScope handle_scope(isolate);

// 创建对象模板 `console_tpl`
Local<ObjectTemplate> console_tpl = ObjectTemplate::New(isolate);
console_tpl->Set(String::NewFromUtf8(isolate, "log"),
FunctionTemplate::New(isolate, ConsoleLog));

Local<FunctionTemplate> cb_fn_tpl = FunctionTemplate::New(isolate, RunCallback);

// 初始化 `global_template`
Local<ObjectTemplate> global_template = ObjectTemplate::New(isolate);
global_template->Set(String::NewFromUtf8(isolate, "console"), console_tpl);
global_template->Set(String::NewFromUtf8(isolate, "RunFunc"), cb_fn_tpl);

// 创建 Context,将 `global_template` 做为它的 global 对象模板
Local<Context> context = Context::New(isolate, NULL, global_template);

Context::Scope context_scope(context);

{
// 通过命令行第一个参数读取 js 文件代码
const char* js_code = ReadSourceCodeFile(argv[1]);
Local<String> source = String::NewFromUtf8(isolate, js_code);

// 编译
Local<Script> script = Script::Compile(context, source).ToLocalChecked();

// v8 异常捕获
TryCatch trycatch(isolate);
MaybeLocal<Value> result = script->Run(context);
// v8 异常处理
if (trycatch.HasCaught()) {
Local<Value> exception = trycatch.Exception();
String::Utf8Value exception_str(isolate, exception);
cout << *exception_str << endl;
}

delete js_code;
}
}

isolate->Dispose();
V8::Dispose();
V8::ShutdownPlatform();
delete create_params.array_buffer_allocator;
return 0;
}

编译

v8 编译相关请查看 Javascript Engine V8 Embedding - 编译

1
g++ -I. -Iinclude calback.cc -lv8_monolith -Lout.gn/x64.release.sample/obj/ -pthread -std=c++0x -o inode

执行

1
./inode app.js   # output: `hello world`

说明

代码已经比较清晰了,跟上一篇函数传参处理的内容是一致的,还配有简单的注释说明。

下篇,我们将探索如何封装 C++ 类,然后就可以无障碍的编写 Node.js C++ Addon 或 阅读 Node.js 源码了。

相关链接