C++ addon
node-addon-api
https://github.com/nodejs/node-addon-api
https://www.cnblogs.com/ajanuw/p/14404060.html
hello world
#include <napi.h>
using namespace Napi;
String Method(const CallbackInfo& info) {
Env env = info.Env();
return String::New(env, "world"); // 返回数据
}
Object Init(Env env, Object exports) {
// 导出
exports.Set(String::New(env, "hello"), Function::New(env, Method));
return exports;
}
NODE_API_MODULE(hello, Init)
const addon = require("./build/Release/addon");
console.log( addon.hello() );
args
``
info.Length()
info[0].IsNumber()
double arg0 = info[0].AsNapi::Number().DoubleValue();
std::string str = info[0].ToString().Utf8Value();
// 和js一样模糊判断
Boolean b = opt.Get("b").ToBoolean();
if(b)...
env.Null();
env.Undefined();
Napi::Boolean::New(env, true);
callback
Napi::Function cb = info[0].As<Napi::Function>();
cb.Call(env.Global(), { Napi::String::New(env, "hello world") });
return function
String MyFunction(const CallbackInfo& info) {
Env env = info.Env();
return String::New(env, "hello world");
}
Function CreateFunction(const CallbackInfo& info) {
Env env = info.Env();
Function fn = Function::New(env, MyFunction, "funName");
return fn;
}
Wrap 获取传入的c++类
MyObject* obj1 = Napi::ObjectWrap<MyObject>::Unwrap( info[0].As<Napi::Object>() );
接收arraybuffer
#include <napi.h>
using namespace Napi;
Napi::Value a(const Napi::CallbackInfo& info) {
Napi::ArrayBuffer buf = info[0].As<Napi::ArrayBuffer>();
uint32_t* n = (uint32_t*)buf.Data();
printf("%d\n", *n); // 10
return info.Env().Undefined();
}
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports["a"] = Napi::Function::New(env, a);
return exports;
}
NODE_API_MODULE(hello, Init)
const array = new Uint8Array([0x0A, 0, 0, 0]);
addon.a(array.buffer)
返回array_buffer
let r = addon.test()
console.log(r); // ArrayBuffer { [Uint8Contents]: <0a 00 00 00>, byteLength: 4 }
console.log( new Uint8Array(r)[0] ); // 10
Env env = info.Env();
DWORD a = 10;
auto buf = ArrayBuffer::New(env, 4);
memcpy_s((void *)buf.Data(), 4, &a, 4);
return buf;
接收 TypedArray
let r = addon.test(new Uint8Array([0x0A, 0]));
console.log(r); // 1
Value test(const CallbackInfo &info)
{
auto env = info.Env();
auto ta = info[0].As<TypedArray>();
auto buf = ta.Get("buffer").As<ArrayBuffer>();
return Number::From(env, *(WORD*)buf.Data());
}
接收 async function
nw.test(async function () {
return 100;
});
Napi::Value test(const Napi::CallbackInfo &info)
{
Napi::Env env = info.Env();
auto promise = info[0].As<Napi::Function>().Call({}).As<Napi::Promise>();
auto thenFunc = promise.Get("then").As<Napi::Function>();
auto v = thenFunc.Call(promise, {Napi::Function::New(env, [](const Napi::CallbackInfo &info)
{ printf("%d\n", info[0].ToNumber().Int32Value()); /* 100 */ })});
return env.Undefined();
}
event
Napi::Value a(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
Napi::Function emit = info[0].As<Napi::Function>();
emit.Call({ Napi::String::New(env, "start") });
emit.Call({ Napi::String::New(env, "data"), Napi::Number::New(env, 1) });
emit.Call({ Napi::String::New(env, "end") });
emit.Call({ Napi::String::New(env, "ok") });
}
const EventEmitter = require("events").EventEmitter;
const emitter = new EventEmitter();
emitter.on("start", () => {
console.log("### START ...");
});
emitter.on("data", (evt) => {
console.log(evt);
});
emitter.on("end", () => {
console.log("### END ###");
});
addon.a(emitter.emit.bind(emitter));
保存callback函数不被垃圾回收
#include <napi.h>
class NativeAddon : public Napi::ObjectWrap<NativeAddon> {
public:
static Napi::Object Init(Napi::Env env, Napi::Object exports)
{
Napi::Function func =
DefineClass(env,
"NativeAddon",
{ InstanceMethod("tryCallByStoredReference",
&NativeAddon::TryCallByStoredReference),
InstanceMethod("tryCallByStoredFunction",
&NativeAddon::TryCallByStoredFunction) });
constructor = Napi::Persistent(func);
constructor.SuppressDestruct();
exports.Set("NativeAddon", func);
return exports;
}
NativeAddon(const Napi::CallbackInfo& info) : Napi::ObjectWrap<NativeAddon>(info) {
jsFnRef = Napi::Persistent(info[0].As<Napi::Function>()); // use Persistent
jsFn = info[1].As<Napi::Function>();
}
private:
static Napi::FunctionReference constructor;
Napi::FunctionReference jsFnRef;
Napi::Function jsFn;
void TryCallByStoredReference(const Napi::CallbackInfo& info)
{
// Napi::Env env = info.Env();
jsFnRef.Call({});
}
void TryCallByStoredFunction(const Napi::CallbackInfo& info)
{
// Napi::Env env = info.Env();
jsFn.Call({});
}
};
Napi::FunctionReference NativeAddon::constructor;
Napi::Object Init(Napi::Env env, Napi::Object exports) {
NativeAddon::Init(env, exports);
return exports;
}
NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)
const { NativeAddon } = require("./build/Release/addon");
const addon = new NativeAddon(
function () {
console.log("JSFnRef");
},
function JSFn() {
console.log("JSFn");
}
);
addon.tryCallByStoredReference(); // ok
addon.tryCallByStoredFunction(); // err
继承 EventEmitter
#include <napi.h>
class NativeEmitter : public Napi::ObjectWrap<NativeEmitter> {
public:
static Napi::Object Init(Napi::Env env, Napi::Object exports)
{
Napi::Function func =
DefineClass(env,
"NativeEmitter",
{ InstanceMethod("callAndEmit",
&NativeEmitter::CallAndEmit) });
constructor = Napi::Persistent(func);
constructor.SuppressDestruct();
exports.Set("NativeEmitter", func);
return exports;
}
NativeEmitter(const Napi::CallbackInfo& info) : Napi::ObjectWrap<NativeEmitter>(info) {}
private:
static Napi::FunctionReference constructor;
Napi::Value CallAndEmit(const Napi::CallbackInfo& info)
{
Napi::Env env = info.Env();
Napi::Function emit =
info.This().As<Napi::Object>().Get("emit").As<Napi::Function>();
emit.Call(info.This(), { Napi::String::New(env, "start") });
for (int i = 0; i < 3; i++) {
std::this_thread::sleep_for(std::chrono::seconds(1));
emit.Call(
info.This(),
{ Napi::String::New(env, "data"), Napi::String::New(env, "data ...") });
}
emit.Call(info.This(), { Napi::String::New(env, "end") });
return Napi::String::New(env, "OK");
}
};
Napi::FunctionReference NativeEmitter::constructor;
Napi::Object Init(Napi::Env env, Napi::Object exports) {
NativeEmitter::Init(env, exports);
return exports;
}
NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)
const { NativeEmitter } = require("./build/Release/addon");
const EventEmitter = require("events").EventEmitter;
const inherits = require("util").inherits;
inherits(NativeEmitter, EventEmitter);
const emitter = new NativeEmitter();
emitter.on("start", () => {
console.log("### START ...");
});
emitter.on("data", (evt) => {
console.log(evt);
});
emitter.on("end", () => {
console.log("### END ###");
});
emitter.callAndEmit();
c++ 返回 promise
Value a(const CallbackInfo& info)
{
Env env = info.Env();
auto re = Promise::Deferred::New(env);
re.Resolve(String::New(env, "str"));
return re.Promise();
}
addon.a().then((e) => {
console.log(e); // str
});
主线程同步回调
struct TsfnContext
{
TsfnContext(Napi::Env env, const Napi::CallbackInfo &info) : env_(env), cb(Persistent(info[0].As<Function>())){};
FunctionReference cb;
Napi::Env env_;
};
uintptr_t WINAPI threadEntry(void *hwnd, TsfnContext *context)
{
return context->cb.Call({Number::New(context->env_, (uintptr_t)hwnd)}).ToNumber().Int64Value();
}
Napi::Value CreateTSFN(const Napi::CallbackInfo &info)
{
Napi::Env env = info.Env();
auto testData = new TsfnContext(env, info);
BOOL r = EnumWindows((WNDENUMPROC)threadEntry, (LPARAM)testData);
return Number::New(env, r);
}
let r = addon.createTSFN((data) => {
return false;
});
对象包装
类属性和描述符
在子线程中调用jscallback
addon.test(() => {
console.log("ok"); // ok 10
});
struct TsfnContext
{
TsfnContext(const CallbackInfo &info, Napi::Env env){};
Napi::ThreadSafeFunction tsfn;
HANDLE hThread;
};
void mythread(TsfnContext*context)
{
auto callback = [](Napi::Env env, Napi::Function jsCallback, void *data) {
jsCallback.Call({Number::New(env, (int)data)});
};
napi_status status = context->tsfn.BlockingCall((void *)10, callback);
if (status != napi_ok)
Napi::Error::Fatal("ThreadEntry", "err.");
context->tsfn.Release();
}
void FinalizerCallback(Napi::Env env, void *finalizeData, TsfnContext*context)
{
CloseHandle(context->hThread);
delete context;
}
void test(const CallbackInfo &info)
{
Napi::Env env = info.Env();
auto testData = new TsfnContext(info, env);
testData->tsfn = Napi::ThreadSafeFunction::New(
env, // Environment
info[0].As<Napi::Function>(), // JS function from caller
"name", // Resource name
0, // Max queue size (0 = unlimited).
1, // Initial thread count
testData, // Context,
FinalizerCallback, // Finalizer
(void *)nullptr // Finalizer data
);
testData->hThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)mythread, testData, 0, 0);
}
获取global对象
napi_value g;
napi_get_global(env, &g);
auto global = Object(env, g);
auto str = global.Get("name").ToString().Utf8Value();
printf("str: %s\n", str.c_str()); // str: ajanuw
name = "ajanuw";
addon.test();
运行js字符串
globalThis.num = 1;
console.log(nw.test());
console.log(globalThis.num); // 2
Napi::String jsStr = Napi::String::New(env, "num++");
napi_value result;
napi_run_script(env, jsStr, &result);
return Napi::Value::From(env, result);
可以这样运行函数
globalThis.xx = () => {
return 233;
}
console.log(nw.test()); // 233
Napi::String jsStr = Napi::String::New(env, "xx()");
使用eval
globalThis.num = 1;
console.log(nw.test());
console.log(globalThis.num); // 2
auto eval = env.Global().Get("eval").As<Napi::Function>();
return eval.Call(env.Global(), {Napi::String::New(env, "num++")});