Node-ffi guide



Node Ffi Guide



in nodejs / elctron In、合格可能 node-ffi 、by Foreign Function Interface一般にDLLとして知られるダイナミックリンクライブラリを呼び出すと、C / C ++コードへの呼び出しが実装され、多くのノードがうまく実装できない関数を実装したり、実装された多くの関数関数を再利用したりします。

Node-ffiは、純粋なJavaScriptを使用してダイナミックライブラリをロードおよび呼び出すためのNode.jsプラグインです。 C ++コードを記述せずに、ローカルDLLライブラリへのバインディングを作成するために使用できます。また、JavaScriptとC全体で型変換を処理する役割も果たします。



Node.js Addons比較すると、この方法には次の利点があります。

1. No source code is required. 2. You don't need to recompile `node` every time. The `.node` referenced by `Node.js Addons` will have a file lock, which will cause trouble for the `electron application hot update. 3. Developers are not required to write C code, but still require developers to have a certain C knowledge. Copy code

弱点は次のとおりです。



1. Performance is compromised 2. Similar to FFI debugging in other languages, this method approximates black box calls, and the error is more difficult. Copy code

インストール

node-ffi by Bufferクラス、CコードとJSコード間のメモリ共有、型変換が渡されます refref-arrayref-struct 成し遂げる。理由node-ffi / ref Cネイティブコードが含まれているため、インストールではノードネイティブプラグインビルド環境を構成する必要があります。

// The administrator runs bash/cmd/powershell, otherwise it will prompt insufficient permissions. npm install --global --production windows-build-tools npm install -g node-gyp Copy code

必要に応じて対応するライブラリをインストールします

npm install ffi npm install ref npm install ref-array npm install ref-struct Copy code

if electron Projectの場合、プロジェクトは電子再構築プラグインをインストールできます。このプラグインは簡単にトラバースできますnode-modules必要なものすべてrebuildライブラリが再コンパイルされます。



npm install electron-rebuild Copy code

package.jsonでショートカットを構成する

package.json 'scripts': { 'rebuild': 'cd ./node_modules/.bin && electron-rebuild --force --module-dir=../../' } Copy code

後に実行 npm runrebuild 操作は完了できますelectron再コンパイル。

簡単な例

extern 'C' int __declspec(dllexport)My_Test(char *a, int b, int c) extern 'C' void __declspec(dllexport)My_Hello(char *a, int b, int c) Copy code import ffi from 'ffi' // `ffi.Library` is used to register the function, the first input parameter is the DLL path, preferably the file absolute path const dll = ffi.Library( './test.dll', { // My_Test is a function defined in the dll, the names of the two need to be consistent // [a, [b,c....]] a is the function argument type, [b,c] is the argument type of the dll function My_Test: ['int', ['string', 'int', 'int']], / / can be expressed in text type My_Hello: [ref.types.void, ['string', ref.types.int, ref.types.int]] // It is recommended to use `ref.types.xx` to indicate the type, which is convenient for type checking. The special abbreviation of `char*` will be explained below. }) / / Synchronous call const result = dll.My_Test('hello', 3, 2) //Asynchronous call dll.My_Test.async('hello', 3, 2, (err, result) => { if(err) { //todo } return result }) Copy code

可変型

C言語には4つの基本的なデータ型があります----整数浮動小数点ポインター

基礎

整数と文字タイプは両方とも符号付きと符号なしです。

の種類 最小範囲
char 0〜127
符号付き文字 -127〜127
unsigned char 0〜256

署名なしとして宣言されていない場合、デフォルトは署名されています。

ref in unsigned略してu、たとえばuchar対応するusigned char

浮動小数点型float double long double

refライブラリは、基本タイプの対応する関係をすでに準備しています。

