Javascript里有个C:Part 3 - 深入对象
发布于 8 年前 作者 fool 8165 次浏览 最后一次编辑是 3 年前

<em>Javascript里有个C </em>系列文章: <br/><ol> <br/> <li><a title=“编辑“Javascript里有个C:Part 1 – 基础”” href=“http://cnodejs.org/blog/?p=1621”>Javascript里有个C:Part 1 – 基础</a></li> <br/> <li><a href=“http://cnodejs.org/blog/?p=1804”>Javascript里有个C:Part 2 – 对象</a></li> <br/> <li><a href=“http://cnodejs.org/blog/?p=1833”>Javascript里有个C:Part 3 - 深入对象</a></li> <br/> <li>Javascript里有个C:Part 4 - 异步</li> <br/> <li>Javascript里有个C:Part 5 - node.js</li> <br/> <li>Javascript里有个C:Part 6 - 实战node.js Module</li> <br/></ol> <br/>书至第三回。前两回当啷结束,C++里怎么摆弄Javascript,相信大家已略有所知。在本文,我们将更进一步。怎么从上帝视角玩弄Javascript?怎么把C++强力插入Javascript?这里都将一一解惑。 <br/><h4>External</h4> <br/>首先我们需要了解一种特殊的Value,v8::External,它的作用是把C++的对象包装成Javascript中的变量。External::New接受一个C++对象的指针作为初始化参数,然后返回一个包含这个指针的Handle<External>对象供v8引擎使用。在使用这个Handle<External>对象时可以通过External::Value函数来得到C++对象的指针,接口定义如下: <br/><pre escaped=“true” lang=“c” class=“brush: c;”>/** <br/> * A JavaScript value that wraps a C++ void*. This type of value is <br/> * mainly used to associate C++ data structures with JavaScript <br/> * objects. <br/> * <br/> * The Wrap function V8 will return the most optimal Value object wrapping the <br/> * C++ void*. The type of the value is not guaranteed to be an External object <br/> * and no assumptions about its type should be made. To access the wrapped <br/> * value Unwrap should be used, all other operations on that object will lead <br/> * to unpredictable results. <br/> / <br/>class External : public Value { <br/>public: <br/> V8EXPORT static Local<Value> Wrap(void data); <br/> static inline void* Unwrap(Handle<Value> obj); <br/> <br/> V8EXPORT static Local<External> New(void* value); <br/> static inline External* Cast(Value* obj); <br/> V8EXPORT void* Value() const; <br/> private: <br/> V8EXPORT External(); <br/> V8EXPORT static void CheckCast(v8::Value* obj); <br/> static inline void* QuickUnwrap(Handlev8::Value obj); <br/> V8EXPORT static void* FullUnwrap(Handlev8::Value obj); <br/>};</pre> <br/>需要注意的是,External::Wrap会尝试把指针作为数值进行处理,若不支持则等同于New。 <br/> <br/>和Handle不同,External并不会托管C++对象的生命期,所以你必须手动构造、释放用来包装的C++对象。比较常用的手段是,在C++的栈上建立对象,然后再用External进行托管。下面是一个例子[<a href=“https://github.com/zcbenz/there-is-a-c-in-javascript/blob/master/external.cc” target="_blank">完整代码</a>]: <br/><pre escaped=“true” lang=“c” class=“brush: c;”> std::string str (“Hello World!”); <br/> Local<External> external = External::New (&str); <br/> cout << static_caststd::string* (external->Value());</pre> <br/><h4>Template</h4> <br/>怎么把Javascript的对象和C++的对象联系起来?v8::Object并未提供从C++对象构造的方法,而Javascript的函数v8::Function甚至没有提供初始化::New函数!这就需要一种特殊的类型,v8::Template。 <br/> <br/>Template是介于Javascript和C++变量间的中间层,你首先由C++对象生成一个Template,然后再由Template生成Javascript函数的对象。你可以事先在Template中准备好一些预备属性,然后生成的Javascript对象都将具备这些属性。 <br/> <br/>一个例子就是Google Chrome的DOM,DOM就是先用ObjectTemplate预先封装好对应的C++节点,最后再为每一个标签生成DOM对象。 <br/><h4>FunctionTemplate</h4> <br/>首先介绍FunctionTemplate,顾名思义,就是用来生成函数的Template。之前函数一直在文章里缺席,原因之一就是Javascript函数和C++函数的绑定必须仰赖Template。 <br/> <br/>FunctionTemplate的接口如下: <br/><pre escaped=“true” lang=“c” class=“brush: c;”>class V8EXPORT FunctionTemplate : public Template { <br/>public: <br/> /* Creates a function template./ <br/> static Local<FunctionTemplate> New( <br/> InvocationCallback callback = 0, <br/> Handle<Value> data = Handle<Value>(), <br/> Handle<Signature> signature = Handle<Signature>()); <br/> /* Returns the unique function instance in the current execution context./ <br/> Local<Function> GetFunction(); <br/> <br/> /* <br/> * Set the call-handler callback for a FunctionTemplate. This <br/> * callback is called whenever the function created from this <br/> * FunctionTemplate is called. <br/> / <br/> void SetCallHandler(InvocationCallback callback, <br/> Handle<Value> data = Handle<Value>()); <br/>/* Causes the function template to inherit from a parent function template./ <br/> void Inherit(Handle<FunctionTemplate> parent); <br/> <br/> /* <br/> * A PrototypeTemplate is the template used to create the prototype object <br/> * of the function created by this template. <br/> / <br/> Local<ObjectTemplate> PrototypeTemplate(); <br/> <br/> /* <br/> * Set the class name of the FunctionTemplate. This is used for <br/> * printing objects created with the function created from the <br/> * FunctionTemplate as its constructor. <br/> / <br/> void SetClassName(Handle<String> name); <br/>};</pre> <br/>你可以使用FunctionTemplate::New ()生成一个空函数,然后用FunctionTemplate::SetCallHandler ()将其和C++函数绑定,或者直接靠FunctionTemplate::New (InvocationCallback callback)来用C++函数初始化一个FunctionTemplate。 <br/> <br/>用来生成FunctionTemplate的C++函数必须满足InvocationCallback的条件,即函数签名必须如下: <br/><pre escaped=“true” lang=“c” class=“brush: c;”>typedef Handle<Value> (InvocationCallback)(const Arguments& args);</pre> <br/>此后,你可以使用FunctionTemplate::GetFunction()来获取对应的v8::Function。但是一个FunctionTemplate只能生成一个Function,FunctionTemplate::GetFunction()返回的都是同一个实体。这是因为Javascript里显式声明的全局函数只有一个实例。 <br/> <br/>不过,得到生成的函数后,你可以使用Function::NewInstance返回一个函数对象,等同于Javascript中的var tmp = new func;。 <br/> <br/>下面是一个使用FunctionTemplate的简单例子[<a href=“https://github.com/zcbenz/there-is-a-c-in-javascript/blob/master/function1.cc”>完整代码</a>]: <br/><pre escaped=“true” lang=“c” class=“brush: c;”>Handle<Value> print (const Arguments& args) { <br/> HandleScope scope; <br/> <br/> for (int i = 0; i< args.Length(); ++i) { <br/> cout << String::Utf8Value (args[i]) << " "; <br/> }
<br/> <br/> cout << endl; <br/> <br/> return Undefined (); <br/>} <br/> <br/>// main函数中 <br/> <br/> // Generate Function <br/> Local<FunctionTemplate> func_tpl = FunctionTemplate::New (print); <br/> func_tpl->SetClassName (String::NewSymbol (“print”)); <br/> Local<Function> func = func_tpl->GetFunction (); <br/> cout << String::Utf8Value (func) << endl; <br/> <br/> // Generate parameters <br/> Local<Value> args[4] = { <br/> String::New (“I”), <br/> String::New (“Love”), <br/> String::New (“C++”), <br/> String::New ("!") <br/> }; <br/> <br/> // Call Function <br/> func->Call (Object::New (), 4, args);</pre> <br/>首先,我们定义了一个print函数,这个函数接受Arguments作为参数,Arguments实际上是一个包含了传进进来的参数的数组。接着我们建立了FunctionTemplate func_tpl,然后通过func_tpl->GetFunction得到函数实体,最后对这个函数实体进行调用。 <br/> <br/>那么,如果我们想要在Javascript代码中调用这个函数,应该怎么做呢,下面是一个演示[<a href=“https://github.com/zcbenz/there-is-a-c-in-javascript/blob/master/function2.cc”>完整代码</a>]: <br/><pre escaped=“true” lang=“c” class=“brush: c;”> // Generate Function <br/> Local<FunctionTemplate> func_tpl = FunctionTemplate::New (print); <br/> context->Global()->Set (String::NewSymbol (“print”), <br/> func_tpl->GetFunction ()); <br/> <br/> // Call in javascript <br/> Local<String> source = String::New (“print (‘I’, ‘Love’, ‘C++’, ‘!’);”); <br/> Script::Compile(source)->Run ();</pre> <br/>修改之处主要是在全局作用域中加入我们的函数对象。 <br/><h4>Function Instance</h4> <br/>Javascript常用new Function ();的形式来创建对象,而C++中,Function::NewInstance可以返回一个函数的Instance。 <br/> <br/>比如下面的代码: <br/><pre escaped=“true” lang=“javascript” class=“brush: js;”>function girl (name) { <br/> this.name = name; <br/>} <br/> <br/>var alice = new girl (“Alice”); <br/>alice.age = 21;</pre> <br/>对应到C++中则是[<a href=“https://github.com/zcbenz/there-is-a-c-in-javascript/blob/master/function4.cc” target="_blank">完整代码</a>]: <br/><pre escaped=“true” lang=“c” class=“brush: c;”>Handle<Value> girl (const Arguments& args) { <br/> HandleScope scope; <br/> <br/> if (args.Length() != 1 && !args[0]->IsString ()) <br/> return ThrowException(v8::String::New(“Unexpected arguments”)); <br/> <br/> args.This()->Set (String::New (“name”), args[0]); <br/> <br/> return Undefined (); <br/>} <br/> <br/>// 在main函数中 <br/> Local<FunctionTemplate> func_tpl = FunctionTemplate::New (girl); <br/> <br/> Handle<Value> args[1] = { String::New (“Alice”) }; <br/> Handle<Object> alice = func_tpl->GetFunction()->NewInstance (1, args); <br/> alice->Set (String::New (“age”), Integer::New (21));</pre> <br/>其中有两点需要新的注意,首先Javascript中的this指针可以通过Arguments::This ()得到,其次C++中抛出Javascript的异常时,需要返回一个ThrowException对象。 <br/><h4>ObjectTemplate</h4> <br/>既然有生成函数的Template,自然也可以想到会有生成对象的Template,ObjectTemplate的目的就是根据包装起来的C++对象生成v8::Object。接口与Template也大致相当,通过ObjectTemplate::New返回新的ObjectTemplate,通过ObjectTemplate::NewInstance。 <br/> <br/>但是,C++对象应该存放在哪里呢?ObjectTemplate提供了一种Internal Field,也就是内部储存空间,我们可以通过External类型把C++对象储存在ObjectTemplate中相关接口如下: <br/><pre escaped=“true” lang=“c” class=“brush: c;”> /
<br/> * Gets the number of internal fields for objects generated from <br/> * this template. <br/> / <br/> int InternalFieldCount(); <br/> <br/> /
<br/> * Sets the number of internal fields for objects generated from <br/> * this template. <br/> / <br/> void SetInternalFieldCount(int value);</pre> <br/>v8::Object中也有关于Internal Field的接口: <br/><pre escaped=“true” lang=“c” class=“brush: c;”> /
Gets the number of internal fields for this Object. / <br/> V8EXPORT int InternalFieldCount(); <br/> /* Gets the value in an internal field. / <br/> inline Local GetInternalField(int index); <br/> /* Sets the value in an internal field. */ <br/> V8EXPORT void SetInternalField(int index, Handle<value>);</pre> <br/>建立了ObjectTemplate后,我们可以通过ObjectTemplate::SetInternalFieldCount设定内部储存多少个内部变量。然后通过ObjectTemplate::NewInstance建立新的Object,再在v8::Object中通过SetInternalField和SetInternalField来对内部变量进行操作。 <br/> <br/>前文中我们提过Accessors,一种用来把C++中的变量返回到Javascript中的机制。那时我们操作的是全局变量,下面的例子里我们将为Object绑定一个C++对象,然后在Javascript中获取它的数据成员[<a href=“https://github.com/zcbenz/there-is-a-c-in-javascript/blob/master/object1.cc” target="_blank">完整代码</a>]。 <br/> <br/>首先我们建立一个C++对象: <br/><pre escaped=“true” lang=“c” class=“brush: c;”>class Point { <br/> public: <br/> Point (int x, int y) <br/> :x (x), y (y) <br/> { <br/> } <br/> <br/> int x, y; <br/>};</pre> <br/>然后在main函数中生成并设置Object: <br/><pre escaped=“true” lang=“c” class=“brush: c;”> // 建造ObjectTemplate <br/> Handle<ObjectTemplate> obj_tpl = ObjectTemplate::New (); <br/> obj_tpl->SetInternalFieldCount (1); <br/> <br/> // 建立Object,并与C++对象绑定 <br/> Point point (10, 10); <br/> Handle<Object> obj = obj_tpl->NewInstance (); <br/> obj->SetInternalField (0, External::New (&point)); <br/> obj->SetAccessor(String::New(“x”), XGetter, XSetter); <br/> obj->SetAccessor(String::New(“y”), YGetter, YSetter); <br/> <br/> // 打印结果 <br/> cout << *String::AsciiValue (obj->Get (String::New (“x”))) << endl; <br/> cout << *String::AsciiValue (obj->Get (String::New (“y”))) << endl;</pre> <br/>最后是用来将C++对象中的数据成员传递到Javascript中的函数: <br/><pre escaped=“true” lang=“c” class=“brush: c;”>Handle<Value> XGetter (Local<String> property, const AccessorInfo& info) { <br/> Handle<Object> obj = info.This (); <br/> Point& point = static_cast<Point> ( <br/> Local<External>::Cast(obj->GetInternalField(0))->Value ()); <br/> <br/> return Integer::New (point.x); <br/>} <br/> <br/>void XSetter (Local<String> property, Local<Value> value, <br/> const AccessorInfo& info) { <br/> Handle<Object> obj = info.This (); <br/> Point& point = static_cast<Point> ( <br/> Local<External>::Cast(obj->GetInternalField(0))->Value ()); <br/> <br/> point.x = value->Int32Value(); <br/>}</pre> <br/>简单说明一下代码,AccessorInfo::This ()可以返回调用该函数的v8::Object,由于C++对象作为External储存在Object的Internal Field中,我们需要使用Object::GetInternalField和External::Value最终得到这个对象。 <br/> <br/>最后,顺带一提,ObjectTemplate对应于Object也有相应的Set、SetAccssor函数,在ObjectTemplate设置好相应的属性后,生成的Object会自动继承它们。比如上面的代码有一处可以改成这样[<a href=“https://github.com/zcbenz/there-is-a-c-in-javascript/blob/master/object2.cc” target="_blank">完整代码</a>]: <br/><pre escaped=“true” lang=“c” class=“brush: c;”> // 建造ObjectTemplate <br/> Handle<ObjectTemplate> obj_tpl = ObjectTemplate::New (); <br/> obj_tpl->SetInternalFieldCount (1); <br/> obj_tpl->SetAccessor(String::New(“x”), XGetter, XSetter); <br/> obj_tpl->SetAccessor(String::New(“y”), YGetter, YSetter);</pre> <br/><h4>PrototypeTemplate</h4> <br/>咦,为什么会突然冒出来个PrototypeTemplate?难道Javascript里有Prototype这种类型吗?非也非也,PrototypeTemplate专为Funtion.prototype而生,FunctionTemplate::PrototypeTemplate返回的是一个ObjectTemplate,用来供用户设定生成函数的prototype,一个例子如下[<a href=“https://github.com/zcbenz/there-is-a-c-in-javascript/blob/master/function5.cc” target="_blank">完整代码</a>]: <br/><pre escaped=“true” lang=“c” class=“brush: c;”> Local<FunctionTemplate> func_tpl = FunctionTemplate::New (); <br/> Local<ObjectTemplate> prototype = func_tpl->PrototypeTemplate (); <br/> prototype->Set (String::New (“proto_const”), Integer::New (2)); <br/> prototype->Set (String::New (“proto_method”), FunctionTemplate::New (print));</pre> <br/><h4>More and more…</h4> <br/>那么,到这里,Javascript的对象和函数就讲完了吗?冰山一角。虽然题为深入,但内容仍是v8基础概念和用法,更多细节,还需要在v8.h中深挖,这篇文章点到为止。下文我将描述node.js异步的机理。

5 回复

[…] Javascript里有个C:Part 3 – 深入对象 […]

顶,“External::Wrap用于包装v8的内部类型,包装外部C++对象时不要使用” 有误 <br/>Wrap尝试把指针作为数值进行处理,若不支持则等同于New,包装的对象可以是任一指针。 <br/>而且把局域变量地址作为指针传入的作用感觉不大,因为往往js对象往往会带着指针跑天下,局域变量显然不合适,因此更常用的手法是Persistent + MakeWeak,不过似乎是题外话了。

谢谢指正!Persistent + MakeWeak我打算放到后面做实例时再讲

介绍的挺清晰的,支持~ <br/>有个问题想问一下,在ObjectTemplate和PrototypeTemplate里Set一个属性有什么区别呢?是不是在ObjectTemplate中Set,会直接在每一个生成的对象实例中添加相应的属性;而PrototypeTemplate则是走js的prototype语义,默认不会为每个实例添加属性,在读时有个寻值的过程,写时才会添加新的属性?

楼主,后面的文章能不能继续写下去?关注你很久了,谢谢!

回到顶部