Node.jsのネイティブなアドオンを書く

はじめに

こんにちは。イメージ・マジックの矢野です。入社からもうすぐ1か月です。

Node.jsのアドオンを、何らかの理由でネイティブのコードで書かなければならないことがあります。Node.jsのエンジンであるV8のお作法に則って書くことになりますが、V8がバージョンアップして仕様が変われば、そのお作法も変わります。お作法が変わればアドオンの書き方も変えなければならず、毎度毎度書き直すのは大変。そこで、Node.jsのネイティブなアドオンの実装をイイ感じにラップして抽象化してくれる、NAN(Native Abstractions for Node.js)というものがつくられたとのこと(READMEの冒頭を読むと、クスリとさせられます)。もちろんNANを使わなくてむ作れるわけですが、せっかく便利なものがあるのですから、使わない手はありません。

準備

Node.jsのアドオンをつくるのですから、当然Node.jsはインストールされていないといけません。

NANはnode-gypでビルドするため、node-gypもインストールしておかなければいけません。さらにgypはPythonスクリプトなので、Pythonが入っていなければインストールしておかなければなりません。

ネイティブコードをビルドするために、コンパイラなども必要です。今回はWindowsを使って開発しているので、Visual Studio 2017をインストールしておきます。

作成

準備ができたところで、作業用のディレクトリを作成します。

mkdir hoge
cd hoge

NANをインストールします。

npm install --save nan

binding.gypを作成します。このテキストファイルには、アドオンの構成を記述します。ネイティブならではのアドオンをばっちり作り込むのはそれなりに大変なので、今回はOSのバージョンを取得するだけの簡単なものにしようと思います。

{
  "targets": [
    {
      "target_name": "getosversion",
      "sources": [
        "src/addon.cpp",
      ],
      "include_dirs": [
        "<!(node -e \"require('nan')\")",
      ],
    }
  ]
}

ソースファイルを格納するディレクトリ(src)をつくり、ソースファイルを形だけでも(空ファイルでOK)つくっておきます(src/addon.cpp)。

node-gypconfigureすると、上記の構成に従ってVisual StudioのC++プロジェクトやソリューションを生成してくれます。

node-gyp configure

buildディレクトリの下にいろいろファイルができています。

│  binding.gyp
│
├─build
│      binding.sln
│      config.gypi
│      getosversion.vcxproj
│      getosversion.vcxproj.filters
│
└─src
        addon.cpp

binding.slnを開くと、Visual Studioが起動しますので、addon.cppの中身を書いていきます。NANの恩恵を受けるには、ヘッダファイルnan.hをincludeしておく必要があります。
Win32 APIのGetVersion()を呼び出して、OSのバージョン情報を取得してみます。ちなみにこの関数は現在「非推奨」になっていますが、Win32がメインの記事ではないので、そのまま使ってしまいます(業務ではこんなことしませんよ)。

#include <sstream>
#include <nan.h>

NAN_METHOD(GetOsVersion)
{
	DWORD dwVersion = GetVersion();

	DWORD dwMajorVersion = (DWORD)(LOBYTE(LOWORD(dwVersion)));
	DWORD dwMinorVersion = (DWORD)(HIBYTE(LOWORD(dwVersion)));

	DWORD dwBuild = 0;
	if (dwVersion < 0x80000000)
	{
		dwBuild = (DWORD)(HIWORD(dwVersion));
	}

	std::stringstream ss;
	ss << dwMajorVersion << "." << dwMinorVersion << "." << dwBuild;

	info.GetReturnValue().Set(Nan::New<v8::String>(ss.str()).ToLocalChecked());
}

NAN_MODULE_INIT(init)
{
	Nan::SetMethod(target, "GetOsVersion", GetOsVersion);
}

NODE_MODULE(getosversion, init)

ビルドします。

node-gyp build

もろもろビルドされます。肝心のアドオンは、build/Release/getosversion.nodeとして生成されます。

│  binding.gyp
│
├─build
│  │  binding.sln
│  │  config.gypi
│  │  getosversion.vcxproj
│  │  getosversion.vcxproj.filters
│  │
│  └─Release
│      │  getosversion.exp
│      │  getosversion.iobj
│      │  getosversion.ipdb
│      │  getosversion.lib
│      │  getosversion.map
│      │  getosversion.node
│      │  getosversion.pdb
│      │
│      └─obj
│          └─getosversion
│              │  addon.obj
│              │  vc141.pdb
│              │  win_delay_load_hook.obj
│              │
│              └─getosversion.tlog
│                      ... (省略) ...
│
└─src
        addon.cpp

これを実際に動かしてみましょう。テスト用コードをこんな感じで書いてみます(test.js)。

var addon = require('./build/Release/getosversion');

var version = addon.GetOsVersion();

console.log(result);

nodeコマンドから実行してみると、それらしい値が取れました。

node test.js
10.0.16299