背景
前端想要做一个桌面端,需要掌握的知识。
技术选项
PC上跨平台UI方案就两种:
- 基于QT开发
- 基于Electron开发
封装native能力给前端使用,同样有两种方案:
- WebAssembly
- Node-Api
WebAssembly方案
将三方库编译成 wasm 文件,再由 js 使用,开发有一定难度。
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