[翻译] v8 learning advanced advanced, I believe there must be a lot of people see the clouds in the fog, at this time you need this article for advanced advanced combat work, to help you through.

The concepts mentioned in the following advanced steps can be represented in the following three sections. We’ll still have a V8-demo to help us understand things before we get to the concept.

1. Environment preparation

After downloading v8-Demo locally, we need to compile a working V8 library.

1.1. Build V8

If you’ve read nodeJS, you’ll be familiar with v8 compilation. We use the second compilation method, but with a slight difference, we directly link all the dynamic libraries together and generate an object file called v8_monolith, using the following command:

$ aliasV8gen = / path/to/v8 / tools/dev/v8gen py / / this step when compiling v8 has been set for the first time, No Settings can be whole again $v8gen x64. The sample $ninja - C out. Designed.the gn/x64. The sample v8_monolithCopy the code

So you can be in the out. Designed.the gn/x64. The sample directory see so a file libv8_monolith. A:

Then we use CLion to create a new C++ project with the following directory:

The content of our new cmakelists. TXT is as follows:

Cmake_minimum_required (VERSION 3.2) project (V8Demo) include_directories (/ Users/linxiaowu/lot/v8 / include) include_directories(/Users/linxiaowu/Github/v8) link_directories( /Users/linxiaowu/Github/v8/out.gn/x64.release.sample/obj ) link_libraries( v8_monolith )set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -pthread")

set(SOURCE_FILES
        ./helloworld.cc)
set(CALC_SOURCE
        ./shell/shell.cpp
        ./shell/interceptor.cpp
        ./shell/exposeToJsFuncs.cpp
        ./shell/exposeToJsVar.cpp
        ./shell/shell_util.cpp)
add_executable(HelloWorld ${SOURCE_FILES})
add_executable(Shell ${CALC_SOURCE})
Copy the code

The syntax of CMake is not the focus of our study. If you want to learn it, you can refer to CMake Tutorial

Then we press the Shift key twice in CLion to bring up the command line window as shown below:

The reload cmake command generates makefiles as well as ancillary files. So we can Build the project. The generated files are placed under cmake-build-debug:

Execute the corresponding executable file. The result is as follows:

Now that you’re familiar with the whole process, let’s talk about how you can do something with the V8 engine and Js scripts.

2. Brief introduction of basic concepts of V8 engine

[真 题] Many of the concepts are described in full detail in the advanced stages of V8 learning, but here they are simplified to make them more memorable.

2.1, isolate

This concept is not mentioned in the advanced progression of V8 learning, which represents a separate V8 virtual machine, with its own stack. That’s why it got the name ISOLATE, which means “isolate”. Initialize in V8 using the following syntax:

Isolate* isolate = Isolate::New(create_params);
Copy the code

2.2, handle…

Handle is a pointer to an object. In V8, all objects are referred to by Handle, which is used primarily for garbage collection in V8. In V8, the Handle is divided into two types: Persistent Handle and Local Handle. The Persistent handle is stored on the heap, while the Local handle is stored on the stack. For example, if I want to use a local handle that points to a string, you define it like this:

Local<String> source = String::NewFromUtf8(isolate, "'Hello' + ', World'", NewStringType::kNormal).ToLocalChecked();

V8 also provides HandleScope for batch handling. You can declare this before handling:

HandleScope handle_scope(isolate);

2.3, the context

Context is an executor environment that allows separate JavaScript scripts to run in the same V8 instance without interference. To run a JavaScript script, you need to explicitly specify the context object. To create a context, do this:

// Create a Context Local<Context> Context = Context::New(ISOLATE); // Enter the Context to compile and run the script Context::Scope context_scope(Context);Copy the code

2.4. Data type of V8

Since C++ native Data types are very different from JavaScript Data types, V8 provides the Data class. This class and its subclasses are used from JavaScript to C++, and from C++ to JavaScrpt, for example:

String::NewFromUtf8(info.GetIsolate(), "version").ToLocalChecked()
Copy the code

Here String is V8’s data type. Such as:

v8::Integer::New(info.GetIsolate(), 10);
Copy the code

Object templates and function templates

