Electron学习笔记(五) 通过Addon(n-api)实现可扩展接口

Electron学习笔记(四)

Electron使用的API接口

一方面electron给开发者提供了不少API,

另一方面, 也可以使用node.js的API.

但是, 有时候开发者还是想用自己实现的API.

下面, 我将介绍如何在Electron通过Addon添加接口.

Addon接口扩展

addon是node.js扩展api的方式, 同时electron也可以以同样的方式扩展.

addon有三种编写方式, n-api, nan, addon-api.

其中官方推荐使用n-api, 因为N-API编译的lib兼容性最好.

示例接口说明

CreateObject:

    从JS传入一个字符串, 在C++创建一个JS可访问的Object对象, 其中属性包含前面的字符串, 返回.

CreateFunction:

    从C++创建一个JS可调用的函数. 调用后, 可以获得该函数的返回值.

CreateDoer:

    创建一个对象, 该对象的todo函数返回promise. 这个函数有点绕, 为什么不直接返回promise呢? 因为官方示例直接返回的promise对象用第一次还可以, 用第二次就在C++层冲突了. 原因是内部实现用同一个对象在记录. 如果发生多次调用就会冲突, 因此这里我实现了一个对象, 每次创建一个对象来管理上面提到的对象, 从而避免多次调用引起的冲突.

RegSvcRsp, UnregSvcRsp, SendSvcReq:

   这里考虑到native接口的添加对js开发者来说有些繁琐, 因此, 这里想出了一种实现方式, 采用json命令的模式来完成调用和回调. 只要使用这一套接口, 后面addon(n-api)代码的接口层就可以不用再改动.

   方法是这样的, js app启动时, 先通过RegSvcRsp接口监听消息.  app退出时, 通过UnregSvcRsp接口注销监听.

   当需要向C++发起命令时, 通过SendSvcReq发出一个json字符串, 示例如下.

其中type: req表示当前是请求, cmd可以扩展很多命令, params是该命令的参数, 参数又是一个json, 可以任意扩展.

var reqStr = JSON.stringify({
    "type":"req",
    "cmd":"get_user_info",
    "params":{ "user_id": 100001 }
})

然后命令执行的结果可以直接通过返回值同步返回. 

也可以通过前面RegSvcRsp注册的回调异步返回, 返回的也是json, 格式上跟上面保持一致.

其中, type: rsp表示是回应, cmd跟前面的请求保持一致, params也是json. 

char* retStr = "{\"type\":\"rsp\",\"cmd\":\"get_user_info\",\"params\":{ \"user_name\": \"miller\" }}";

所以有了这一套接口, 足以应付几乎所有的情况, 除非开发者对性能要求到了极致, 不能接受json转换过程中的性能消耗.

这里也像前面的promise一样, 对官方示例做了优化, 从官方的示例如果设置一个回调, 从另一个函数去触发该回调, 会异常. 因为底层new一个对象使用napi_create_threadsafe_function, napi_release_threadsafe_function, napi_call_threadsafe_function来管理和访问这个回调. 所以这里我相应的实现了UnregSvcRsp来做回收工作, 否则无法退出和泄露资源.

环境搭建

1. 首先创建一个electron的项目.

npm install create-electron-app -g

create-electron-app my-app

2. 在my-app目录添加一个目录addon

3. 在addon添加文件binding.gyp, 内容如下, 其中的napi是我设置的导出接口对象名. 

{
  "targets": [
    {
      "target_name": "napi",
      "sources": [ "napi.cc" ]
    }
  ]
}

在addon目录添加napi.js, 用于直接在node.js调试或命令运行. 内容如下.

var napi = require('bindings')('napi');

console.log("RetStr: " + napi.RetStr());
console.log("Add: " + napi.Add(1, 2)); 
napi.RunCallback((msg) => {
    console.log("RunCallback: " + msg);
});

var obj1 = napi.CreateObject("MyObj");
console.log("CreateObject: " + obj1.msg);

var func = napi.CreateFunction();
console.log("CreateFunction: " + func());

