跨端方案 node-api 编译技术储备指南

617 阅读2分钟

背景

前端想要做一个桌面端,需要掌握的知识。

技术选项

PC上跨平台UI方案就两种:

  1. 基于QT开发
  2. 基于Electron开发

封装native能力给前端使用,同样有两种方案:

  1. WebAssembly
  2. Node-Api

WebAssembly方案

webassembly 官方文档

将三方库编译成 wasm 文件,再由 js 使用,开发有一定难度。

Node-API方案

C/C++ AddOn with Node-API

将三方库编译成 node 文件,js中直接 require 使用。

大型C++工程的Node-Api编译

简单的Node-Api编译实现(hello.cpp)

安装node-addon-api

传统的Node-Api工程,文件会比较少,一般使用gyp的方式编译,只需要安装node-gyp与node-addon-api即可。

npm install -S node-gyp
npm install -S node-addon-api

编写一个简单的C++文件

hello.h

// hello.h
#include <string>

std::string helloUser(std::string name);

hello.cpp

// hello.cpp

#include "hello.h"
#include <string>

std::string helloUser(std::string name)
{
    return "Hello " + name + "!";
}

hello.h 和 hello.cpp 是c++,现在要提供给js调用需要创建一个封装 hello.cpp 的文件 index.cpp

#include <napi.h>
#include <string>
#include "hello.h"

Napi::String getHello(const Napi::CallbackInfo& info) {
    Napi::Env env = info.Env();

    std::string user = helloUser("Jacob");

    return Napi::String::New(env, user);
} 

Napi::Object Init(Napi::Env env, Napi::Object exports) {
    // set a key on exports
    exports.Set(
        Napi::String::New(env, "getHello"),
        Napi::Function::New(env, getHello)
    );

    return exports;
}

NODE_API_MODULE(hello, Init)

同级目录下创建 binding.gyp 文件

{
  "targets": [
    {
      "target_name": "hello",
      "cflags!": [ "-fno-exceptions" ],
      "cflags_cc!": [ "-fno-exceptions" ],
      "sources": [
        "./src/hello.cpp",
        "./src/index.cpp"
      ],
      "include_dirs": [
        "<!@(node -p \"require('node-addon-api').include\")"
      ],
      'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ],
    }
  ]
}

编译运行

node-gyp configue
node-gyp build

编译成功后,在build目录下生成hello.node

编写测试代码,调用 hello.node

新建一个index.js文件

const helloModule = require('./build/Release/hello.node')

console.log('exports: ', helloModule)
console.log();

console.log("helloModule.helloUser: ", helloModule.getHello());
console.log();

运行 index.js

node index.js

就这样,一个简单的hello.cpp如何编译成静态库给js端使用整个流程就完成了。

复杂C++工程的Node-Api使用cmake-js编译

如果是一个复杂的工程,用cmake进行工程的管理与构建,这个要怎样与Node-Api结合起来呢? 了解下 CMake.js

安装CMake-js环境

npm install -g cmake-js

package.json修改

{
  ...
  "scripts": {
    "install": "cmake-js compile"
  }
}

编译

cmake-js compile

到了这一步,整个native的C++工程,就被编译成了一个js端可以调用的静态库了。

但仅仅编译出静态库,js端还不能调用对应的接口,需要将相应的native接口,封装成NApi的接口。

NApi接口封装

NApi的封装,前面的编写hello.cpp的时候,已经有示例了,这里讲一下注意事项。

接口的封装,主要有几个注意的地方:

1、必须有一个定义函数,定义了所有对js暴露的接口(Napi::Object Initialize(Napi::Env& env, Napi::Object& exports) )。

2、接口的参数与返回值,分别是Napi::CallbackInfo与Napi::Value类型(后面会讲到这两种固定的类型,是如何使用的)。

Native回调Js端

很复杂,参考 ThreadSafeFunction