JS技术

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

字号+ 作者:fool 来源: 2014-11-16 22:20 我要评论( )

Javascript里有个C系列文章:

  1. Javascript里有个C:Part 1 – 基础
  2. Javascript里有个C:Part 2 – 对象
  3. Javascript里有个C:Part 3 – 深入对象
  4. Javascript里有个C:Part 4 – 异步
  5. Javascript里有个C:Part 5 – node.js
  6. Javascript里有个C:Part 6 – 实战node.js Module

书至第三回。前两回当啷结束,C++里怎么摆弄Javascript,相信大家已略有所知。在本文,我们将更进一步。怎么从上帝视角玩弄Javascript?怎么把C++强力插入Javascript?这里都将一一解惑。

External

首先我们需要了解一种特殊的Value,v8::External,它的作用是把C++的对象包装成Javascript中的变量。External::New接受一个C++对象的指针作为初始化参数,然后返回一个包含这个指针的Handle<External>对象供v8引擎使用。在使用这个Handle<External>对象时可以通过External::Value函数来得到C++对象的指针,接口定义如下:

/**
 * A JavaScript value that wraps a C++ void*.  This type of value is
 * mainly used to associate C++ data structures with JavaScript
 * objects.
 *
 * The Wrap function V8 will return the most optimal Value object wrapping the
 * C++ void*. The type of the value is not guaranteed to be an External object
 * and no assumptions about its type should be made. To access the wrapped
 * value Unwrap should be used, all other operations on that object will lead
 * to unpredictable results.
 */
class External : public Value {
public:
  V8EXPORT static Local<Value> Wrap(void* data);
  static inline void* Unwrap(Handle<Value> obj);

  V8EXPORT static Local<External> New(void* value);
  static inline External* Cast(Value* obj);
  V8EXPORT void* Value() const;
 private:
  V8EXPORT External();
  V8EXPORT static void CheckCast(v8::Value* obj);
  static inline void* QuickUnwrap(Handle<v8::Value> obj);
  V8EXPORT static void* FullUnwrap(Handle<v8::Value> obj);
};

需要注意的是,External::Wrap会尝试把指针作为数值进行处理,若不支持则等同于New。

和Handle不同,External并不会托管C++对象的生命期,所以你必须手动构造、释放用来包装的C++对象。比较常用的手段是,在C++的栈上建立对象,然后再用External进行托管。下面是一个例子[完整代码]:

    std::string str ("Hello World!");
    Local<External> external = External::New (&str);
    cout << *static_cast<std::string*> (external->Value());

Template

怎么把Javascript的对象和C++的对象联系起来?v8::Object并未提供从C++对象构造的方法,而Javascript的函数v8::Function甚至没有提供初始化::New函数!这就需要一种特殊的类型,v8::Template。

Template是介于Javascript和C++变量间的中间层,你首先由C++对象生成一个Template,然后再由Template生成Javascript函数的对象。你可以事先在Template中准备好一些预备属性,然后生成的Javascript对象都将具备这些属性。

一个例子就是Google Chrome的DOM,DOM就是先用ObjectTemplate预先封装好对应的C++节点,最后再为每一个标签生成DOM对象。

FunctionTemplate

首先介绍FunctionTemplate,顾名思义,就是用来生成函数的Template。之前函数一直在文章里缺席,原因之一就是Javascript函数和C++函数的绑定必须仰赖Template。

FunctionTemplate的接口如下:

class V8EXPORT FunctionTemplate : public Template {
public:
  /** Creates a function template.*/
  static Local<FunctionTemplate> New(
      InvocationCallback callback = 0,
      Handle<Value> data = Handle<Value>(),
      Handle<Signature> signature = Handle<Signature>());
  /** Returns the unique function instance in the current execution context.*/
  Local<Function> GetFunction();

  /**
   * Set the call-handler callback for a FunctionTemplate.  This
   * callback is called whenever the function created from this
   * FunctionTemplate is called.
   */
  void SetCallHandler(InvocationCallback callback,
                      Handle<Value> data = Handle<Value>());
/** Causes the function template to inherit from a parent function template.*/
  void Inherit(Handle<FunctionTemplate> parent);

  /**
   * A PrototypeTemplate is the template used to create the prototype object
   * of the function created by this template.
   */
  Local<ObjectTemplate> PrototypeTemplate();

  /**
   * Set the class name of the FunctionTemplate.  This is used for
   * printing objects created with the function created from the
   * FunctionTemplate as its constructor.
   */
  void SetClassName(Handle<String> name);
};

你可以使用FunctionTemplate::New ()生成一个空函数,然后用FunctionTemplate::SetCallHandler ()将其和C++函数绑定,或者直接靠FunctionTemplate::New (InvocationCallback callback)来用C++函数初始化一个FunctionTemplate。