var doer1 = napi.CreateDoer();
let promise1 = doer1.todo();
promise1.then(data => {
    console.log("Promise1.0.then data param: " + data);
});
promise1.then((data)=>{
    console.log("Promise1.then data param: " + data);
    return data + 1;
}).then((data)=>{
    console.log("Promise1.2.then data param: " + data);
    return data + 1;
});

var doer2 = napi.CreateDoer();
doer2.todo().then((data)=>{
    console.log("Promise2.then data param: " + data);
    return data + 1;
});

var doer3 = napi.CreateDoer();
doer3.todo().then((data)=>{
    console.log("Promise3.then data param: " + data);
    return data + 1;
});

function onSvcRsp(resp) {
    console.log("RegSvcRsp: resp:" + resp);
}
global.onSvcRsp = onSvcRsp;
var ret = napi.RegSvcRsp(global.onSvcRsp);

var reqStr = JSON.stringify({
    "type":"req",
    "cmd":"get_user_info",
    "params":{ "user_id": 100001 }
});
console.log("SendSvcReq: " + reqStr);
ret = napi.SendSvcReq(reqStr);
console.log("SendSvcReq ret: " + ret);

setTimeout(() => {
    napi.UnregSvcRsp();
    console.log("UnregSvcRsp");
    console.log("done");
}, 4000);

4. 在addon目录添加package.json, 也可以自己敲命令生成, 主要是安装bindings.

{
  "name": "napi",
  "version": "0.0.0",
  "description": "Node.js Addons Example #1",
  "main": "napi.js",
  "private": true,
  "dependencies": {
    "bindings": "^1.5.0"
  },
  "scripts": {
    "test": "node napi.js"
  },
  "gypfile": true
}

运行npm install, 这里会自动配置本地的node-gyp编译环境.

期间如果依赖的环境不对, 就会报错, 可以百度了解一下, 需要配哪些依赖.

5. 在addon目录添加napi.cc, 主要用于编译出lib文件.

#include <windows.h>
#include <string>
#include <assert.h>
#include <stdlib.h>
#include <node_api.h>
#include <stdio.h>
#include <ctime>

#define NAPI_DESC(name, func) \
  napi_property_descriptor{ name, 0, func, 0, 0, 0, napi_default, 0 }

#define NAPI_DESC_Data(name, func, data) \
  napi_property_descriptor{ name, 0, func, 0, 0, 0, napi_default, data }

#define CHECK(expr) \
  { \
    if ((expr == napi_ok) == 0) { \
      fprintf(stderr, "[Err] %s:%d: %s\n", __FILE__, __LINE__, #expr); \
      fflush(stderr); \
      abort(); \
    } \
}

#define Logout(str) { \
    fprintf(stderr, "%s:%d: %s \n", __FILE__, __LINE__, str); \
    fflush(stderr); \
  }
#define LogoutInt(nValue) { \
  fprintf(stderr, "%s:%d: %ld \n", __FILE__, __LINE__, nValue); \
  fflush(stderr); \
}
///
// 1. Return a string.
napi_value RetStr(napi_env env, napi_callback_info info) {
  napi_value world;
  CHECK(napi_create_string_utf8(env, "world", 5, &world));
  return world;
}

///
// 2. Return sum of two params.
napi_value Add(napi_env env, napi_callback_info info) {
  size_t argc = 2;
  napi_value args[2];
  CHECK(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));

  double value0, value1;
  CHECK(napi_get_value_double(env, args[0], &value0));
  CHECK(napi_get_value_double(env, args[1], &value1));

  napi_value sum;
  CHECK(napi_create_double(env, value0 + value1, &sum));
  return sum;
}

