#include <node.h>
#include <string>
v8::Persistent<v8::Function> fn;
// Call this at any time, but after the capture!
void printToNode(std::string msg) {
auto isolate = fn->GetIsolate();
// This part is the one that transforms your std::string to a javascript
// string, and passes it as the first argument:
const unsigned argc = 1;
auto argv[argc] = {
v8::String::NewFromUtf8(isolate,
msg.c_str(),
v8::NewStringType::kNormal).ToLocalChecked()
};
cb->Call(context, Null(isolate), argc, argv).ToLocalChecked();
}
// This is your native function that captures the reference
void setPrintFunction(const v8::FunctionCallbackInfo<Value>& args) {
auto isolate = args.GetIsolate();
auto context = isolate->GetCurrentContext();
auto cb = v8::Local<v8::Function>::Cast(args[0]);
fn = v8::Persistent<v8::Function>::New(cb);
}
// This part exports the function
void Init(v8::Local<v8::Object> exports, v8::Local<v8::Object> module) {
NODE_SET_METHOD(module, "exports", setPrintFunction);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, Init)
然后,只是导入您的插件,并使用它像: (Call例如index.js)
const { setPrintFunction } = require('<your path to .node file>');
function printNodeMsg(msg) {
console.log('<msg>: ' + msg);
}
setPrintFunction(printNodeMsg);
JSValue commonjs_module_data_to_function(JSContext *ctx, const uint8_t *data, size_t data_length, const char *function_name)
{
JSValue result = JS_UNDEFINED;
char * module_function_name = NULL;
// Make sure you properly free all JSValues created from this procedure
if(data == NULL) {
goto done;
}
/**
* To pull the script objects, including require_thing() etc, into global scope,
* load the patched NodeJS script from the object file embedded in the binary
*/
result = JS_Eval(ctx, data, data_length, "<embed>", JS_EVAL_TYPE_GLOBAL);
if(JS_IsException(result)) {
printf("failed to parse module function '%s'\n", function_name);
goto cleanup_fail;
}
JSValue global = JS_GetGlobalObject(ctx);
/**
* Automatically create the require_thing() function name
*/
asprintf(&module_function_name, "require_%s", function_name);
JSValue module = JS_GetPropertyStr(ctx, global, module_function_name);
if(JS_IsException(module)) {
printf("failed to find %s module function\n", function_name);
goto cleanup_fail;
}
result = JS_Call(ctx, module, global, 0, NULL);
if(JS_IsException(result)) {
goto cleanup_fail;
}
/* don't lose the object we've built by passing over failure case */
goto done;
cleanup_fail:
/* nothing to do, cleanup context elsewhere */
result = JS_UNDEFINED;
done:
free(module_function_name);
return result;
}
例如,如果要获取上面提到的foo(bar, baz)函数,可以编写如下函数:
#include <stdio.h>
#include <inttypes.h>
// A simple helper for getting a JSContext
JSContext * easy_context(void)
{
JSRuntime *runtime = JS_NewRuntime();
if(runtime == NULL) {
puts("unable to create JS Runtime");
goto cleanup_content_fail;
}
JSContext *ctx = JS_NewContext(runtime);
if(ctx == NULL) {
puts("unable to create JS context");
goto cleanup_runtime_fail;
}
return ctx;
cleanup_runtime_fail:
free(runtime);
cleanup_content_fail:
return NULL;
}
int call_foo(int bar, int baz)
{
JSContext *ctx = easy_context();
JSValue global = JS_GetGlobalObject(ctx);
/**
* esbuild output was to my-foo-lib.mjs, so symbols will be named with my_foo_lib_mjs
*/
JSValue foo_fn = commonjs_module_data_to_function(
ctx
, _binary_my_foo_lib_mjs_start // gcc/Linux-specific naming
, _binary_my_foo_lib_mjs_size
, "foo"
);
/**
* To create more complex objects as arguments, use
* JS_ParseJSON(ctx, json_str, strlen(json_str), "<input>");
* You can also pass callback functions by loading them just like we loaded foo_fn
*/
JSValue args[] = {
JS_NewInt32(ctx, bar),
JS_NewInt32(ctx, baz)
};
JSValue js_result = JS_Call(ctx
, foo_fn
, global
, sizeof(args)/sizeof(*args)
, args
);
int32_t c_result = -1;
JS_ToInt32(ctx, &c_result, js_result);
return c_result;
}
3条答案
按热度按时间mum43rcc1#
一种方法是使用提供的函数作为回调函数,例如,假设你在原生环境中声明了一个名为
setPrintFunction()
的函数(A native addon):(Call例如
main.cc
)然后,只是导入您的插件,并使用它像:
(Call例如
index.js
)因此,您所做的基本工作是捕获对
v8::Function
的引用(这是javascript函数,但在本机环境中),然后调用它并将"Hello World!"
作为第一个(唯一的)参数传递。关于这个主题的更多信息:https://nodejs.org/api/addons.html
bvuwiixz2#
我最近一直在解决这个问题,并且用QuickJS和esbuild找到了一个易于处理的解决方案。它不是最漂亮的,但是它工作得相当好!我的解决方案在C中工作,你应该可以毫不费力地把它适应到C++中。
要从C中调用JS,一般的过程是:
1.获取QuickJS和esbuild
1.使用CommonJS将你想要的库/脚本构建成ESM格式。这将输出一个包含所有需要的依赖项的大脚本。
1.修补esbuild的输出,使其与QuickJS兼容:
1.使用链接器将脚本文本加载到目标文件中:
根据您的编译器,这将在目标文件中自动创建3个符号:
在此上下文中,
name
是从作为ld的最后一个参数提供的文件名自动生成的。它将所有非字母数字字符替换为下划线,因此my-cool-lib.mjs
给出my_cool_lib_mjs
的name
。您可以使用
ld_magic.h
作为跨平台的方法,从C代码访问这些数据。生成目标文件后,如果运行
strings
:1.将目标文件链接到二进制文件中:
您也可以将目标文件链接到共享库中,以便在其他应用程序中使用:
这个repo的源代码展示了如何在CMake项目中实现这一点。
如何实际调用JS函数
假设您有一个NodeJS库,其中包含要从C:
esbuild
创建了一系列require_thing()
函数,这些函数可用于获取底层的thing(param1, param2...)
函数对象,您可以使用这些对象进行调用。QuickJS中的一个简单加载器如下所示:
例如,如果要获取上面提到的
foo(bar, baz)
函数,可以编写如下函数:请在此处查看使用CMake的最小示例项目:https://github.com/ijustlovemath/jescx/blob/master/README.md
vmdwslir3#
当然可以。例如,如果你想用
C++
编写一个简单的阶乘函数,你可以这样做:在您的
JavaScript
文件中,将其命名为