These two template classes are used to define JavaScript objects and JavaScript functions. We’ll look at examples of template classes in a later section. ObjectTemplate is used to expose C++ objects to scripting environments, and FunctionTemplate is used to expose C++ functions to scripting environments for use by scripts.

How to use the V8 engine?

For example, we comment out helloWorld.cc and write the corresponding steps:

Create a stack-allocated handle range. Create a context. Enter the context and compile and run the scriptCopy the code

The corresponding code is as follows:

Int main (int arg c, char * argv []) {/ / 1, to initialize the V8 V8: : InitializeICUDefaultLocation (argv [0]). V8::InitializeExternalStartupData(argv[0]); unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform(); V8::InitializePlatform(platform.get()); V8::Initialize(); Isolate::CreateParams Create_params Isolate::CreateParams Create_params Isolate::CreateParams create_params.array_buffer_allocator = ArrayBuffer::Allocator::NewDefaultAllocator(); Isolate* isolate = Isolate::New(create_params); { Isolate::Scope isolate_scope(isolate); // create a stack allocation HandleScope HandleScope handle_scope(isolate); // create a Context Local<Context> Context = Context::New(isolate); Context::Scope context_scope(Context); { Local<String>source = String::NewFromUtf8(isolate, "'Hello' + ', World'", NewStringType::kNormal).ToLocalChecked();

      Local<Script> script = Script::Compile(context, source).ToLocalChecked();

      Local<Value> result = script->Run(context).ToLocalChecked();

      String::Utf8Value utf8(isolate, result);

      printf("%s\n", *utf8); Dispose of isolate->Dispose(); Dispose of isolate->Dispose(); V8::Dispose(); V8::ShutdownPlatform(); delete create_params.array_buffer_allocator;return 0;
}
Copy the code

With the basics above, it should be easy to understand the code. So next, how do you do something with V8 and JS scripts? Let’s take shell. Cc on the official website as an example, split the file and put it in the v8-demo shell directory:

.├ ── ExposeTojsfuncs.cpp => Store those functions exposed to Js script prototype ├── ExposeTojsfuncs.cpp => Store those exposed to Js script variable access exercise.├ ── Interceptor. ├─ load. Js => Show script files loaded in js ├─ shell.cpp => Main file ├─ shell.h => header file ├─ shell_utilCopy the code

Shell. Cc provides a simple version of the CLI, in which you can execute js scripts and output results, or load JS files into the CLI for execution. For example, demo provides load.

Now that we have an overview of shell.cc we are going to pull the template out of the demo and start with the following three parts.

4. Use C++ variables

Sharing templates between C++ and Js is relatively easy, and access to C++ variables is mentioned in section 3 accessors in V8’s advanced progression. There is a distinction between static global variables and dynamic variables, which we implemented in V8-Demo.

4.1. Use global static variables

The use of this is made clear in the advanced stages of V8 learning, where the flow should be:

Char version[100]; char version[100];

2. Define Getter/Setter methods for global static variables in exposeToJsVar:

void VersionGetter(Local<String> property,
                 const PropertyCallbackInfo<Value> &info) {
  info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), version).ToLocalChecked());
}

void VersionSetter(Local<String> property, Local<Value> value, const PropertyCallbackInfo<void> &info) {
  String::Utf8Value str(info.GetIsolate(), value);
  const char *result = ToCString(str);
  strncpy(version, result, sizeof(version));
}
Copy the code

Remember its generic function signature: (Local

property, const PropertyCallbackInfo

&info) and (Local

property, Local

Value, const PropertyCallbackInfo

&info)




Local

global = ObjectTemplate::New(ISOLATE);

Mount getters and setters for version to global objects and expose them to Js as version:

global->SetAccessor(String::NewFromUtf8(isolate, "version").ToLocalChecked(), VersionGetter, VersionSetter);
Copy the code

To run our example directly, do the following:

4.2. Use dynamic variables

Using dynamic variables is what we’ll talk about in section 3.

2. Call C++ functions

Calling C++ functions in JavaScript is the most common way of scripting. By using C++ functions, you can greatly enhance the capabilities of JavaScript scripts, such as file reading and writing, network/database access, graphics/image processing, etc. In V8, Calling C++ functions is also very convenient.