///
// 3. Call the callback from params, and get the result.
napi_value RunCallback(napi_env env, const napi_callback_info info) {
  size_t argc = 1;
  napi_value args[1];
  CHECK(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
  napi_value cb = args[0];

  napi_value argv[1];
  CHECK(napi_create_string_utf8(env, "test RunCallback", NAPI_AUTO_LENGTH, argv));
  napi_value global;
  CHECK(napi_get_global(env, &global));

  napi_value result;
  CHECK(napi_call_function(env, global, cb, 1, argv, &result));

  return nullptr;
}

///
// 4. Return a object, with a property from js.
napi_value CreateObject(napi_env env, const napi_callback_info info) {
  size_t argc = 1;
  napi_value args[1];
  CHECK(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));

  napi_value obj;
  CHECK(napi_create_object(env, &obj));
  CHECK(napi_set_named_property(env, obj, "msg", args[0]));
  return obj;
}

///
// 5. Return a function, for js call.
napi_value MyFunction(napi_env env, napi_callback_info info) {
  napi_value str;
  CHECK(napi_create_string_utf8(env, "do Something", NAPI_AUTO_LENGTH, &str));
  return str;
}
napi_value CreateFunction(napi_env env, napi_callback_info info) {
  napi_value fn;
  CHECK(napi_create_function(
      env, "theFunction", NAPI_AUTO_LENGTH, MyFunction, nullptr, &fn));
  return fn;
}

///
// 7. Fetcher
struct AddonData {
  AddonData(): work(nullptr), deferred(nullptr), dataValue(-1) {};
  napi_async_work work;
  napi_deferred deferred;
  int dataValue;
};

static void ExecuteWork(napi_env env, void* data) {
  static int s_work_count = 0;
  if (data != nullptr) {
    ((AddonData*)data)->dataValue = ++ s_work_count;
  }
}

static void WorkComplete(napi_env env, napi_status status, void* data) {
  if (status != napi_ok) {
    return;
  }

  if (data != nullptr) {
    AddonData* addon_data = (AddonData*)data;
    napi_value jsValue;
    CHECK(napi_create_int32(env, addon_data->dataValue, &jsValue));
    CHECK(napi_resolve_deferred(env, addon_data->deferred, jsValue));
    CHECK(napi_delete_async_work(env, addon_data->work));
    addon_data->work = NULL;
    addon_data->deferred = NULL;
    delete addon_data;
  }
}

napi_value todo(napi_env env, napi_callback_info info) {
  napi_value work_name, promise;
  AddonData* addon_data;
  CHECK(napi_get_cb_info(env, info, NULL, NULL, NULL, (void**)(&addon_data)));
  CHECK(napi_create_string_utf8(env, "", NAPI_AUTO_LENGTH, &work_name));
  CHECK(napi_create_promise(env, &(addon_data->deferred), &promise));
  CHECK(napi_create_async_work(env, NULL, work_name, ExecuteWork, WorkComplete, 
    addon_data, &(addon_data->work)));
  CHECK(napi_queue_async_work(env, addon_data->work));
  return promise;
}

napi_value CreateDoer(napi_env env, const napi_callback_info info) {
  napi_value doer;
  CHECK(napi_create_object(env, &doer));
  AddonData* addon_data = new AddonData();
  napi_property_descriptor desc = NAPI_DESC_Data("todo", todo, addon_data);
  CHECK(napi_define_properties(env, doer, 1, &desc));
  return doer;
}

///
// RegSvcRsp, CallSvc
struct CallbackData{
  CallbackData(): work(nullptr), tsfn(nullptr) {}
  napi_async_work work;
  napi_threadsafe_function tsfn;
};
CallbackData* g_cb_data;
napi_value g_js_cb;
static void PostSvcRsp(napi_env env, napi_value js_cb, void* context, void* pData) {
  Logout((char*)pData);
  napi_value argv[1];
  CHECK(napi_create_string_utf8(env, (char*)pData, NAPI_AUTO_LENGTH, argv));
  napi_value undefined;
  CHECK(napi_get_undefined(env, &undefined));
  napi_value result;
  CHECK(napi_call_function(env, undefined, js_cb, 1, argv, &result));
}