用来生成FunctionTemplate的C++函数必须满足InvocationCallback的条件,即函数签名必须如下:

typedef Handle<Value> (*InvocationCallback)(const Arguments& args);

此后,你可以使用FunctionTemplate::GetFunction()来获取对应的v8::Function。但是一个FunctionTemplate只能生成一个Function,FunctionTemplate::GetFunction()返回的都是同一个实体。这是因为Javascript里显式声明的全局函数只有一个实例。

不过,得到生成的函数后,你可以使用Function::NewInstance返回一个函数对象,等同于Javascript中的var tmp = new func;。

下面是一个使用FunctionTemplate的简单例子[完整代码]:

Handle<Value> print (const Arguments& args) {
    HandleScope scope;

    for (int i = 0; i< args.Length(); ++i) {
        cout << *String::Utf8Value (args[i]) << " ";
    }   

    cout << endl;

    return Undefined ();
}

// main函数中

    // Generate Function
    Local<FunctionTemplate> func_tpl = FunctionTemplate::New (print);
    func_tpl->SetClassName (String::NewSymbol ("print"));
    Local<Function> func = func_tpl->GetFunction ();
    cout << *String::Utf8Value (func) << endl;

    // Generate parameters
    Local<Value> args[4] = {
        String::New ("I"),
        String::New ("Love"),
        String::New ("C++"),
        String::New ("!")
    };

    // Call Function
    func->Call (Object::New (), 4, args);

首先,我们定义了一个print函数,这个函数接受Arguments作为参数,Arguments实际上是一个包含了传进进来的参数的数组。接着我们建立了FunctionTemplate func_tpl,然后通过func_tpl->GetFunction得到函数实体,最后对这个函数实体进行调用。

那么,如果我们想要在Javascript代码中调用这个函数,应该怎么做呢,下面是一个演示[完整代码]:

    // Generate Function
    Local<FunctionTemplate> func_tpl = FunctionTemplate::New (print);
    context->Global()->Set (String::NewSymbol ("print"),
                            func_tpl->GetFunction ());

    // Call in javascript
    Local<String> source = String::New ("print ('I', 'Love', 'C++', '!');");
    Script::Compile(source)->Run ();

修改之处主要是在全局作用域中加入我们的函数对象。

Function Instance

Javascript常用new Function ();的形式来创建对象,而C++中,Function::NewInstance可以返回一个函数的Instance。

比如下面的代码:

function girl (name) {
    this.name = name;
}

var alice = new girl ("Alice");
alice.age = 21;

对应到C++中则是[完整代码]:

Handle<Value> girl (const Arguments& args) {
    HandleScope scope;

    if (args.Length() != 1 && !args[0]->IsString ())
        return ThrowException(v8::String::New("Unexpected arguments"));

    args.This()->Set (String::New ("name"), args[0]);

    return Undefined ();
}

// 在main函数中
    Local<FunctionTemplate> func_tpl = FunctionTemplate::New (girl);

    Handle<Value> args[1] = { String::New ("Alice") };
    Handle<Object> alice = func_tpl->GetFunction()->NewInstance (1, args);
    alice->Set (String::New ("age"), Integer::New (21));

其中有两点需要新的注意,首先Javascript中的this指针可以通过Arguments::This ()得到,其次C++中抛出Javascript的异常时,需要返回一个ThrowException对象。

ObjectTemplate

既然有生成函数的Template,自然也可以想到会有生成对象的Template,ObjectTemplate的目的就是根据包装起来的C++对象生成v8::Object。接口与Template也大致相当,通过ObjectTemplate::New返回新的ObjectTemplate,通过ObjectTemplate::NewInstance。

但是,C++对象应该存放在哪里呢?ObjectTemplate提供了一种Internal Field,也就是内部储存空间,我们可以通过External类型把C++对象储存在ObjectTemplate中相关接口如下:

  /**
   * Gets the number of internal fields for objects generated from
   * this template.
   */
  int InternalFieldCount();

  /**
   * Sets the number of internal fields for objects generated from
   * this template.
   */
  void SetInternalFieldCount(int value);

v8::Object中也有关于Internal Field的接口:

  /** Gets the number of internal fields for this Object. */
  V8EXPORT int InternalFieldCount();
  /** Gets the value in an internal field. */
  inline Local GetInternalField(int index);
  /** Sets the value in an internal field. */
  V8EXPORT void SetInternalField(int index, Handle<value>);

建立了ObjectTemplate后,我们可以通过ObjectTemplate::SetInternalFieldCount设定内部储存多少个内部变量。然后通过ObjectTemplate::NewInstance建立新的Object,再在v8::Object中通过SetInternalField和SetInternalField来对内部变量进行操作。