First we define the prototype function Print in C++ :

void Print(const FunctionCallbackInfo <Value> &args) {
  bool first = true;
  for (int i = 0; i < args.Length(); i++) {
    HandleScope handle_scope(args.GetIsolate());
    if (first) {
      first = false;
    } else {
      printf("");
    }
    String::Utf8Value str(args.GetIsolate(), args[i]);
    const char *cstr = ToCString(str);
    printf("%s", cstr);
  }
  printf("\n");
  fflush(stdout);
}
Copy the code

This class of functions all have the same function signature :(const FunctionCallbackInfo &args).

Then expose it to the Js environment:

Local<ObjectTemplate> global = ObjectTemplate::New(isolate);
global->Set(
      String::NewFromUtf8(isolate, "print", NewStringType::kNormal)
          .ToLocalChecked(),
      FunctionTemplate::New(isolate, Print));
Copy the code

We can then call the print function in the Js environment, which was shown in Figure 5 above:

function sum(){
	var s = 0;
	for(var i = 0; i < arguments.length; i++){
    print(arguments[i])
		s += arguments[i];
	}
	return s;
}
Copy the code

3. Use C++ classes (dynamic variables)

The only confusion is that the article does not provide a way to access the defined C++ classes, and the examples are not “dynamic” enough, as they are only supported in C++, not in js scripts. So we converted it.

Before we talk about the transformation, let’s make it clear that if we use dynamic variables, in our V8-demo, shell. CPP defines a macro that looks like this:

Local<ObjectTemplate> point_templ = ObjectTemplate::New(isolate);
point_templ->SetInternalFieldCount(1);
point_templ->SetAccessor(String::NewFromUtf8(isolate, "x").ToLocalChecked(), GetPointX, SetPointX);
point_templ->SetAccessor(String::NewFromUtf8(isolate, "y").ToLocalChecked(), GetPointY, SetPointY);
Point* p = new Point(11, 22);
Local<Object> obj = point_templ->NewInstance(context).ToLocalChecked();
obj->SetInternalField(0, External::New(isolate, p));
context->Global()->Set(context, String::NewFromUtf8(isolate, "p").ToLocalChecked(), obj).ToChecked();
Copy the code

The complete process is as follows:

  1. Creating an Object Template
  2. Object template Specifies the number of internal fields
  3. Accessors that mount the member variables of the object template
  4. Create a new instance object
  5. Object template instantiation
  6. Set the internal field index of the instantiated template to 0 pointing to the instance object
  7. Expose the variable p to the JS space for access

So what if we want to dynamically create Point objects in Js space? So here’s another set of processes:

3.1. Dynamically create Point objects

First we define the Point class in shell.h:

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


  int multi() {
    returnthis->x_ * this->y_; }};Copy the code

A class has two member variables and a member function. Next we wrap the Point constructor:

void constructPoint(const FunctionCallbackInfo <Value> &args) {
  Isolate* isolate = Isolate::GetCurrent();

  //get an x and y
  double x = args[0]->NumberValue(isolate->GetCurrentContext()).ToChecked();
  double y = args[1]->NumberValue(isolate->GetCurrentContext()).ToChecked();

  //generate a new point
  Point *point = new Point(x, y);

  args.This()->SetInternalField(0, External::New(isolate, point));
}
Copy the code

As you can see from the function prototype, the wrapper for the constructor is the same as the wrapper for the function in the previous section, because a constructor, in V8’s view, is also a function. Note that after taking the parameter from args and converting it to the appropriate type, we call the actual constructor of the Point class based on this parameter and set it in the internal field of Object. Next, we need to wrap the getter/setter for the Point class:

