本文转载自:众成翻译
译者:乱发小生
链接:http://www.zcfy.cc/article/3360
原文:https://nodeaddons.com/type-conversions-from-javascript-to-c-in-v8/
学习怎样传递信息从JavaScript到C++是一个非常难的事情。原因在于JavaScript和C++两种语言类型之间的巨大差异。虽然C++是一门强类型语言("42"不是一个整数类型,它只是一个字符串!),JavaScript非常渴望帮我们转换这些类型。
JavaScript语言包含 String,Numbers,Booleans,null,undefined这5种原始类型。V8使用继承方式,JavaScript的类型都继承于C++的Value,和Primitive的子类。除了标准的JavaScript语言之外,V8同时支持整型(Int32
and Uint32
)。你可以在 这里查看所有的类型。
所有JavaScript值的引用都通过C++的Handle对象来保存-在大多数情况下是Local
。Handle对象把运行中的JavaScript指向V8的存储单元。你可以在我上一篇文章学习更多的存储单元的知识。
当你通过API来为这些基础单元工作时,你会注意到没有分配Local
对象很奇怪!这是有很重要的如以下三个原因:
JavaScript语句就是指向V8的存储单元的。比如
var x = 5;
,就是使X指向一个5的存储单元,重新分配x=6并没有改变这个存储单元,它只是使x指向6.如果x
和y
都赋值为10,那么他们都指向同一个存储单元。
2.函数调用值的传递,所以JavaScript 调用带有参数的C++插件,如果这个值是一个原始类型数据,它始终是一个独特的副本,改变它的值对调用的代码没有任何影响。Handles(
Local
)都引用存储单元,基于上述第一点,让handle的值改变是没有任何意义的,因为它的基本数据类型没有变。
希望这是有道理的,然后你仍有可能修改V8的变量,我们只需要重新赋值给它。
几个例子
现在,让我们看来一下Number 这种基本类型,当我们构建一个用JavaScript写的C++插件,看看它会从JavaScript接收什么。我写了一个C++PassNumber函数的例子:
void PassNumber(const FunctionCallbackInfo<Value>& args) {
Isolate * isolate = args.GetIsolate();
double value = args[0]->NumberValue();
value+= 42;
Local<Number> retval = Number::New(isolate, value);
args.GetReturnValue().Set(retval);
}
完整的插件代码在这里。
这个插件没有做对函数的参数做任何处理,甚至它存不存在都不能保证。下面是相关的mocha测试,我们可以看到V8如果处理这些数字,更重要的是,其他输入能不能转成数字在JavaScript中。
describe('pass_number()', function () {
it('return input + 42 when given a valid number', function () {
assert.equal(23+42, loose.pass_number(23));
assert.equal(0.5+42, loose.pass_number(0.5));
});
it('return input + 42 when given numeric as a string', function () {
assert.equal(23+42, loose.pass_number("23"));
assert.equal(0.5+42, loose.pass_number("0.5"));
});
it('return 42 when given null (null converts to 0)', function () {
assert.equal(42, loose.pass_number(null));
});
it('return NaN when given undefined', function () {
assert(isNaN(loose.pass_number()));
assert(isNaN(loose.pass_number(undefined)));
});
it("return NaN when given a non-number string", function () {
assert(isNaN(loose.pass_number("this is not a number")));
});
完整的类型转换清单
我创建了一个git仓库里面有类型转换清单,我认为是非常有用的。为拿到它,获取它:
`> git clone https://github.com/freezer333/nodecpp-demo.git`
建立这两个组件,进入loose
和 strict
目录,并执行在每个目录执行 node-gyp configure build
命令。首选你需要在全局安装node-gyp
。如果你完成了这两步, 来看看这里。
> cd nodecpp-demo/conversion/loose
> node-gyp configure build
...
> cd ../strict
> node-gyp configure build
The two addons (loose and strict) expose a series of functions that accept different types - Numbers, Integers, Strings, Booleans, Objects, and Arrays - and perform (somewhat silly) operations on them before returning a value. I’ve included a JavaScript test program that shows you the expected outputs of each function - but the real learning value is in the addons’ C++ code (strict/loose)
这两个组件(松散和严格的)表明一系列函数接收不同类型的参数-Number,Integers,String,Booleans,Objects和Arrays和它们的返回值。我已经包含了JavaScript的测试程序,它会显示每个函数的预期产出-但真正的学习值在插件C ++代码(严格 / 宽松
在运行测试时,你需要先安装 mocha
,进入conversions
目录(含 index.js
):
`> npm test`
在“宽松”addons组件有很宽松的类型检查-它基本上模仿纯JavaScript函数将如何工作。例如,pass_string
函数接受任何可能在JavaScript中转换为字符串值,并返回它的倒序排列:
describe('pass_string()', function () {
var str = "The truth is out there";
it('reverse a proper string', function () {
assert.equal(reverse(str), loose.pass_string(str));
});
it('reverse a numeric/boolean since numbers are turned into strings', function () {
assert.equal("24", loose.pass_string(42));
assert.equal("eurt", loose.pass_string(true));
});
it('return "llun" when given null - null is turned into "null"', function () {
assert.equal("llun", loose.pass_string(null));
});
it('return "denifednu" when given undefined', function () {
assert.equal(reverse("undefined"), loose.pass_string(undefined));
});
it('return reverse of object serialized to string', function () {
assert.equal(reverse("[object Object]"), loose.pass_string({x: 5}));
});
it('return reverse of array serialized to string', function () {
assert.equal(reverse("9,0"), loose.pass_string([9, 0]));
});
});
下面是字符串输入的宽松转换C ++代码:
void PassString(const FunctionCallbackInfo<Value>& args) {
Isolate * isolate = args.GetIsolate();
v8::String::Utf8Value s(args[0]);
std::string str(*s);
std::reverse(str.begin(), str.end());
Local<String> retval = String::NewFromUtf8(isolate, str.c_str());
args.GetReturnValue().Set(retval);
}
所述“严格”的插件执行完整的类型和错误检查,表现更像一个JavaScript C ++函数。对于所有的附加严格的方法,如果输入和预期不相符合便会返回一个undefined
。例如,pass_string函数的行为和宽松的解释完全不同:
describe('pass_string()', function () {
it('return reverse a proper string', function () {
var str = "The truth is out there";
it('reverse a proper string', function () {
assert.equal(reverse(str), strict.pass_string(str));
});
});
it('return undefined for non-strings', function () {
assert.equal(undefined, strict.pass_string(42));
assert.equal(undefined, strict.pass_string(true));
assert.equal(undefined, strict.pass_string(null));
assert.equal(undefined, strict.pass_string(undefined));
assert.equal(undefined, strict.pass_string({x: 5}));
assert.equal(undefined, strict.pass_string([9, 0]));
});
});
void PassString(const FunctionCallbackInfo<Value>& args) {
Isolate * isolate = args.GetIsolate();
if ( args.Length() < 1 ) {
return;
}
else if ( args[0]->IsNull() ) {
return;
}
else if ( args[0]->IsUndefined() ) {
return;
}
else if (!args[0]->IsString()) {
// This clause would catch IsNull and IsUndefined too...
return ;
}
v8::String::Utf8Value s(args[0]);
std::string str(*s);
std::reverse(str.begin(), str.end());
Local<String> retval = String::NewFromUtf8(isolate, str.c_str());
args.GetReturnValue().Set(retval);
}
前往前进,下载查看完整的源代码,并看看-该代码是在/conversions
目录中。你会使用整数,布尔值,对象和数组见的例子。
Looking for more info?
This post is actually a small excerpt from a book I’ve published - Node.js C++ Addons that covers this in detail. In it, you’ll also find equivalent code when using NaN. If you are interested, click here for the full contents and info on how to get your copy.
寻找更多信息?
这篇文章实际上是从一本书,我已经出版了一本小摘录- Node.js的C ++扩展中心覆盖此详细。在这里面,你还可以使用NaN当发现等效代码。如果你有兴趣,请点击这里了解如何让你的副本中的全部内容和信息。