Node和Electron環境下集成第三方原生SDK

Node Addons是爲了能讓nodejs調用原生模塊而設計的機制,以前對一些常見原生模塊的重編譯作過梳理。但那都是Git上已經封裝好的Addons庫, 但若項目須要集成第三方提供的原生SDK,如何使用Nodejs去調用,以及如何打包到Electron項目中?html

背景

項目中須要集成第三方投屏功能,合做廠商扔過來Mac和Windows SDK。文檔看了後,接口確實設計的比較簡單易用,但SDK是爲原生框架設計的,沒有現成的Node集成方案。node

Mac SDK是Framework形式,Mac開發基於OC,因此咱們選用 NodObjC 嘗試調用SDK。NodObjC能讓Node直接調用Framework暴露的接口。其自己也基於ffiref這兩個庫。python

Windows SDK比較複雜,SDK申明的接口全放置在一個頭文件中,在常規VC項目裏,咱們只需引入頭文件和核心.lib文件,便可實現調用。在看過接口封裝形式後,感受不適合使用ffi這類JS庫,決定本身寫一個Addons來實現調用。linux

Mac

環境c++

下載NodObjcnpm install nodobjcgit

NodObjc依賴的ffiref兩個庫屬於Addons,須要根據運行環境去重編譯。先全局安裝好node-gyp,而後根據當前是在node環境仍是Electron環境調試去重編譯。github

這裏遇到一個Node版本致使的問題,nodobjc在依賴庫的版本號上彷佛有些問題,致使依賴ref沒法在node 10下編譯成功。經過n安裝node 8,從新下載和編譯。objective-c

APIshell

  • 引用npm

    const $ = require('nodobjc');
    
    $.import(frameworkPath);
    $.framework('Foundation');
    複製代碼
  • JS與OC的類型轉換

    字符串: String -> NSString,var str = $('abc');

    Number類型: Number -> NSNumber, var num = 123;

    Boolean: bool -> Bool,var isTrue = $.Yes;

    回調函數: callback -> Block:

    OC接口:

    [[Test commonTest] startTest:@"123456" completeBlock:^(BOOL succeed, NSError *error) {
    
    }];
    複製代碼

    經過NodObjc調用該OC接口:

    const startTest = (data) => {
      const param = $(data);
      const completeBlock = $(function(self, success, err) {
        console.log('test result', success, err);
      }, ['v', ['?', 'B', '@']]);
      commonTest('startTest', param, 'completeBlock', completeBlock);
    }
    複製代碼

    NodObjc的文檔並非很詳細,須要必定的OC語法基礎。固然,花點時間看下NodObjc的源碼也能知道具體調用方式。

  • 編譯與打包

    打包有兩個注意點,一是須要經過node-gyp將ffi和ref庫重編譯生成Electron環境下可用的.node。二是適應electron-builder的打包規則。

    有問題的打包方案:

    1. 直接打包到app.asar,打完包會發現js沒法引用Framework

    2. 經過配置electron-builder打包規則,將Framework全部文件打包到app.asar.unpack目錄。講道理,按以前的經驗,這樣應該就能夠了。結果直接在打包的簽名這步GG了。看上去electron-builder沒法對二進制文件進去簽名:

      ***.framework, bundle format unrecognized, invalid, or unsuitable
      複製代碼

    解決方案

    最後,選擇用extraResources字段,將Framework從打包文件中抽出來,直接複製到Mac應用的Resources目錄下,而後在調用的js文件中,根據運行環境動態選擇調用路徑:

    const path = require('path');
    
    const isDev = process.env.NODE_ENV == 'dev';
    
    const devPath = path.resolve(__dirname, 'Test.framework');
    const prodPath = path.resolve(__dirname, 'Test.framework').replace('app.asar/src', 'src');
    // const frameworkPath = path.resolve(__dirname, 'HPOfficeCastWork.framework');
    const path = isDev ? devPath : prodPath;
    const frameworkPath = require(path);
    複製代碼

Windows

windows方面,咱們先本身按照Node Addons的開發規則來實現一個.node文件,後面直接調用.node來實現功能。

環境

首先配好windows的node-gyp編譯環境。先下載安裝python2.7和windows-build-tools。將python路徑配置到系統環境。

由於要編譯C++程序,須要保證系統已安裝好C++相關的組件。C++組件缺失會在Addons模塊編譯的時候報錯,根據具體錯誤內容,下載對應缺失組件便可。