前文中我们提过Accessors,一种用来把C++中的变量返回到Javascript中的机制。那时我们操作的是全局变量,下面的例子里我们将为Object绑定一个C++对象,然后在Javascript中获取它的数据成员[完整代码]。

首先我们建立一个C++对象:

class Point {
    public:
        Point (int x, int y)
            :x  (x), y (y)
        {
        }

        int x, y;
};

然后在main函数中生成并设置Object:

    // 建造ObjectTemplate
    Handle<ObjectTemplate> obj_tpl = ObjectTemplate::New ();
    obj_tpl->SetInternalFieldCount (1);

    // 建立Object,并与C++对象绑定
    Point point (10, 10);
    Handle<Object> obj = obj_tpl->NewInstance ();
    obj->SetInternalField (0, External::New (&point));
    obj->SetAccessor(String::New("x"), XGetter, XSetter);
    obj->SetAccessor(String::New("y"), YGetter, YSetter);

    // 打印结果
    cout << *String::AsciiValue (obj->Get (String::New ("x"))) << endl;
    cout << *String::AsciiValue (obj->Get (String::New ("y"))) << endl;

最后是用来将C++对象中的数据成员传递到Javascript中的函数:

Handle<Value> XGetter (Local<String> property, const AccessorInfo& info) {
    Handle<Object> obj = info.This ();
    Point& point = *static_cast<Point*> (
            Local<External>::Cast(obj->GetInternalField(0))->Value ());

    return Integer::New (point.x);
}

void XSetter (Local<String> property, Local<Value> value,
              const AccessorInfo& info) {
    Handle<Object> obj = info.This ();
    Point& point = *static_cast<Point*> (
            Local<External>::Cast(obj->GetInternalField(0))->Value ());

    point.x = value->Int32Value();
}

简单说明一下代码,AccessorInfo::This ()可以返回调用该函数的v8::Object,由于C++对象作为External储存在Object的Internal Field中,我们需要使用Object::GetInternalField和External::Value最终得到这个对象。

最后,顺带一提,ObjectTemplate对应于Object也有相应的Set、SetAccssor函数,在ObjectTemplate设置好相应的属性后,生成的Object会自动继承它们。比如上面的代码有一处可以改成这样[完整代码]:

    // 建造ObjectTemplate
    Handle<ObjectTemplate> obj_tpl = ObjectTemplate::New ();
    obj_tpl->SetInternalFieldCount (1);
    obj_tpl->SetAccessor(String::New("x"), XGetter, XSetter);
    obj_tpl->SetAccessor(String::New("y"), YGetter, YSetter);

PrototypeTemplate

咦,为什么会突然冒出来个PrototypeTemplate?难道Javascript里有Prototype这种类型吗?非也非也,PrototypeTemplate专为Funtion.prototype而生,FunctionTemplate::PrototypeTemplate返回的是一个ObjectTemplate,用来供用户设定生成函数的prototype,一个例子如下[完整代码]:

    Local<FunctionTemplate> func_tpl = FunctionTemplate::New ();
    Local<ObjectTemplate> prototype = func_tpl->PrototypeTemplate ();
    prototype->Set (String::New ("proto_const"), Integer::New (2));
    prototype->Set (String::New ("proto_method"), FunctionTemplate::New (print));

More and more…

那么,到这里,Javascript的对象和函数就讲完了吗?冰山一角。虽然题为深入,但内容仍是v8基础概念和用法,更多细节,还需要在v8.h中深挖,这篇文章点到为止。下文我将描述node.js异步的机理。

关于作者 赵成, 东南大学, 学生 C++狂热,缺少零花钱 新浪微博 向文章付费 请作者吃饭

Related posts:

  1. Javascript里有个C:Part 2 – 对象
  2. Javascript里有个C:Part 1 – 基础
  3. node.js源码研究—模块组织加载
  4. NodeJS的面向对象编程
  5. 请求路由模块journey的源码解读和学习心得(一)

 

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

相关文章
  • 学习JavaScript之this,call,apply

    学习JavaScript之this,call,apply

    2016-01-28 20:45

  • 零基础入门学习Python(10):函数 - qq_33256568的博客 - 博客频道 - CSDN.NET qq_3

    零基础入门学习Python(10):函数 - qq_33256568的博客 - 博客频道

    2015-12-15 09:04

  • Swift 2.0学习笔记(Day48)——类型检查与转换 - 关东升 - 博客频道 - CSDN.NET 关东升 iO

    Swift 2.0学习笔记(Day48)——类型检查与转换 - 关东升 - 博客频道

    2015-12-14 18:16

  • 有趣的Ruby-学习笔记1 - 我可以接受失败,但我不能接受放弃。--迈克尔 乔丹 - 博客频道 - CSDN.NET

    有趣的Ruby-学习笔记1 - 我可以接受失败,但我不能接受放弃。--迈克

    2015-12-14 17:17

网友点评