NodejsがDllモジュールを呼び出す方法

11248 ワード

  • スグチーム
  • 著者:Tomey
  • 一、なぜnode.jsでdllを呼び出す必要があるのですか?
    同社のプロジェクトはElectronjs.org/)を用いてpcアプリケーションを開発し、下位ハードウェアデバイスとの通信に関与するが、sdkパッケージは基本的にC++ダイナミックリンクライブラリdllによって実現される.
    選択肢は2つあります.
  • シナリオ1:node-ffi
  • を使用する
  • シナリオ2:C++を使用してnode addonを記述し、LoadLibraryを介してdll
  • を呼び出す
    以上の2つの方案はすべてdll呼び出しの問題を解決することができて、方案の選択型は個人のC++に対する掌握の程度を要して、C++の開発を熟知すれば、直接方案の2を選ぶことができて最も便利です.C++を全く知らない場合は、シナリオ1しか採用できません.
    ペンの持ち主はC++がよく分からないので、最終的に最初の案を選びます.
    二、node-ffiとは何ですか.
    (www.npmjs.com/package/ffi…
    Node-ffiは、純粋なJavaScriptを使用してダイナミックライブラリをロードおよび呼び出すnode addonであり、C++コードを書かずにダイナミックリンクライブラリのAPIインタフェースを呼び出すために使用できます.
    ffiはいったい何をしたのか.本質的にはコンパイルされたNode addonであり、node_modules/ffi/build/Release/ffi_bindings.node、ffi_bindings.nodeはaddon ffiがnodejsとdllの橋渡しとして機能している.
    次に、dllを簡単にロードするdemoの例を示します.
    var ffi = require('ffi');
    var libpath = path.join(_dirname, '/test.dll');
    var testLib = ffi.Library(libpath, {
    	'start': ['bool', ['bool']]
    });
    
    testLib.start(true); // true
    

    三、node-ffiのインストール
    npm install ffi
    

    次の図に示すように、node addonをコンパイルする環境がローカルにインストールされていない場合はエラーが発生します.
    ffiを使用するにしても、node addonを直接書くにしても、node addonをコンパイルするステップが欠けていません.node addonをコンパイルするには、2つの方法があります.
    1、node-gyp(www.npmjs.com/package/nod…).
    npm install node-gyp
    

    具体的なインストールの参考:github.com/nodejs/node...
    まとめると以下の4点が必要です.
  • python 2.7-3.0バージョン間(v 2.7、v 3.x.xはサポートされていません)
  • NET Framework 4.5.1
  • Visual C++コンパイルツール(windowsではVSをインストールする必要はありません.例えばVS 2015を自分でインストールすると、コンパイルエラーerror MSB 4122:The tools version"2.0"is unrecognized.Available tools versions are"4.0".この問題は、コンパイラが装着されていない、またはコンパイラが正しく認識されていないことを示しており、node-gypのドキュメントではnpm config set msvs_version 2015を使用することを推奨しているが、このように設定しても無効な機器もあり、msvs_versionを手動で設定する必要がある.node-gyp rebuild--msvs_version=2015と書くべきである.VS 2015がインストールされているため正常にコンパイルできない場合は、インストールVS以前のリストアポイントに直接復元可能)
  • 環境変数構成.(注:pythonインストール場所は環境変数に追加する必要があります)
  • 2、electron-rebuild(www.npmjs.com/package/ele… )
    electronがアプリケーションを開発する場合、electronも同様にnodeオリジナルモジュールをサポートしますが、公式のnodeとは異なるV 8エンジンを使用しているため、オリジナルモジュールをコンパイルしたい場合は、electronのheadersの位置を手動で設定する必要があります.
    Electron-rebuildは、複数のバージョンのnodeおよびelectronに対して、プリコンパイルバイナリプロトタイプモジュールを簡単にパブリッシュする方法を提供します.electronモジュールを再構築し、現在のelectronバージョンを識別し、headersのダウンロード、プロトタイプモジュールのコンパイルなどのステップを自動的に完了します.次のelectron-rebuildをロードして再コンパイルする例:
    npm install --save-dev electron-rebuild
      
    #     "npm install" ,       
    ./node_modules/.bin/electron-rebuild
    
    #  windows            ,    :
    .
    ode_modules\.bin\electron-rebuild.cmd

    詳細はelectronjs.org/docs/tutoriを参照してください...
    ここでnodejsバージョンの問題に注意してください.nodejsプラットフォームはdllと一致しなければなりません.同じ32ビットまたは64ビットです.一致しないと、dllの呼び出しに失敗します.
    ffiモジュールのインストールに成功すると、次のffi呼び出しdllのインスタンスアプリケーションを開始できます.
    四、応用例
    開発ニーズでは,C++に基づいて作成されたTCPデータ転送サービスのSDKを呼び出す必要がある.
    まず、dllヘッダファイルインタフェース宣言のコードを見てみましょう.
    #ifndef JS_CONNECTION_SDK
    #define JS_CONNECTION_SDK
    
    
    #ifdef JS_SDK
    #define C_EXPORT __declspec(dllexport)
    #else
    #define C_EXPORT __declspec(dllimport)
    #endif
    
    
    extern "C"
    {
        typedef void(*ReceiveCallback) (int cmd, int seq, const char *data);
    
        /*        */
        C_EXPORT void _cdecl SetReceiveCallback(ReceiveCallback callback);
    
        /*
        *  option
        */
        C_EXPORT void _cdecl SetOption(
            const char* appKey, 
            const char* tk,
            int lc, 
            int rm
        );
    
        /*
        *    
        */
        C_EXPORT bool _cdecl CreateConnection();
    
        /*    */
        C_EXPORT bool _cdecl SendData(int cmd, int seq, const char *data, unsigned int len);
    
        /*    */
        C_EXPORT void _cdecl ReleaseConnection();
    }
    
    #endif
    
    

    ffiはdllモジュールパッケージを呼び出し、コードは以下の通りです.
    try {
    	const ffi = require('ffi');
    	const path = require('path');
    	const Buffer = require('buffer').Buffer;
    	const libpath = path.join(APP_PATH, '..', '..', '/testSDK.dll');
    	
    	const sdkLib = ffi.Library(libpath, {
    		'CreateConnection': ['bool', []],
    		'SendData': ['bool', ['int', 'int', 'string', 'int']],
    		'ReleaseConnection': ['void', []],
    		'SetOption': ['void', ['string', 'string', 'int', 'int']],
    		'SetReceiveCallback': ['void', ['pointer']]
    	});
    	
    	module.exports = {
    		createConnection: function(){
    			sdkLib.CreateConnection();
    		},
    		setReceiveCallback(cb) {
    			global.setReceiveCallback = ffi.Callback('void', ['int', 'int', 'string'], function(cmd, seq, data){
    				cb && cb(cmd, seq, data && JSON.parse(data));
    			});
    			sdkLib.SetReceiveCallback(global.setReceiveCallback);
    		},
    		sendData: function(cmd, seq, data){
    			data = JSON.stringify(data);
    			sdkLib.SendData(cmd, seq, data, data.replace(/[^\x00-\xff]/g, '000').length, 0);
    		},
    		releaseConnection: function(){
    			sdkLib.ReleaseConnection();
    		},
    		setOption: function (option) {
    			sdkLib.SetOption(
    				option.appKey,
    				option.tk,
    				option.lc,
    				option.rm
    			);
    		}
    	}	
    } catch (error) {
    	log.info(error);
    }
    
    

    ステップ1:ffiでdllインタフェースを登録する
    	const sdkLib = ffi.Library(libpath, {
    		'CreateConnection': ['bool', []],
    		'SendData': ['bool', ['int', 'int', 'string', 'int']],
    		'ReleaseConnection': ['void', []],
    		'SetOption': ['void', ['string', 'string', 'int', 'int']],
    		'SetReceiveCallback': ['void', ['pointer']]
    	});
    	
    

    ffi.Libraryメソッドでは、最初のパラメータがdllパスに入力され、第2のパラメータJSONオブジェクトが関連インタフェースを構成します.
    keyはdllヘッダファイルから出力されるインタフェース、例えばC_EXPORT bool_cdecl CreateConnection()に対応する.
    value array構成パラメータタイプ、array[0]登録インタフェース関数戻り値タイプ、array[1]登録インタフェース関数転送パラメータタイプ.
    1、基礎パラメータタイプbool,char,short,int,longなど.
    2、ポインタタイプ、refモジュールを導入する必要があります.以下の通りです.
    var ref = require('ref');
    var intPointer = ref.refType('char');
    var doublePointer = ref.refType('short');
    var charPointer = ref.refType('int');
    var stringPointer = ref.refType('long');
    var boolPointer = ref.refType('bool');
    

    3、コールバック関数ポインタpointer、ffi.Callbackで作成できます.以下の通りです.
    global.setReceiveCallback = ffi.Callback('void', ['int', 'int', 'string'], function(cmd, seq, data){
    		cb && cb(cmd, seq, data && JSON.parse(data));
    	});
    sdkLib.SetReceiveCallback(global.setReceiveCallback);
    

    コールバック関数パラメータタイプ構成はdllインタフェースパラメータタイプ構成と同じであり、ここでは多くは言わない.
    ここで注意したいのは、コールバック関数がJavaScriptゴミ自動回収メカニズムによって回収される可能性があるので、ここではコールバック関数をグローバルオブジェクトglobalにマウントします.
    ステップ2:インタフェース呼び出し
    ffi.Library(libpath,{...})でインタフェースを登録すると、返されたsdkLibオブジェクトを介してドッキングしたインタフェースを直接呼び出すことができます.たとえば、次のようになります.
    var bool = sdkLib.CreateConnection();
    console.log(bool); // true or false;
    
    var cmd = 0, seq = 0, data = {...};
    var dataStr = JSON.stringify(data);
    // JavaScript       C++      *3
    sdkLib.SendData(cmd, seq, data, data.replace(/[^\x00-\xff]/g, '000').length);
    
    global.setReceiveCallback = ffi.Callback('void', ['int', 'int', 'string'], function(cmd, seq, data){
    	cb(cmd, seq, data && JSON.parse(data));
    });
    sdkLib.SetReceiveCallback(global.setReceiveCallback);
    

    文章はこれで終わります.この文章を書く目標は主に自分がnodeを通じてdllを呼び出して無から有までの過程とピットの記録を記録することです.文章に間違いがあるところは、皆さんのご指摘を歓迎します.