65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 V8 - Google 的 Chrome JavaScript 虚拟机

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (41投票s)

2008年9月5日

公共领域

6分钟阅读

viewsIcon

276887

downloadIcon

7236

教程和示例,解释如何在您的应用程序中使用 V8 虚拟机。

引言

谁不曾好奇虚拟机是如何工作的?但是,与其自己创建一个虚拟机,不如使用一家大型公司开发并专注于提高虚拟机速度的虚拟机?在这篇文章中,我将介绍如何在您的应用程序中使用 V8,它是 Chrome(谷歌的新浏览器)中谷歌的开源 JavaScript 引擎。

背景

这段代码将 V8 用作嵌入式库来执行 JavaScript 代码。要获取库的源代码和更多信息,请访问 V8 开发者页面。要有效地使用 V8 库,您需要了解 C/C++ 和 JavaScript。

Using the Code

让我们看看演示中有什么。演示展示了

  • 如何使用 V8 库 API 执行 JavaScript 源代码。
  • 如何访问脚本中的整数和字符串。
  • 如何创建自己的函数,该函数可以在脚本中调用。
  • 如何创建自己的 C++ 类,该类可以在脚本中调用。

首先,让我们了解如何初始化 API。请看这个在 C++ 中嵌入 V8 的简单示例

#include <v8.h> 
using namespace v8;
int main(int argc, char* argv[]) {
  // Create a stack-allocated handle scope. 
  HandleScope handle_scope;
  // Create a new context. 
  Handle<Context> context = Context::New();
  // Enter the created context for compiling and 
  // running the hello world script.
  Context::Scope context_scope(context);
  // Create a string containing the JavaScript source code. 
  Handle<String> source = String::New("'Hello' + ', World!'");
  // Compile the source code. 
  Handle<Script> script = Script::Compile(source);
  // Run the script to get the result. 
  Handle<Value> result = script->Run();
  // Convert the result to an ASCII string and print it. 
  String::AsciiValue ascii(result);
  printf("%s\n", *ascii);
  return 0;
}

好的,但这并没有解释我们如何控制脚本中的变量和函数。好吧,脚本会做一些事情……但是,我们需要以某种方式获取这些信息,并拥有自定义函数来控制脚本内的特定行为。

全局模板

首先,我们需要一个全局模板来控制我们的修改

v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New();

这会创建一个新的全局模板,它管理我们的上下文和我们的自定义项。这很重要,因为在 V8 中,每个上下文都是独立的,并且可以拥有自己的全局模板。在 V8 中,上下文是一个执行环境,它允许独立的、不相关的 JavaScript 应用程序在单个 V8 实例中运行。

添加自定义函数

之后,我们可以添加一个名为“plus”的新函数

// plus function implementation - Add two numbers
v8::Handle<v8::Value> Plus(const v8::Arguments& args)
{ 
    unsigned int A = args[0]->Uint32Value();
    unsigned int B = args[1]->Uint32Value();
    return v8_uint32(A +  B);
} 
//...
//associates plus on script to the Plus function
global->Set(v8::String::New("plus"), v8::FunctionTemplate::New(Plus));

自定义函数需要接收 const v8::Arguments& 作为参数,并且必须返回 v8::Handle<v8::Value>。使用之前创建的全局模板指针,我们将函数添加到模板中,并将名称“plus”与回调“Plus”关联起来。现在,每次我们在脚本中使用“plus”函数时,都会调用“Plus”函数。Plus 函数只做一件事:获取第一个和第二个参数并返回它们的和。

好的,现在我们可以在 JavaScript 端使用自定义函数了

plus(120,44);  

并且我们可以在脚本中使用此函数的结果

x = plus(1,2); 
if( x == 3){
   // do something important here!
}

访问器 - 脚本内可访问的变量!

现在,我们可以创建函数……但如果我们可以使用脚本外部定义的某些内容,那不是更酷吗?我们来试试! V8 有一个称为 Accessor 的东西,通过它,我们可以将一个变量与一个名称和两个 Get/Set 函数关联起来,V8 在运行脚本时会使用它们来访问该变量。

