C++ addon

RunTimeErrors發表於2024-11-06

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++")});