构建Windows桌面应用

看到标题首先想到的就是利用Visual studio创建各种各样的窗口应用,我这里讲的是利用node构建Windows窗口应用,窗口中运行Html。我这里主要讲的是nodejs与c++交互。搭建Windows窗口应用看看下面两篇文章
nodejs的桌面应用(electron)
用nodejs开发桌面应用
electron 地址

环境

node -v v10.15.3

依赖库

"dependencies": {
"bindings": "^1.5.0",
"electron": "^5.0.2",
"electron-packager": "^13.1.1",
"electron-squirrel-startup": "^1.0.0",
"nan": "^2.14.0",
"say": "^0.16.0"
},
"devDependencies": {
"electron-rebuild": "^1.8.5",
"electron-winstaller": "^3.0.4"
},

nan库 nodejs与c++
say库 语音播放
bindings 打包后的c++通过这个引入模块(var kinect = require('bindings')('kinect.node')

Hello world!

应用创建成功会有以下生命周期函数

/*窗口初始化*/
function createWindow() {
    //创建浏览器窗口
    homeWinodw = new BrowserWindow({
        width: 3300,
        height: 1200,
        frame: false,
        fullscreen:true,
        webPreferences: {
            nodeIntegration: true
        },
        backgroundColor: "#2577c2"
    })
    homeWinodw.loadFile('Home.html')
    //打开开发者工具
    //homeWinodw.webContents.openDevTools()
    //createChildWindow()
    homeWinodw.on("close", () => {
        homeWinodw = null
    })
}
/*生命周期*/
app.on("ready", createWindow)

app.on("window-all-closed", () => {
    console.log("close")
    if (process.platform !== 'darwin') {
        app.quit()
    }
})
app.on('activate', () => {
    if (homeWinodw === null) {
        createWindow();
    }
})

nodejs 与c++交互- 打包引用

node.js 查了很多资料可以说是单线程工作的,在与c++ 交互过程中 c++如果是一个循环就是阻塞nodejs 当前线程,为防止线程阻塞,node采用开进程来实现多线程工作

//主进程
var fork = require('child_process').fork;
var child = fork('./resources/app/child.js');
/*子进程监听发送事件*/
child.on('message', function(msg){
    console.log('来自子进程消息: ' + msg);
    homeWinodw.webContents.send('firstPage', msg);
});
child.on('uncaughtExpection', function(err){
    console.log(err);
});
child.send('我向子进程发送消息');
//child.js
process.on('message', function(msg){
    console.log('来自主进程: ' + msg)
}
process.send('我向主进程发送消息');

在子进程中引入模块 这里引入的手势控制脚本,代码c++编写先要打包为node插件,这里用了 node-gyp进行打包。

node-gyp安装
npm install -g node-gyp
npm install --global --production windows-build-tools
use
node-gyp configure
node-gyp build
node-gyp rebuild
node-gyp configure错误 可能是python版本错误 需要2.7 也可能是node-gyp.js路径不对 /home/xxx/.nvm/versions/node/v7.8.0/bin/node" "/home/xxx/.nvm/versions/node/v7.8.0/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js" "rebuild"需要根据提示找到node-gyp.js所在文件夹node-gyp拷贝一份到指定目录,该目录不存在需要创建。目前是通过拷贝来解决的,也是node-gyp在windows下的一个BUG,别的问题请点击这里

node-gyp build 依赖 binding.gyp 文件

{
  "targets": [
    {
      "target_name": "kinect",
      "sources": [ "kinectLib/kinect.cpp"],
      "include_dirs": [
        "<!(node -e \"require('nan')\")",
        "$(KINECTSDK20_DIR)\\inc",
        "kinectLib",
        "kinectLib\\someThing",
        "$(WindowsSDK_IncludePath)",
        "$(PATH)",
        "$(MSBuildToolsPath32)",
        "$(WindowsSDK_ExecutablePath_x86)",
       "$(SystemRoot)\SysWow64",
       "$(FxCopDir)",
       #"D:\\vs2012\\VC\\atlmfc\\include",
       "$(VSINSTALLPATH)\\VC\\atlmfc\\include"
      ],
      'library_dirs': [
        "kinectLib",
        "$(WindowsSDK_MetadataPath)",
        #"D:\\vs2012\\VC\\atlmfc\\lib",
        "$(VSINSTALLPATH)\\VC\\atlmfc\\lib"
      ],
      "conditions" : [
        ["target_arch=='ia32'", {
         "libraries": [ "-l$(KINECTSDK20_DIR)\\lib\\x86\\kinect20.lib"
         #,"-lD:\\sapi\\lib\\sapi.lib"
         ,"-l$(SPEECH11_DIR)\\lib\\sapi.lib"
         ]
        }],
        ["target_arch=='x64'", {
          "libraries": [ "-l$(KINECTSDK20_DIR)\\lib\\x64\\kinect20.lib"
          #,"-lD:\\sapi\\lib\\sapi.lib"
          ,"-l$(SPEECH11_DIR)\\lib\\sapi.lib"
          ]
        }]
      ],
      "cflags!": [ "-fno-exceptions" ],
      "cflags": [ "-std=c++11","-D_FILE_OFFSET_BITS=64"],
      "cflags_cc!": [ "-fno-exceptions" ]
    }
  ]
}

有关gyp语法参考此文章 gyp语法规则,打包文件为.cc 或.cpp 文件 要将.h 的内容拷贝至.cpp 文件

nodejs 与c++交互- 交互

交互用到的库 nan库
npm install --save nan

child.js 代码

var kinect = require('bindings')('kinect.node')
// 语音回调函数
kinect.audioReadCallback(function (school) {
    console.log(Buffer.from(school.index,'base64').toString());
})
执行代码
kinect.openAudio();
kinect.openAudioEn();

Kinect.cpp

// 出入的是function (school) 将function转化为Callback
NAN_METHOD(audioReadCallback){
    if(audioCallback)
    {
        delete audioCallback;
        audioCallback = NULL;
    }
    audioCallback = new Callback(To<v8::Function>(info[0]).ToLocalChecked());
    //设置异步回掉资源
    if (!audioAsyncResource)
    {
        audioAsyncResource = new AsyncResource("nan:audio.nancallback");
    }
}
//异步回调识别到的语音代码 传回js
void async_call_Audio(uv_async_t* req){
    AudioCallBackData * my_data = static_cast<AudioCallBackData *>(req->data);
    if (audioAsyncResource && audioCallback)
    {
        v8::Local<v8::Object> obj = Nan::New<v8::Object>();
        Nan::Set(obj,Nan::New("index").ToLocalChecked(),Nan::New(my_data->audioStr).ToLocalChecked());
        Nan::Set(obj,Nan::New("status").ToLocalChecked(),Nan::New<v8::Integer>(my_data->status));
        const unsigned argc = 1;
        Local<Value> argv[1] = {
            obj
        };
        (*audioCallback).Call(GetCurrentContext()->Global(),argc,argv,audioAsyncResource);
    }
    uv_close((uv_handle_t*)req,NULL);
    delete req;
}
// 请求data
struct AudioCallBackData
{
    uv_async_t request;
    string audioStr;
    AudioCallStatus status;
};
//c++的回调函数 函数中异步请求发送str status给js
void __stdcall onAudioCallBack(string str,AudioCallStatus status)
{
    AudioCallBackData * req_data = new AudioCallBackData;
    req_data->request.data = req_data;
    req_data->audioStr = str;
    req_data->status = status;
    uv_async_init(uv_default_loop(),&req_data->request,async_call_Audio);
    uv_async_send (&req_data->request);
}
//声明对象指针
KinectApp *app;
MKSpeech *speech;
//打开手势控制
NAN_METHOD(open) {
    if(SUCCEEDED(OleInitialize(0)))
    {
        app = new KinectApp();
        app->m_setActionCallBack(onActionCallBack);
        app->m_setErrorActionCallBack(onErrorCallBack);
        app->m_setPanActionCallBack(onPanActionCallBack);
        app->open();

        speech = new MKSpeech();
        speech->m_setErrorActionCallBack(onErrorCallBack);
        speech->m_setAudioCallBack(onAudioCallBack);
    }   
}
//关闭手势控制
NAN_METHOD(close){
    CoUninitialize();
}
//打开语音
NAN_METHOD(openAudio)
{
    speech->open(0);
}
//打开英文语音
NAN_METHOD(openAudioEn)
{
    speech->open(1);
}
//关闭语音
NAN_METHOD(closeAudio)
{
    speech->close();
}
//初始化
NAN_MODULE_INIT(Init){
    NAN_EXPORT(target, tapGestureCallBack);
    NAN_EXPORT(target, panGestureCallBack);
    NAN_EXPORT(target, audioReadCallback);
    NAN_EXPORT(target, open);
    NAN_EXPORT(target, close);
    NAN_EXPORT(target, pause);
    NAN_EXPORT(target, openAudio);
    NAN_EXPORT(target, openAudioEn);
    NAN_EXPORT(target, closeAudio);
}

NODE_MODULE(demo, Init)

关于Say库

安装
npm install say --save
使用

使用之前需要修改node_modules下的say库的文件,文件名是win32.js。修改的内容如下`

// psCommand += `$speak.Speak([Console]::In.ReadToEnd())`
//
// pipedData += text

psCommand += `$speak.Speak('${text}')`

使用的例子如下:

// automatically pick platform
const say = require('say')

// or, override the platform
const Say = require('say').Say
const say1 = new Say('win32')

// Use default system voice and speed
// 第一个参数是文字,第二个参数是语音库的名字,第三个字是语速,第四个是阅读完毕回调函数
say1.speak('你好', null, 1, function(res) {
    if (res) {
        console.log('语音出错')
    } else {
        console.log("hhh")
    }
})

注意

  经过反复尝试,在c++线程中调用回调函数 回调函数中不可以直接调用Callback函数 将值传给js 所以采用了 libuv
多线程之间传递消息 Nan库中也有Nan::AsyncWorker可调用 都是用 V8 JS引擎
来传递数据。Nan库 我也只了解了个皮毛🤦‍♀️ 就介绍到这里啦 本文没有介绍到js 传基本数据给c++,只传递了函数 如果我没有说明白 您可以查看这篇文章 C++与Nodejs的交互

如有疑问可联系249086205@qq.com 欢迎交流

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343