napi_value RegSvcRsp(napi_env env, const napi_callback_info info) {
  napi_value work_name;
  CallbackData* cb_data;
  size_t argc = 1;
  CHECK(napi_get_cb_info(env, info, &argc, &g_js_cb, NULL, (void**)(&cb_data)));
  CHECK(napi_create_string_utf8(env, "CallAsyncWork", NAPI_AUTO_LENGTH, &work_name));
  CHECK(napi_create_threadsafe_function(env, g_js_cb, NULL, work_name,
        0, 1, NULL, NULL, NULL, PostSvcRsp, &(cb_data->tsfn)));
  return nullptr;
}

napi_value UnregSvcRsp(napi_env env, const napi_callback_info info) {
  g_js_cb = nullptr;
  CHECK(napi_release_threadsafe_function(g_cb_data->tsfn, napi_tsfn_release));
  // 这里没启动work, 所以不需要删除work
  // CHECK(napi_delete_async_work(env, g_cb_data->work));
  g_cb_data->work = nullptr;
  g_cb_data->tsfn = nullptr;
  delete g_cb_data;
  return nullptr;
}

napi_value SendSvcReq(napi_env env, const napi_callback_info info) {
  size_t argc = 1;
  napi_value args[1];
  CHECK(napi_get_cb_info(env, info, &argc, args, nullptr, nullptr));
  size_t realSize = 0;
  CHECK(napi_get_value_string_utf8(env, args[0], nullptr, realSize, &realSize));   // get length
  char* buf = new char[realSize];
  CHECK(napi_get_value_string_utf8(env, args[0], buf, realSize, &realSize));

  Logout(buf);
  delete buf;
  // send resp with another thread.
  char* retStr = "{\"type\":\"rsp\",\"cmd\":\"get_user_info\",\"params\":{ \"user_name\": \"miller\" }}";
  CHECK(napi_call_threadsafe_function(g_cb_data->tsfn, (void*)retStr, napi_tsfn_blocking));

  // or return data directly
  napi_value retData;
  CHECK(napi_create_string_utf8(env, retStr, NAPI_AUTO_LENGTH, &retData));
  return retData;
}

///
// Init, exports functions.
napi_value Init(napi_env env, napi_value exports) {
  g_cb_data = new CallbackData();
  Logout("napi init");
  napi_property_descriptor desc;
  
  desc = NAPI_DESC("RetStr", RetStr);
  CHECK(napi_define_properties(env, exports, 1, &desc));

  desc = NAPI_DESC("Add", Add);
  CHECK(napi_define_properties(env, exports, 1, &desc));

  desc = NAPI_DESC("RunCallback", RunCallback);
  CHECK(napi_define_properties(env, exports, 1, &desc));

  desc = NAPI_DESC("CreateObject", CreateObject);
  CHECK(napi_define_properties(env, exports, 1, &desc));

  desc = NAPI_DESC("CreateFunction", CreateFunction);
  CHECK(napi_define_properties(env, exports, 1, &desc));
  
  desc = NAPI_DESC("CreateDoer", CreateDoer);
  CHECK(napi_define_properties(env, exports, 1, &desc));

  desc = NAPI_DESC_Data("RegSvcRsp", RegSvcRsp, g_cb_data);
  CHECK(napi_define_properties(env, exports, 1, &desc));
  
  desc = NAPI_DESC("UnregSvcRsp", UnregSvcRsp);
  CHECK(napi_define_properties(env, exports, 1, &desc));

  desc = NAPI_DESC("SendSvcReq", SendSvcReq);
  CHECK(napi_define_properties(env, exports, 1, &desc));
  
  return exports;
}

NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)

以上addon目录就可以单独调试和运行了. 使用node-gyp命令编译出lib.

使用node napi.js运行起来.

接下来, 将在electron工程中引用addon.

Electron中引用Addon

1. 修改index.html

  <body>
    <h1>show napi call and callback, take look at console!</h1>
    <script>
      require("./app.js");
    </script>
  </body>

演示接口为主, 因此这里的调用过程和结果返回都直接通过devtools中的console来查看. 并没有实现网页.

