node-ffi wchar_t
障害
「のでーっふぃ」とかカワイイ...「ノードエフエフアイ」つまり、Node.jsがらみのForeign function interfaceです。C++ DLLとjavascriptをつなぐモジュールです。が、wchar_tがC++にうまく渡りません。困りました。
経緯
関連スペックは次のようになります。
- node.js@0.12.3
- node-ffi@1.3.1
- ref@1.0.2
- ref-wchar@1.0.0
- Visual Studio Community 2013
- Win7 64bit
テスト用のC++DLL関数は、次のように、引数がwchar_tです。
size_t fnsub(const wchar_t* txt);
また、typesriptからは、次のように接続、呼び出しをします。
var ref = require('ref'); var ffi = require('ffi'); var wchar_t = require('ref-wchar'); var mylib = ffi.Library('sub', { 'fnsub': ['int', [wchar_t]], }); var ret = mylib.fnsub('愛あればこそ。');
結果は、不正アクセスで異常終了です。困りました。
対策
ref-wcharをあきらめ、同一作者のテストコードwchar_t.jsを改変して使いました。
Nathan Rajlich@TooTallNate's wchar_t.js
改変したwchar_t.js
// original author: Nathan Rajlich@TooTallNate var ref = require('ref'); var ffi = require('ffi'); // added by eggtoothcroc var Iconv = require('iconv').Iconv; // vars used by the "wchar_t" type var wchar_size, getter, setter; // On Windows they're UTF-16 (2-bytes), but on Unix platform they're UTF-32 // (4-bytes). if ('win32' == process.platform) { wchar_size = 2; getter = new Iconv('UTF-16' + ref.endianness, 'UTF-8'); setter = new Iconv('UTF-8', 'UTF-16' + ref.endianness); } else { wchar_size = 4; getter = new Iconv('UTF-32' + ref.endianness, 'UTF-8'); setter = new Iconv('UTF-8', 'UTF-32' + ref.endianness); } // Create a "wchar_t *" type. We use the "CString" type as a base since it's pretty // close to what we actually want. We just have to define custom "get" and "set" // functions and then we can use this type in FFI functions. var wchar_t = Object.create(ref.types.CString); wchar_t.get = function get(buf, offset) { var _buf = buf.readPointer(offset); if (_buf.isNull()) { return null; } var stringBuf = _buf.reinterpretUntilZeros(wchar_size); return getter.convert(stringBuf).toString('utf8'); }; wchar_t.set = function set(buf, offset, val) { var _buf = val; // val is a Buffer? it better be \0 terminated... if ('string' == typeof val) { _buf = setter.convert(val + '\0'); } return buf.writePointer(_buf, offset); }; wchar_t.ffi_type = ffi.FFI_TYPES.pointer; // added by eggtoothcroc module.exports = wchar_t; // added by eggtoothcroc
テスト用javascript (app.ts)
var ref = require('ref'); var ffi = require('ffi'); var wchar_t = require('./wchar_t'); var mylib = ffi.Library('sub', { 'fnsub': ['int', [wchar_t]], }); var ret = mylib.fnsub('00愛0'); console.log(' len=', ret);
テスト用C++DLLヘッダ (sub.h)
#ifdef SUB_EXPORTS #define SUB_API __declspec(dllexport) #else #define SUB_API __declspec(dllimport) #endif extern "C"{ SUB_API size_t fnsub(const wchar_t* txt); }
テスト用C++DLLソース (sub.cpp)
#include "stdafx.h" #include <iostream> #include <iomanip> #include "sub.h" SUB_API size_t fnsub(const wchar_t* txt) { setlocale(LC_ALL, "japanese"); std::wcout << "txt=" << txt << std::endl; std::cout << std::hex << std::setfill('0') << std::setw(4) << (unsigned)txt[0] <<" " << std::hex << std::setfill('0') << std::setw(4) << (unsigned)txt[1] <<" " << std::hex << std::setfill('0') << std::setw(4) << (unsigned)txt[2] <<" " << std::hex << std::setfill('0') << std::setw(4) << (unsigned)txt[3] <<" " << std::hex << std::setfill('0') << std::setw(4) << (unsigned)txt[4] <<" " << std::endl; return wcslen(txt); }
実行結果
txt=00愛0 0030 0030 611b 0030 0000 len= 4
補足
文字コードについて
javascript内部(string) | UTF-16 |
FFI変換 | UTF-16 -> UTF-8 |
C++内部(wchar_t) | UTF-16LE |
javascript内部文字コードのendiannessがBig EndianかLittle Endianかは解りませんが、FFIを通すと、外部に対してUTF-8になるようです。で、wchar_tはUTF-16LEなので、変換(UTF-8 -> UTF-16LE)しなければならないようです。
改変部分抜粋
オリジナルのwchar_t.jsから必須部分を抜き出すと次のようなコードになります。
var ref = require('ref'); var ffi = require('ffi'); var Iconv = require('iconv').Iconv; var setter = new Iconv('UTF-8', 'UTF-16LE'); var wchar_t = { size: 8, indirection: 1, ffi_type : ffi.FFI_TYPES.pointer, get: function get(buf, offset) { return 'foo'; }, set: function set(buf, offset, val) { var _buf = val; if ('string' == typeof val) { _buf = setter.convert(val + '\0'); } return buf.writePointer(_buf, offset); } } module.exports = wchar_t;
重要なのは、ffi_typeの付加です。次のようにも書けます。
wchar_t.ffi_type =ffi.FFI_TYPES.pointer;
このコードはあくまでも見通しを良くする為のもので、getは機能しません。エンディアンネスも考慮していません。このコードも含め、今回のコード改変は、FFIの機構を理解しないままで、正当性は二の次にして、動くということを試したものです。悪しからず。