global->SetAccessor(v8::String::New("x"), XGetter, XSetter);

这会将名称“x”与“XGetter”和“XSetter”函数关联起来。“XGetter”将在脚本需要“x”变量的值时由 V8 调用,而“XSetter”将在脚本必须更新“x”的值时由 V8 调用。现在,函数

//the x variable!
int x;
//get the value of x variable inside javascript
static v8::Handle<v8::Value> XGetter( v8::Local<v8::String> name, 
                  const v8::AccessorInfo& info) {
  return  v8::Number::New(x);
}
//set the value of x variable inside javascript
static void XSetter( v8::Local<v8::String> name, 
       v8::Local<v8::Value> value, const v8::AccessorInfo& info) {
  x = value->Int32Value();
}

XGetter 中,我们只需要将“x”转换为 V8 管理的数字类型值。在 XSetter 中,我们需要将作为参数传递的值转换为整数值。每种基本类型都有一个函数(NumberValue 用于 doubleBooleanValue 用于 bool 等)

现在,我们可以为字符串(char*)做同样的事情

//the username accessible on c++ and inside the script
char username[1024];
//get the value of username variable inside javascript
v8::Handle<v8::Value> userGetter(v8::Local<v8::String> name, 
           const v8::AccessorInfo& info) {
    return v8::String::New((char*)&username,strlen((char*)&username));
}
//set the value of username variable inside javascript
void userSetter(v8::Local<v8::String> name, v8::Local<v8::Value> value,
    const v8::AccessorInfo& info) {
    v8::Local<v8::String> s = value->ToString();
    s->WriteAscii((char*)&username);
}

对于字符串,情况稍有变化。“userGetter”以与 XGetter 相同的方式创建一个新的 V8 字符串,但 userSetter 需要首先通过使用 ToString 函数访问内部字符串缓冲区。然后,使用指向内部字符串对象的指针,我们使用 WriteAscii 函数将内容写入我们的缓冲区。现在,只需添加访问器,即可大功告成!

//create accessor for string username
global->SetAccessor(v8::String::New("user"),userGetter,userSetter); 

打印

print”函数是另一个自定义函数,它捕获所有参数并使用“printf”函数打印它们。就像我们之前对“plus”函数所做的那样,我们将新函数注册到全局模板

//associates print on script to the Print function
global->Set(v8::String::New("print"), v8::FunctionTemplate::New(Print)); 

“print”实现

// The callback that is invoked by v8 whenever the JavaScript 'print'
// function is called. Prints its arguments on stdout separated by
// spaces and ending with a newline.
v8::Handle<v8::Value> Print(const v8::Arguments& args) {
    bool first = true;
    for (int i = 0; i < args.Length(); i++)
    {
        v8::HandleScope handle_scope;
        if (first)
        {
            first = false;
        }
        else
        {
            printf(" ");
        }
        //convert the args[i] type to normal char* string
        v8::String::AsciiValue str(args[i]);
        printf("%s", *str);
    }
    printf("\n");
    //returning Undefined is the same as returning void...
    return v8::Undefined();
}

对于每个参数,都会构造 v8::String::AsciiValue 对象来创建一个 char* 表示形式的值。通过它,我们可以将其他类型转换为它们的字符串表示形式并打印它们!

JavaScript 示例

在演示程序中,我们有以下 JavaScript 示例来使用我们迄今为止创建的内容

print("begin script");
print(script executed by  + user);
if ( user == "John Doe"){
    print("\tuser name is invalid. Changing name to Chuck Norris");
    user = "Chuck Norris";
}
print("123 plus 27 = " + plus(123,27));
x = plus(3456789,6543211);
print("end script");

此脚本使用了“x”变量、“user”变量以及“plus”和“print”函数。

带 C++ 对象的访问器!

v8_demo_object.JPG

为我们的类准备环境

现在,如何使用 C++ 将类映射到 JavaScript?首先,示例类

//Sample class mapped to v8
class Point 
{ 
public: 
    //constructor
    Point(int x, int y):x_(x),y_(y){}