在node環境下寫模塊demo的時候,編譯報錯:沒法解析外部符號。這個錯一開始覺得是某一塊的語法有問題,實際上是由於SDK提供的lib是32位,而咱們編譯的node環境是64位。從新安裝和配置32位的Node環境即能解決這個問題。

binding.gyp

node-gyp經過binding.gyp文件配置模塊的編譯,所以,先了解好.gyp的屬性頗有必要:gyp3.org

{
    "targets": [
        "target_name": "test",
        "sources": ["test.cc"],
  	    "include_dirs": [
                "inc",
                "<!(node -e \"require('nan')\")",
        ],
  	"libraries": [
            "../lib/sdk.lib"
        ],
  	"conditions": [
     	[
            "OS='win'", {
                "copies": [
                {
                    "destination": "<(PRODUCT_DIR)",
                    "files": [
                        "<(DLL_ROOT)/dnssd.dll",
                        "<(DLL_ROOT)/avutil.dll",
                        ...
              ]
            }
          ]
        }
      ]
    ]
  ]
}
複製代碼

結合實際開發,介紹幾個經常使用字段:

include_dirs: 要用到的頭文件所在目錄,<!(node -e \"require('nan')\")用於引入nan的頭文件,<!是命令行擴展,gyp會將後面的字符經過shell執行。

conditions,天然是判斷條件,一般咱們經過OS字段來判斷當前的操做系統環境,對應的值是winmaclinux

copies是爲了執行文件的拷貝。起初,沒有加這段配置,編譯成功後,咱們引用生成的.node會報the specified module could not be found錯誤。這裏能夠經過工具dependency walker分析.node文件,查看缺失的依賴。通常這種狀況,將相關dll文件放到.node同級目錄便可。 因而,經過copies能夠在編譯後將指定文件複製到目標目錄。<(PRODUCT_DIR)即表示.node生成後的目錄。

Addons開發

Addons是Node提供的動態連接共享對象,具備C/C++類庫的調用能力。bingding.gyp中,咱們配置了sources字段的值test.cc。在test.cc中,咱們經過引入v8.h, node.h , SDK提供的頭文件等來實現對C++接口的調用。

#include <node.h>
#include <nan.h>
#include "inc/test.h"

namespace test
{

	using namespace v8;
	using namespace test; 
  
    static IMirror *pobMirror = 0;
  
    void initSdk(const FunctionCallbackInfo<Value>& args) {
        Isolate* isolate = args.GetIsolate();

  	std::string appKey = *Nan::Utf8String(args[0]);
  	std::string pinCode = *Nan::Utf8String(args[1]);
  	std::string userId = *Nan::Utf8String(args[2]);
  	std::string serverAddr = *Nan::Utf8String(args[3]);
  	unsigned int serverPort = args[4]->Uint32Value();
  	bool isEnterprise = args[5]->BooleanValue();
  	Local<Function> cb = Local<Function>::Cast(args[6]);
    
        emRtn = pobMirror->Start(appKey, pinCode, userId, serverAddr, 
            serverPort, isEnterprise);
    
        const unsigned argc = 1;
        Local<Value> argv[argc] = {String::NewFromUtf8(isolate, ToString(emRtn))};
        cb->Call(isolate->GetCurrentContext()->Global(), argc, argv);
    }

  
    void Initialize(Local<Object> exports) {
        NODE_SET_METHOD(exports, "initSdk", InitSdk);
    }

    NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)
} // namespace test
複製代碼

js調用Addons的接口,傳遞進來的是v8數據類型,而咱們調用SDK接口,須要將其轉爲C++數據類型。這裏引用了Nan的類型轉換方法:

std::string serverAddr = *Nan::Utf8String(args[3]);
unsigned int serverPort = args[4]->Uint32Value();
bool isEnterprise = args[5]->BooleanValue();
複製代碼

對於回調函數,在轉爲Local<Function>類型後,經過Call方法觸發回調:

Local<Function> cb = Local<Function>::Cast(args[6]);

const unsigned argc = 1;
Local<Value> argv[argc] = {String::NewFromUtf8(isolate, ToString(emRtn))};
cb->Call(isolate->GetCurrentContext()->Global(), argc, argv);
複製代碼

打包

和mac相似,若是直接將SDK和源碼一塊兒打包,electron是沒法引入SDK的。因windows暫時沒有簽名,咱們直接經過asarUnpack將SDK文件打包到app.asar.unpack目錄。

相關文章
相關標籤/搜索