C ++タイプ 対応するタイプを参照
ボイド ref.types.void
int8 ref.types.int8
uint8 ref.types.uint8
int16 ref.types.int16
uint16 ref.types.uint16
浮く ref.types.float
ダブル ref.types.double
ブール ref.types.bool
char ref.types.char
uchar ref.types.uchar
ショート ref.types.short
ushort ref.types.ushort
int ref.types.int
uint ref.types.uint
長いです ref.types.long
ウロン ref.types.ulong
DWORD ref.types.ulong

DWORDはwinapiタイプ、以下に詳述

より多くの拡張が行くことができます ref doc

ffi.Libraryこの場合、ref.types.xxxまたはテキスト(uint16など)で型を宣言できます。

文字タイプ

文字タイプchar構成、in GBKコード内の1つの漢字は、UTF-8では2バイトを占め、3〜4バイトを占めます。 One ref.types.charデフォルトは1バイトです。必要な文字長に基づいて、十分な長さのメモリスペースを作成します。今回は使用する必要がありますref-arrayライブラリ。

const ref = require('ref') const refArray = require('ref-array') const CharArray100 = refArray(ref.types.char, 100) // Declare char[100] type CharArray100 const bufferValue = Buffer.from('Hello World') // Hello World converts Buffer // Copy through Buffer, compare const value1 = new CharArray100() for (let i = 0, l = bufferValue.length i / / Use ref.alloc to initialize the type const strArray = [...bufferValue] / / Need to convert `Buffer` to `Array` const value2 = ref.alloc(CharArray100, strArray) Copy code

漢字を渡すときは、事前に知っておく必要がありますDLLライブラリのエンコーディング。ノードはデフォルトでUTF-8エンコーディングを使用します。 DLLがUTF-8でエンコードされていない場合は、トランスコードする必要があります。使用をお勧めします iconv-lite

npm install iconv-lite Copy code const iconv = require('iconv-lite') const cstr = iconv.encode(str, 'gbk') Copy code

注意! encode cstr for Buffer Classでトランスコードした後、ucharタイプとして直接扱うことができます。

iconv.encode(str.'gbk ')でのgbkのデフォルトの使用法はunsigned char | 0 ~ 256 Storeです。 Cコードにsigned char | -127 ~ 127が必要な場合は、バッファー内のデータのint8型変換を使用する必要があります。