    //internal class functions
    //just increment x_
    void Function_A(){++x_;    }

    //increment x_ by the amount
    void Function_B(int vlr){x_+=vlr;}

    //variables
    int x_; 
};

为了让这个类完全在 JavaScript 中可用,我们需要映射函数和内部变量。第一步是在我们的上下文中映射一个类模板

Handle<FunctionTemplate> point_templ = FunctionTemplate::New();
point_templ->SetClassName(String::New("Point"));

我们创建一个“函数”模板,但这可以被视为一个类。此模板将具有“Point”名称,以便以后使用(例如在测试脚本中创建该类的实例)。

然后,我们访问原型类模板以向我们的类添加内置方法

Handle<ObjectTemplate> point_proto = point_templ->PrototypeTemplate();
point_proto->Set("method_a", FunctionTemplate::New(PointMethod_A));
point_proto->Set("method_b", FunctionTemplate::New(PointMethod_B));

在此之后,该类“知道”它有两个方法和回调。但这仍然在原型中,我们无法使用它,除非访问类的实例。

Handle<ObjectTemplate> point_inst = point_templ->InstanceTemplate();
point_inst->SetInternalFieldCount(1);

SetInternalFieldCount 函数为 C++ 类指针创建空间(稍后会看到)。

现在,我们有了类的实例,并且可以为内部变量添加访问器

point_inst->SetAccessor(String::New("x"), GetPointX, SetPointX);

然后,我们准备好了“地面”……撒下种子

Point* p = new Point(0, 0);

新类已创建,但只能在 C++ 中访问。要访问它,我们需要

Handle<Function> point_ctor = point_templ->GetFunction();
Local<Object> obj = point_ctor->NewInstance();
obj->SetInternalField(0, External::New(p));

好了,GetFunction 返回点构造函数(在 JavaScript“端”),通过它可以创建一个新实例使用 NewInstance。然后,我们使用类指针设置我们的内部字段(我们已经用 SetInternalFieldCount 为其“创建了空间”)。这样,JavaScript 就可以通过指针访问该对象。

只剩最后一步了。我们只有一个类模板和实例,但没有名称可以在 JavaScript 中访问它

context->Global()->Set(String::New("point"), obj);

最后一步将“point”名称链接到实例 obj。通过这些步骤,我们可以在脚本中(在加载或运行它之前!)模拟创建名称为 point 的类 Point

在 JavaScript 中访问类方法

好的,但这并没有解释我们如何访问 Point 类中的 Function_A……

让我们看看 PointMethod_A 回调

Handle<Value> PointMethod_A(const Arguments& args)
{
    Local<Object> self = args.Holder();
    Local<External> wrap = Local<External>::Cast(self->GetInternalField(0));
    void* ptr = wrap->Value();
    static_cast<Point*>(ptr)->Function_A();
    return Integer::New(static_cast<Point*>(ptr)->x_);
}

像正常的访问器一样,我们必须处理参数。但是,要访问我们的类,我们必须从内部字段(第一个)获取类指针。在将内部字段映射到“wrap”之后,我们通过检索其“值”来获取类指针。现在,这是转换的问题……

现在,我们有了正在使用的“point”类的类指针(它调用了 method_a,它调用了回调 PointMethod_A)。

示例v8_embedded_demo_with_object.zip 实现所有这些步骤。

我希望这有帮助,如果有什么不对或不清楚的地方,请随时联系。

关注点

在尝试使用 V8 库之前,我曾尝试使用 Lua 作为嵌入式脚本语言,以支持用户轻松配置和更改。我惊叹于 V8,因为它比 Lua 更快、更易于在我的软件中实现脚本功能。我希望这篇文章能帮助其他人完成这项任务。

还可以查看谷歌关于 V8 库的文档!

历史

  • 2008 年 9 月 5 日 - 首次实现。
  • 2008 年 9 月 8 日 - Lua 与 V8 的基准测试以及使用动态变量的示例。
  • 2008 年 9 月 11 日 - Lua 基准测试已更新(第 2 版),最后,JavaScript 中的对象访问。
© . All rights reserved.