void GetPointX(Local<String> property,
                   const PropertyCallbackInfo<Value> &info) {
  printf("GetPointX is calling\n");

  Local<Object> self = info.Holder();
  Local<External> wrap = Local<External>::Cast(self->GetInternalField(0));
  void* ptr = wrap->Value();
  int value = static_cast<Point*>(ptr)->x_;
  info.GetReturnValue().Set(value);
}
void SetPointX(Local<String> property, Local<Value> value, const PropertyCallbackInfo<void> &info) {
  printf("SetPointX is calling\n");

  Local<Object> self = info.Holder();
  Local<External> wrap = Local<External>::Cast(self->GetInternalField(0));
  void* ptr = wrap->Value();
  static_cast<Point*>(ptr)->x_ = value->Int32Value(info.GetIsolate()->GetCurrentContext()).ToChecked();
}
Copy the code

And wrapping the Point class member methods:

void PointMulti(const FunctionCallbackInfo <Value> &args) { Isolate* isolate = Isolate::GetCurrent(); //start a handle scope HandleScope handle_scope(isolate); Local<Object> self = args.Holder(); Local<External> wrap = Local<External>::Cast(self->GetInternalField(0)); void* ptr = wrap->Value(); Int value = static_cast<Point*>(PTR)->multi(); args.GetReturnValue().Set(value); }Copy the code

After wrapping the function, we need to expose the Point class to the script environment:

Handle<FunctionTemplate> point_templ = FunctionTemplate::New(isolate, constructPoint);
point_templ->SetClassName(String::NewFromUtf8(isolate, "Point").ToLocalChecked()); Global ->Set(String::NewFromUtf8(ISOLATE,"Point", NewStringType::kNormal).ToLocalChecked(), point_templ);
Copy the code

Then define the prototype template:

Point_proto = point_templ->PrototypeTemplate(); Handle<ObjectTemplate> point_proto = point_templ->PrototypeTemplate(); Point_proto ->Set(String::NewFromUtf8(ISOLATE,"multi", NewStringType::kNormal).ToLocalChecked(), FunctionTemplate::New(isolate, PointMulti));
Copy the code

Then instantiate the template:

Point_inst = point_templ->InstanceTemplate(); Handle<ObjectTemplate> point_inst = point_templ->InstanceTemplate(); //set the internal fields of the class as we have the Point class internally
point_inst->SetInternalFieldCount(1);

//associates the name "x"/"y" with its Get/Set functions
point_inst->SetAccessor(String::NewFromUtf8(isolate, "x").ToLocalChecked(), GetPointX, SetPointX);
point_inst->SetAccessor(String::NewFromUtf8(isolate, "y").ToLocalChecked(), GetPointY, SetPointY);
Copy the code

So we can easily use the Point class in the Js environment without worrying about the Point class definition:

In this example we use the inheritance function mentioned in Section 7 of V8 learning Advanced: PrototypeTemplate. There is also the InstanceTemplate. Here’s how they work:

  1. PrototypeTemplateUsed to define functions or accessors on prototypes
  2. InstanceTemplateUse to add a function or accessor to a class instance that has already been instantiated by calling the constructor function

4. Use interceptors

Interceptors are for all attributes, accessors are for individual attributes, so we added an interceptor to the example above. Note: the interceptor method in the original is obsolete!! Use the examples below

/ / to visit x set an interceptor point_inst - > SetHandler (NamedPropertyHandlerConfiguration (PointInterceptorGetter, PointInterceptorSetter));Copy the code

The interceptor is defined as follows :(in interceptor.cpp)

void PointInterceptorGetter(
    Local<Name> name, const PropertyCallbackInfo<Value>& info) {
  if (name->IsSymbol()) return;

  // Fetch the map wrapped by this object.
  map<string, string>* obj = UnwrapMap(info.Holder());

  // Convert the JavaScript string to a std::string.
  string key = ObjectToString(info.GetIsolate(), Local<String>::Cast(name));

  printf("interceptor Getting for Point property has called, name[%s]\n", key.c_str()); // If this setting is calledreturnGetter // info.getreturnValue ().set (11); }Copy the code

In the figure above, we see that the interceptor’s printf function is printed whenever p1.x is accessed. That means the interceptor is working. Another thing to note:

If you call info.getreturnValue () again in the interceptor, the accessor will not continue execution and will be put back in the interceptor. You can try uncomment the code

reference

  1. Getting started with embedding V8
  2. Building V8 with GN
  3. Develop customizable applications using the Google V8 engine