2. 创建app.js

var napi = require('bindings')('napi');

var app = {
    run: function() {
        console.log("RetStr: " + napi.RetStr());
        console.log("Add: " + napi.Add(1, 2)); 
        napi.RunCallback((msg) => {
            console.log("RunCallback: " + msg);
        });
        
        var obj1 = napi.CreateObject("MyObj");
        console.log("CreateObject: " + obj1.msg);
        
        var func = napi.CreateFunction();
        console.log("CreateFunction: " + func());
        
        var doer1 = napi.CreateDoer();
        let promise1 = doer1.todo();
        promise1.then(data => {
            console.log("Promise1.0.then data param: " + data);
        });
        promise1.then((data)=>{
            console.log("Promise1.then data param: " + data);
            return data + 1;
        }).then((data)=>{
            console.log("Promise1.2.then data param: " + data);
            return data + 1;
        });
        
        var doer2 = napi.CreateDoer();
        doer2.todo().then((data)=>{
            console.log("Promise2.then data param: " + data);
            return data + 1;
        });
        
        var doer3 = napi.CreateDoer();
        doer3.todo().then((data)=>{
            console.log("Promise3.then data param: " + data);
            return data + 1;
        });


        function onSvcRsp(resp) {
            console.log("RegSvcRsp: resp:" + resp);
        }
        global.onSvcRsp = onSvcRsp;
        var ret = napi.RegSvcRsp(global.onSvcRsp);
        
        var reqStr = JSON.stringify({
            "type":"req",
            "cmd":"get_user_info",
            "params":{ "user_id": 100001 }
        });
        console.log("SendSvcReq: " + reqStr);
        ret = napi.SendSvcReq(reqStr);
        console.log("SendSvcReq ret: " + ret);
        
        setTimeout(() => {
            napi.UnregSvcRsp();
            console.log("UnregSvcRsp");
            console.log("done");
        }, 4000);
    }
}

window.onload = function () {
    app.run();
}

module.exports = exports = app;

3. 这里有一步比较关键.

在electron项目中npm install bindings

然后进入node_modules修改js, 从而可以让bindings访问到addon目录.

".\node_modules\bindings\bindings.js" 

在"defaults"下面添加一行.

['module_root', 'addon', 'build', 'Release', 'bindings'],

接下来就可以运行electron了.

以上代码的开源链接

https://github.com/gzx-miller/electron-n-api

请用star来鼓励我的努力和分享, 谢谢~!

参考资料

https://nodejs.org/api/n-api.html#n_api_n_api

Electron学习笔记(六) 

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在使用`face-api.js`库时,你需要下载并使用预先训练好的模型文件,这些模型文件包含了神经网络的权重和偏差,可以用于进行人脸识别和检测等任务。 这些模型文件需要放在你的Electron应用程序中的某个目录中,以便你的应用程序可以访问它们。你可以将这些文件放在你的应用程序的`assets`目录或`resources`目录中,这些目录通常是用于存放应用程序的静态文件和资源的。 例如,你可以将训练文件放在`/assets/models`目录中,然后在你的Electron应用程序中使用以下代码来加载它们: ```javascript const { join } = require('path'); const { faceDetectionNet, faceDetectionOptions } = require('./assets/models'); const faceDetectionModelPath = join(__dirname, 'assets/models'); faceDetectionNet.loadFromDisk(faceDetectionModelPath); ``` 这里的`faceDetectionNet`和`faceDetectionOptions`是由`face-api.js`库提供的变量,它们包含了人脸检测模型的网络架构和配置选项。`join`函数用于将文件路径拼接起来,`__dirname`表示当前模块所在的目录,因此这里会拼接出`/assets/models`目录的完整路径。最后,我们使用`loadFromDisk`函数从磁盘加载模型文件。 注意,在使用`face-api.js`库时,你需要同时加载人脸检测模型和人脸识别模型,因为它们都是必需的。你可以参考`face-api.js`库的官方文档来了解如何正确地加载和使用这些模型。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值