const Cstring100 = refArray(ref.types.char, 100) const cString = new Cstring100() const uCstr = iconv.encode('Agricultural Pills', 'gbk') for (let i = 0 i Copy code

Cコードは文字配列ですchar[] / char *戻り値セット。通常、返されるテキストは固定長ではなく、事前に割り当てられたスペースは完全には使用されず、末尾は役に立たない値になります。 。事前に初期化された値の場合、終了は通常、0x00手動で行う必要がありますtrimEnd事前に初期化された値でない場合、終了は固定されていません。文字列配列の長さを明示的に返すCコードreturnValueLength

組み込みの速記

いくつかの速記はffiに組み込まれています

ref.types.int => 'int' ref.refType('int') => 'int*' char* => 'string' Copy code

'string'の使用のみをお勧めします。

文字列はjsの基本型と見なされますが、C言語ではオブジェクトの形式で表されるため、参照型と見なされます。など ストリング 実は char *ない char

集計タイプ

多次元配列

多次元配列として定義された基本型に遭遇した場合は、ref-arrayを使用して作成する必要があります

char cName[50][100] / / Create a cName variable storage level 50 maximum length of 100 name Copy code const ref = require('ref') const refArray = require('ref-array') const CName = refArray(refArray(ref.types.char, 100), 50) const cName = new CName() Copy code

構造

構造はCで一般的に使用されるタイプであり、使用する必要があります。ref-struct作成

typedef struct { char cTMycher[100] int iAge[50] char cName[50][100] int iNo } Class typedef struct { Class class[4] } Grade Copy code const ref = require('ref') const Struct = require('ref-struct') const refArray = require('ref-array') Const Class = Struct({ // Note that the returned `Class` is a type cTMycher: RefArray(ref.types.char, 100), iAge: RefArray(ref.types.int, 50), cName: RefArray(RefArray(ref.types.char, 100), 50) }) Const Grade = Struct({ // Note that the returned `Grade` is a type class: RefArray(Class, 4) }) Const grade3 = new Grade() // new instance Copy code

ポインタ

ポインタは、値が実際の変数のアドレス、つまりメモリ位置の直接アドレスである変数であり、JSの参照オブジェクトにいくらか似ています。

C言語で使用*ポインタを表すため

例えば int *は変数の整数へのポインタです&アドレスを示すために使用されます

int a=10, int *p / / Define a pointer to the integer type `p` p=&a // Assign the address of the variable `a` to `p`, ie `p` points to `a` Copy code

node-ffiポインタの実装の原則はref、use BufferクラスはCコードとJSコード間のメモリ共有を実装します。Buffer C言語のポインタになります。参照したらref変更されるBuffer of prototype、いくつかのメソッドを置き換えて注入することに注意してください。ドキュメントを参照してください。 参照ドキュメント

const buf = new Buffer(4) / / Initialize an untyped pointer buf.writeInt32LE(12345, 0) // Write the value 12345 console.log(buf.hexAddress()) // Get the address hexAddress buf.type = ref.types.int / / Set the C type corresponding to buf, you can achieve the mandatory type conversion of C by modifying `type` console.log(buf.deref()) // deref() gets the value 12345 const pointer = buf.ref() // Get a pointer to a pointer of type `int **` console.log(pointer.deref().deref()) // deref() gets the value 12345 twice Copy code

2つの概念を明確にするために、1つは構造型であり、もう1つはポインター型であり、コードで示されています。

// Declare an instance of a class const grade3 = new Grade() // Grade is the structure type // pointer type corresponding to the structure type const GradePointer = ref.refType(Grade) / / The type of pointer corresponding to the structure type `Grade`, that is, pointing to Grade / / Get the pointer instance pointing to grade3 const grade3Pointer = grade3.ref() // deref() gets the value corresponding to the pointer instance console.log(grade3 === grade3Pointer.deref()) // not the same object in the JS layer console.log(grade3['ref.buffer'].hexAddress() === grade3Pointer.deref()['ref.buffer'].hexAddress()) //but actually points to the same memory address, ie the referenced value is the same Copy code

渡すことができるref.alloc(Object|String type, ? value) → Buffer参照オブジェクトを直接取得する

const iAgePointer = ref.alloc(ref.types.int, 18) // Initialize a pointer to the `int` class with a value of 18 const grade3Pointer = ref.alloc(Grade) // Initialize a pointer to the `Grade` class Copy code

折り返し電話

Cのコールバック関数は、通常、入力パラメーターとして使用されます。

const ref = require('ref') const ffi = require('ffi') const testDLL = ffi.Library('./testDLL', { setCallback: ['int', [ ffi.Function(ref.types.void, // ffi.Function declaration type, can also be declared with `'pointer'` [ref.types.int, ref.types.CString])]] }) const uiInfocallback = ffi.Callback(ref.types.void, // ffi.callback returns a function instance [ref.types.int, ref.types.CString], (resultCount, resultText) => { console.log(resultCount) console.log(resultText) }, ) const result = testDLL.uiInfocallback(uiInfocallback) Copy code

注意!コールバックがsetTimeoutで呼び出された場合、GCにバグがある可能性があります。

process.on('exit', () => { /* eslint-disable-next-line */ uiInfocallback // keep reference avoid gc }) Copy code

コードインスタンス

完全なリファレンス例を挙げてください

// head File #pragma once //#include '../include/MacroDef.h' #define CertMaxNumber 10 typedef struct { int length[CertMaxNumber] char CertGroundId[CertMaxNumber][2] char CertDate[CertMaxNumber][2048] } CertGroud #define DLL_SAMPLE_API __declspec(dllexport) extern 'C'{ / / read the certificate DLL_SAMPLE_API int My_ReadCert(char *pwd, CertGroud *data,int *iCertNumber) } Copy code const CertGroud = Struct({ certLen: RefArray(ref.types.int, 10), certId: RefArray(RefArray(ref.types.char, 2), 10), certData: RefArray(RefArray(ref.types.char, 2048), 10), curCrtID: RefArray(RefArray(ref.types.char, 12), 10), }) const dll = ffi.Library(path.join(staticPath, '/key.dll'), { My_ReadCert: ['int', ['string', ref.refType(CertGroud), ref.refType(ref.types.int)]], }) async function readCert({ ukeyPassword, certNum }) { return new Promise(async (resolve) => { // ukeyPassword is a string type, c refers to char* ukeyPassword = ukeyPassword.toString() / / Based on the structure type to open a new memory space const certInfo = new CertGroud() / / Open an int 4 bytes of memory space const _certNum = ref.alloc(ref.types.int) // certInfo.ref() is passed as a pointer to certInfo dll.My_ucRMydCert.async(ukeyPassword, certInfo.ref(), _certNum, () => { // clear invalid null field let cert = bufferTrim.trimEnd(new Buffer(certInfo.certData[certNum])) cert = cert.toString('binary') resolve(cert) }) }) } Copy code

よくある間違い

  • ダイナミックリンクエラー:Win32エラー126

このエラーには3つの理由があります。

  1. 通常、着信DLLパスが間違っており、DLLファイルが見つかりません。絶対パスを使用することをお勧めします。
  2. x64上にある場合node / electron 32ビットDLLへの参照でもこのエラーが報告され、その逆も同様です。 DLLがオペレーティング環境と同じCPUアーキテクチャを必要としていることを確認してください。
  3. DLLは他のDLLファイルも参照しますが、参照されているDLLファイルが見つかりません。これは、VCに依存するライブラリ、または複数のDLL間の依存関係である可能性があります。
  • ダイナミックリンクエラー:Win32エラー127:名前に対応する関数がDLLに見つかりません。ヘッダーファイルに定義されている関数名が、DLLの呼び出し時に書き込まれた関数名と同じかどうかを確認する必要があります。

パス設定

DLLが複数あり、相互呼び出しがある場合は、表示されますDynamic Linking Error: Win32 error 126エラー3。これはデフォルトのプロセスによるものですPathバイナリファイルが配置されているディレクトリ、つまりnode.exe/electron.exe DLLが配置されているディレクトリではなく、ディレクトリによって、同じディレクトリ内の他のDLLへの参照が発生することはありません。次の方法で解決できます。

/ / Method one, call winapi SetDllDirectoryA set the directory const ffi = require('ffi') const kernel32 = ffi.Library('kernel32', { 'SetDllDirectoryA': ['bool', ['string']] }) kernel32.SetDllDirectoryA('pathToAdd') / / Method 2 (recommended), set the Path environment process.env.PATH = `${process.env.PATH}${path.delimiter}${pathToAdd}` Copy code

DLL分析ツール

DLLリンクライブラリのすべての情報とDLL依存関係のツールを表示できますが、残念ながらWIN10はサポートされていません。 WIN10ユーザーでない場合は、この1つのツールのみが必要です。次のツールは、スキップできます。

プロセスの実行時に、IO、レジストリアクセスなどのさまざまな操作を表示できます。ここで使用してリッスンしますnode / electronトラブルシューティングのためのIO操作の処理Dynamic Linking Error: Win32 errorエラー3の原因は、表示できますffi.LibaryすべてのIO要求と対応する結果を参照してください。不足しているものDLL

Dumpbin.exeは、Common Object File Format(COFF)バイナリに関する情報を表示するMicrosoftCOFFバイナリファイルコンバータです。 dumpbinを使用して、COFFオブジェクトファイル、標準COFFオブジェクトライブラリ、実行可能ファイル、およびダイナミックリンクライブラリをチェックできます。 [スタート]メニュー-> [Visual Studio 20XX]-> [VisualStudioツール]-> [VS20XXx86ネイティブコマンドプロンプト]から起動します。

Dumpbin /headers [dll path] / / Returns the DLL header information, it will be 32 bit word Machine / 64 bit word Machine Dumpbin /exports [dll path] // returns the DLL export information, the name list is the exported function name Copy code

フラッシュ崩壊の問題

実際node-ffiデバッグ時には、メモリエラーがクラッシュしやすく、ブレークポイントでもクラッシュが発生します。これは多くの場合、不正なメモリアクセスが原因であり、渡される可能性がありますWindowsログにはエラーメッセージが表示されますが、それは役に立ちません。 Cのメモリエラーは単純な問題ではありません。

付録

自動変換ツール

Tjfontaineは提供しました node-ffi-generate ヘッダーファイルに基づいて自動的に生成できますnode-ffi機能の確認、この必要性に注意してくださいLinux環境、KOAを使用してレイヤーをパックし、オンラインモードに変更するだけです。 ffi-オンライン VPSにスローして実行できます。

WINAPI

ホイール

Winapiには、多数のカスタム変数タイプ、待機中のヒーローのホイールがあります node-win32-api フルセットの完全な翻訳windef.hプロジェクトのタイプ。このプロジェクトでは、TSを使用してFFIリターンインターフェイスを指定します。これは学習する価値があります。

注意!内部のタイプは必ずしも正しいとは限りません。著者はすべての変数を完全にテストしたわけではないと思います。実際には、タイプエラーのあるピットに遭遇しました。

GetLastError

単にnode-ffi winapi経由でDLLを呼び出すと、GetLastError常に0が返されます。最も簡単な方法は独自のC++ addonこの問題を回避することです。

参考資料 Win32 APIを使用する場合、GetLastError()は常に0です。 リファレンスPR github.com/node-ffi/no…

PVOIDはnullを返します。つまり、メモリアドレスFFFFFFFFフラッシュの折りたたみ

Winapi、多くの場合、判断によって返されますpvoid成功したかどうかを判断するためのポインタが存在するかどうか、しかしnode-ffi In、right FFFFFFFF Memory address deref()プログラムがクラッシュします。ポインタのポインタタイプを使用して特別な判断を行う必要があります

HDEVNOTIFY WINAPI RegisterDeviceNotificationA( _In_ HANDLE hRecipient, _In_ LPVOID NotificationFilter, _In_ DWORD Flags) HDEVNOTIFY hDevNotify = RegisterDeviceNotificationA(hwnd, ¬ifyFilter, DEVICE_NOTIFY_WINDOW_HANDLE) if (!hDevNotify) { DWORD le = GetLastError() printf('RegisterDeviceNotificationA() failed [Error: %x] ', le) return 1 } Copy code const apiDef = SetupDiGetClassDevsW: [W.PVOID_REF, [W.PVOID, W.PCTSTR, W.HWND, W.DWORD]] / / Note that the return type `W.PVOID_REF` must be set to pointer, that is, without setting type, node-ffi will not try `deref()` const hDEVINFOPTR = this.setupapi.SetupDiGetClassDevsW(null, typeBuffer, null, setupapiConst.DIGCF_PRESENT | setupapiConst.DIGCF_ALLCLASSES ) const hDEVINFO = winapi.utils.getPtrValue(hDEVINFOPTR, W.PVOID) / / getPtrValue special judgment, if the address is full `FF` then return empty if (!hDEVINFO) { throw new ErrorWithCode(ErrorType.DEVICE_LIST_ERROR, ErrorCode.GET_HDEVINFO_FAIL) } Copy code

転載:https://juejin.im/post/5b58038d5188251b186bc902