最新的基於ChromeV8引擎JS與C++互調過程。


##C++調用js方法:##
我是按照v8給的sample中的process.cc中的實例做的,因為OpenGL繪制的工作在onDrawFrame()中執行的,所以我們要確保js中繪制代碼的方法在onDrawFrame()中執行,所以我們需要在js中加上一些方法,通過v8來讓C++決定方法什么時候調用。比如像下面的一個js方法,邏輯應該是這樣的,我們通過調用initView(這個方法是通過v8包裝的,關於如何包裝的下面會詳細介紹這里先說邏輯)來調用我們在C++中暴露給js的繪制接口,這個方法我們需要在onDrawFrame()(jni方法)中。function initView(){draw();}
有一個重要的問題就是關於v8初始化的時機,一開始我在Activity的onCreate()中單獨用一個jni方法來初始化v8,其他關於v8的操作都是在onSurfaceChanged,onDrawFrame,onSurfaceCreated的jni方法中進行的,但是當我在onDrawFrame()的jni方法調用js方法initView()來繪制view的時候總是報錯。在找不到原因后我把v8初始化的時機改成在onSurfaceCreated的jni方法中,由於v8只需初始化一次,所以要在清單文件中給游戲Activity的配置必須至少為android:configChanges="orientation|keyboardHidden|screenSize";改完之后方法調用成功。這里的原因可能是v8初始化與運行的環境問題,暫時不明。下面來看一下具體邏輯C++調用js方法v8的封裝邏輯:

    ```java

    v8Helper::Initialize();
    Isolate::Scope isolate_scope(v8Helper::GetIsolate());
    // Create a stack-allocated handle scope.
    HandleScope handle_scope(v8Helper::GetIsolate());
    // Create a new context.
    Local<Context> context =creatTestContext(v8Helper::GetIsolate());
    context_.Reset(v8Helper::GetIsolate(),context);
    if (context.IsEmpty())
    {
        LOGI("Error creating context\n");
    }
    // Enter the context for compiling and running the hello world script.
    Context::Scope context_scope(context);
    // Create a string containing the JavaScript source code.
    bool result =   v8Helper::ExecuteString(context->GetIsolate(),
                                            String::NewFromUtf8(context->GetIsolate(), jsSource,
                                                                  NewStringType::kNormal).ToLocalChecked(),true, true);
    LOGI("JS Script Execute Result :%d", result);
    //調用js方法

    Local<String> process_name =
            String::NewFromUtf8(v8Helper::GetIsolate(), "initView", NewStringType::kNormal)
                    .ToLocalChecked();
    Local<Value> process_val;
    // If there is no Process function, or if it is not a function,
    if (!context->Global()->Get(context, process_name).ToLocal(&process_val) ||
        !process_val->IsFunction()) {
        LOGI("initView is not a function\n");
    }
    // It is a function; cast it to a Function
    Local<Function> process_fun = Local<Function>::Cast(process_val);
    process_.Reset(v8Helper::GetIsolate(),process_fun);
              
    ```
    ```JavaScript
     function initView(person){
        
     }

    ```

不管是js調用C++的方法,還是C++調用js的方法,對這些方法的包裝都應該是v8初始化完成后最先操作的。在 ``Local<Context> context =creatTestContext(v8Helper::GetIsolate()); ``之前的操作都是v8初始化常規代碼我做了一些簡單的封裝,這個方法主要是來綁定C++暴露給js接口的所以暫時不討論。注意  ``context_.Reset(GetIsolate(), context); ``這一句是用來保存當前上下文句柄的, ``process_.Reset(GetIsolate(), process_fun); ``是用來保存在js找出來的方法的句柄的,這兩句是我們在以后的任何時候都可以調用js方法的關鍵。中間的一些代碼都很常規,就是找出js中的方法名然后轉成方法。應該注意到找出js方法的操作是在腳本加載並且執行完之后進行的,這是因為如果在加載js腳本之前找js的方法是肯定找不到的。context_是Global<Context>類型,process_是Global<Function>類型,這兩個全局類型的對象其實是用來保存當前的上下文環境和需要以后來執行的方法的句柄的。以后我們我們可以通過這兩個句柄,進入到相應上下文環境中執行相應的方法。接下來看一下,我們是怎么在C++中調用在js中找出來的這個方法的,因為我們需要在onDrawFrame()的jni方法中執行繪制代碼所以在onDrawFrame()的jni方法中要這樣來調用:

    ``` java
        // Create a handle scope to keep the temporary object references.
    HandleScope handle_scope(v8Helper::GetIsolate());

    v8::Local<v8::Context> context =
            v8::Local<v8::Context>::New(v8Helper::GetIsolate(), context_);

    // Enter this processor's context so all the remaining operations
    // take place there
    Context::Scope context_scope(context);

    // Set up an exception handler before calling the Process function
    TryCatch try_catch(v8Helper::GetIsolate());

    const int argc = 0;
    Local<Value> argv[argc] = {};
    v8::Local<v8::Function> process =
            v8::Local<v8::Function>::New(v8Helper::GetIsolate(), process_);
    Local<Value> result;
    if (!process->Call(context, context->Global(), argc, argv).ToLocal(&result)) {
        String::Utf8Value error(v8Helper::GetIsolate(), try_catch.Exception());
        LOGI("call js function error:%s",*error);
    }
      
    ```

首先是創建一棧區域來保存當前臨時對象引用。
紅框內的方法就是把之前保存在 ``context_ ``,和 ``process_ ``對象中的句柄拿出來,先進入相應的上下文,然后在對應的上下文環境中拿到對應的方法來執行。基本C++調用js方法都是這個套路,這里argc為0是調用無參的js方法,調用有參的js方法,我還沒試不過按照process.cc中的套路也應該沒什么問題。
##js調用C++的方法:##

js調用C++的方法就比較容易了

    ```java
    v8Helper::Initialize();
    Isolate::Scope isolate_scope(v8Helper::GetIsolate());
    // Create a stack-allocated handle scope.
    HandleScope handle_scope(v8Helper::GetIsolate());
    // Create a new context.
    Local<Context> context =creatTestContext(v8Helper::GetIsolate());
    context_.Reset(v8Helper::GetIsolate(),context);
    if (context.IsEmpty())
    {
        LOGI("Error creating context\n");
    }
    // Enter the context for compiling and running the hello world script.
    Context::Scope context_scope(context);
    // Create a string containing the JavaScript source code.
    bool result =   v8Helper::ExecuteString(context->GetIsolate(),
                                            String::NewFromUtf8(context->GetIsolate(), jsSource,
                                                                NewStringType::kNormal).ToLocalChecked(),true, true);
    LOGI("JS Script Execute Result :%d", result);
    ```

可以看到在創建一個新的上下文環境的時候這里直接creatTestContext(v8Helper::GetIsolate());其實是做了兩個操作如下

    ```java
     // Creates a new execution environment containing the built-in functions.
   
    v8::Local<v8::Context> CreateShellContext(v8::Isolate* isolate) {
     // Create a template for the global object.
    v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);
    // Bind the global 'print' function to the C++ Print callback.
    global->Set(
      v8::String::NewFromUtf8(isolate, "print", v8::NewStringType::kNormal)
          .ToLocalChecked(),
      v8::FunctionTemplate::New(isolate, Print))

    // Bind the global 'read' function to the C++ Read callback.
    global->Set(v8::String::NewFromUtf8(
                  isolate, "read", v8::NewStringType::kNormal).ToLocalChecked(),
              v8::FunctionTemplate::New(isolate, Read));
    
     }
    ```

    ```JavaScript
      var x = [6, 8, 'JS', 6*8];
      print(x);
      
    ``


首先創建一個全局對象的模板然后綁定C++的方法到字符串上,js就可以用此字符串來調用綁定的方法。應該注意到這些綁定在執行js字符串之前就已經進行了。
##js調C++類##
這里有兩種情況:

1. 在C++中創建類的對象,然后把對象作為參數傳遞給js,在js中訪問對象的屬性和方法。
2. 在js中創建對象,在JS中訪問對象的屬性和方法。
 
第一種情況:
這種情況下與調用C++普通方法的區別是js在調用C++方法的時候多了一個對象參數,由於這個參數是個C++類的對象,所以我們需要把這個C++對象封裝成js對象。

    ```java
     // Create a handle scope to keep the temporary object references.
    HandleScope handle_scope(v8Helper::GetIsolate());

    v8::Local<v8::Context> context =
            v8::Local<v8::Context>::New(v8Helper::GetIsolate(), context_);

    // Enter this processor's context so all the remaining operations
    // take place there
    Context::Scope context_scope(context);

    Person person("SexMonkey",18);
    Local<Object> person_object = WrapPerson(&person);

    // Set up an exception handler before calling the Process function
    TryCatch try_catch(v8Helper::GetIsolate());

    const int argc = 1;
    Local<Value> argv[argc] = {person_object};
    v8::Local<v8::Function> process =
            v8::Local<v8::Function>::New(v8Helper::GetIsolate(), process_);
    Local<Value> result;
    if (!process->Call(context, context->Global(), argc, argv).ToLocal(&result)) {
        String::Utf8Value error(v8Helper::GetIsolate(), try_catch.Exception());
        LOGI("call js function error:%s",*error);
    }
    ```
****
    ```java
     Local<Object> WrapPerson(Person *person) {
    // Local scope for temporary handles.
    EscapableHandleScope handle_scope(v8Helper::GetIsolate());
    // Fetch the template for creating JavaScript person wrappers.
    // It only has to be created once, which we do on demand.
    if (person_template_.IsEmpty()) {
        Local<ObjectTemplate> raw_template = MakePersonTemplate(v8Helper::GetIsolate());
        person_template_.Reset(v8Helper::GetIsolate(), raw_template);
    }
    Local<ObjectTemplate> temp = Local<ObjectTemplate>::New(v8Helper::GetIsolate(),person_template_);
    // Create an empty  person wrapper.
    Local<Object> result = temp -> NewInstance(v8Helper::GetIsolate() -> GetCurrentContext()).ToLocalChecked();
    // Wrap the raw C++ pointer in an External so it can be referenced
    // from within JavaScript.
    Local<External> person_ptr = External::New(v8Helper::GetIsolate(),person);

    // Store the person pointer in the JavaScript wrapper.
    result->SetInternalField(0, person_ptr);
    // Return the result through the current handle scope.  Since each
    // of these handles will go away when the handle scope is deleted
    // we need to call Close to let one, the result, escape into the
    // outer handle scope.
    return handle_scope.Escape(result);
    }
    ```

****

    ```java

    Local<ObjectTemplate> MakePersonTemplate(Isolate *isolate) {

    EscapableHandleScope handle_scope(isolate);

    Local<ObjectTemplate> result = ObjectTemplate::New(isolate);
    result->SetInternalFieldCount(1);

    // Add accessors for each of the fields of the request.
    result->SetAccessor(
            String::NewFromUtf8(isolate, "name", NewStringType::kInternalized)
                    .ToLocalChecked(),
           GetName);
    result->SetAccessor(
            String::NewFromUtf8(isolate, "age", NewStringType::kInternalized)
                    .ToLocalChecked(),
            GetAge);

    result->Set(String::NewFromUtf8(isolate, "setName", NewStringType::kNormal)
                        .ToLocalChecked(),
                FunctionTemplate::New(isolate, SetName));
    result->Set(String::NewFromUtf8(isolate, "setAge", NewStringType::kNormal)
                        .ToLocalChecked(),
                FunctionTemplate::New(isolate, SetAge));

    // Again, return the result through the current handle scope.
    return handle_scope.Escape(result);
    }

    ```

****
    ```java

    void GetName(Local<String> name, const PropertyCallbackInfo<Value>& info) {
    // Extract the C++ request object from the JavaScript wrapper.
    Person* person = UnwrapPerson(info.Holder());

    // Fetch the path.
    const std::string& cName = person -> getName();

    // Wrap the result in a JavaScript string and return it.
    info.GetReturnValue().Set(
            String::NewFromUtf8(info.GetIsolate(), cName.c_str(),
                                NewStringType::kNormal,
                                static_cast<int>(cName.length())).ToLocalChecked());
    }
    ```

****
    ```java
    void SetAge(const FunctionCallbackInfo <Value> &args)
    {
    LOGI("setAge is called");
    Person* person = UnwrapPerson(args.Holder());
    person -> setAge(args[0]->Uint32Value());

    }
    ```

****
    ```java
    Person* UnwrapPerson(Local<Object> obj) {
    Local<External> field = Local<External>::Cast(obj->GetInternalField(0));
    void* ptr = field->Value();
    return static_cast<Person*>(ptr);
    }
    ```

    ```JavaScript
    function initView(person){
       person.setName("JerryZhu");
       person.setAge(25);
       person.name;
    }
    ```
仔細看一下其實套路都是一樣的,只不過是多了幾步針對C++對象封裝的專有步驟而已。主要的步驟就是先創建一個對象模板為這個對象模板綁定暴露給js訪問C++對象的屬性和方法的接口,注意對訪問屬性和訪問方法的封裝是不一樣的,雖然這些套路都是固定的但也要注意其中的區別。

以我傳遞的person對象為例,當我在js中用person.name來訪問person的name屬性的時候,v8實際上就會調用GetName(Local<String> name, const PropertyCallbackInfo<Value>& info)方法,通過回調的info參數來解包裝,轉成你傳遞的對像類型的指針后,再用這個指針來訪問對象的成員方法getName()來獲取name的值,然后再設置成返回值其實也就是person.name的值。訪問方法也一樣,比如在js中訪問person.setName("123"),v8會調用SetName(const FunctionCallbackInfo <Value> &args);也是先解包裝轉換成你傳遞的對像類型的指針后再用對象的成員方法setName(*str(args.GetIsolate(),args[0]));通過v8回調給C++的參數來改變對象的屬性值。注意以上person.name,person.setName("123"),name和setName都是你綁定對象模板時暴露給js接口的字符串,person也是你自己在js中使用的一個字符而已,你也可以用teacher,teacher.name。在模板創建完成后又經過了幾個步驟主要是對剛才創建的模板與C++對象的關聯。這些步驟完成后,一個C++對象就封裝成為js對象了,然后把這個對象當做參數傳給js,js就可以訪問之前創建的模板上綁定的方法了。

第二種情況:
在js中創建C++的對象去訪問對象的屬性和方法,重點是對構造函數的綁定,綁定的時機與一般函數即全局函數一樣,在js文件加載之前就可綁定,注意是在creatTestContext(Isolate *isolate)這個方法中進行綁定。

    ```java

    Local<Context> creatTestContext(Isolate *isolate) {
    Local<ObjectTemplate> global = ObjectTemplate::New(isolate);
    //Create the function template for the constructor, and point it to our constructor,
    Handle<FunctionTemplate> person_template = FunctionTemplate::New(v8Helper::GetIsolate(),PersonConstructor);
    //We can tell the scripts what type to report. In this case, just Person will do.
    person_template->SetClassName(String::NewFromUtf8(v8Helper::GetIsolate(), "Person", NewStringType::kNormal)
                                           .ToLocalChecked());
    //This template is the unique properties of this type, we can set
    //functions, getters, setters, and values directly on each new Person() object.
    Handle<ObjectTemplate> person = person_template -> InstanceTemplate();
    //Again, this is to store the c++ object for use in callbacks.
    person -> SetInternalFieldCount(1);
    person -> SetAccessor(
            String::NewFromUtf8(isolate, "name", NewStringType::kInternalized)
                    .ToLocalChecked(),
            GetName);
    person -> SetAccessor(
            String::NewFromUtf8(isolate, "age", NewStringType::kInternalized)
                    .ToLocalChecked(),
            GetAge);

    person -> Set(String::NewFromUtf8(isolate, "setName", NewStringType::kNormal)
                        .ToLocalChecked(),
                FunctionTemplate::New(isolate, SetName));
    person -> Set(String::NewFromUtf8(isolate, "setAge", NewStringType::kNormal)
                        .ToLocalChecked(),
                FunctionTemplate::New(isolate, SetAge));

    //Finally, we can tell the global scope that it now has a 'function' called Person,
    //and that this function returns the template object above.
    global -> Set(String::NewFromUtf8(isolate, "Person", NewStringType::kNormal)
                          .ToLocalChecked(),person_template);

    return Context::New(isolate,0,global);
    }

    ```
****

    ```java 

    void  PersonConstructor(const FunctionCallbackInfo <Value>& args){
    LOGI("PersonConstructor is called");
    if (!args.IsConstructCall())
    {
        LOGI("args is not PersonConstructor call");
    }

    Handle<Object> object = args.This();
    HandleScope handle_scope(v8Helper::GetIsolate());
    String::Utf8Value str(args.GetIsolate(),args[0]);
    std::string name = *str;
    int age = args[1] -> Uint32Value();
    Person *person =  new Person(name,age);

    //Note that this index 0 is the internal field we created in the template!
    object -> SetInternalField(0,v8::External::New(v8Helper::GetIsolate(),person));
    }

    ```JavaScript
    var per = new Person("JIMI",20);
    print(per.name,per.age);
 
    ```

在creatTestContext(Isolate *isolate)中先是為構造函數創建函數模板,並將其指向我們的構造函數,SetClassName是告訴js腳本C++對象的類型,就是來區分普通字符串和C++類的,InstanceTemplate()是獲取C++實例模板,接下來就是為js中創建C++對象訪問其屬性和方法綁定的C++接口,與我們傳遞C++對象給js函數然后用對象訪問屬性和方法的綁定過程是一樣的,最后一步是設置person(name,age)這個全局函數給js調用;
構造函數的綁定需要注意一下,回調參數args數組索引對應着是初始化對象時的屬性順序,拿到屬性值后,用這些屬性值在C++中創建一個類的對象,然后再把對象指針設置到索引為0的地方,v8會在js中C++對象調用其屬性和方法時從這個地方查詢到真正的C++對象。

##C++調js類##
C++調用調用js的類與C++調用js方法有些許類似,都是在腳本加載並運行之后進行的。看一下代碼會發現調用過程有點復雜,但基本套路都是一樣的。

    ```java 
    v8Helper::Initialize();
    Isolate::Scope isolate_scope(v8Helper::GetIsolate());
    // Create a stack-allocated handle scope.
    HandleScope handle_scope(v8Helper::GetIsolate());
    // Create a new context.
    Local<Context> context =creatTestContext(v8Helper::GetIsolate());
    context_.Reset(v8Helper::GetIsolate(),context);
    if (context.IsEmpty())
    {
        LOGI("Error creating context\n");
    }

    // Enter the context for compiling and running the hello world script.
    Context::Scope context_scope(context);
    // Create a string containing the JavaScript source code.
    bool result =   v8Helper::ExecuteString(context->GetIsolate(),
                                            String::NewFromUtf8(context->GetIsolate(), jsSource,NewStringType::kNormal).ToLocalChecked(),true, true);

    
    LOGI("JS Script Execute Result :%d", result);

    /*C++調用js類 start..*/
    Local<String> js_data = String::NewFromUtf8(v8Helper::GetIsolate(), "Point", NewStringType::kInternalized)
            .ToLocalChecked();
    Local<Value> js_data_value = context -> Global() -> Get(js_data);
    String::Utf8Value str(js_data_value);
    LOGI("Point = %s \n",*str);
    bool  isFunction = js_data_value -> IsFunction();
    LOGI("Point is function %d",isFunction);
    bool  isObject = js_data_value -> IsObject();
    LOGI("Point is object %d",isObject);
    Local<Object> js_data_object = Local<Object>::Cast(js_data_value);
    // var newObj = new Point(1,2);
    const int argc = 2;
    Local<Value> argv[argc] = {};
    argv[0] = Int32::New(v8Helper::GetIsolate(),7);
    argv[1] = Int32::New(v8Helper::GetIsolate(),8);
    Local<Value> newObject = js_data_object -> CallAsConstructor(context, argc, argv).ToLocalChecked();
    LOGI("Point is function %d \n",newObject -> IsFunction());
    LOGI("Point is object %d",newObject -> IsObject());
    // newObj.show();
    Local<Object> obj = Local<Object>::Cast(newObject);
    Local<String> js_func_name = String::NewFromUtf8(v8Helper::GetIsolate(), "show", NewStringType::kInternalized).ToLocalChecked();
    Local<Value>  js_func_ref = obj->Get(js_func_name);
    Local<Function> js_func = Local<Function>::Cast(js_func_ref);
    js_data_value = js_func->Call(obj, 0, NULL) ;

    String::Utf8Value str2(js_data_value);
    LOGI("Point = %s \n",*str2);

    /*C++調用js類 end..*/

    //調用js方法
    Local<String> process_name =
            String::NewFromUtf8(v8Helper::GetIsolate(), "initView", NewStringType::kNormal)
                    .ToLocalChecked();
    Local<Value> process_val;
    // If there is no Process function, or if it is not a function,
    if (!context->Global()->Get(context, process_name).ToLocal(&process_val) ||
        !process_val->IsFunction()) {
        LOGI("initView is not a function\n");
    }
    // It is a function; cast it to a Function
    Local<Function> process_fun = Local<Function>::Cast(process_val);
    process_.Reset(v8Helper::GetIsolate(),process_fun);

        
    ```
    ```JavaScript
     function Point(x,y){
        this.x=x;
        this.y=y;
    }
    Point.prototype.show=function(){
       return '(x,y) = '+this.x+','+this.y;
    }
 
    ```

首先將想要調用的js類的類名以字符串的形式轉成v8能識別的字符串,然后``context -> Global() -> Get(js_data)``, v8開始通過這個類名在js中找到相應的值,然后轉通過相應的方法來先看看這個找出來的值是方法還是類,我們可以在腳本中看到這個值既是類又是方法,這其實是個構造方法,然后``Local<Object>::Cast(js_data_value);``將這個值轉化成了js的類,接着``js_data_object -> CallAsConstructor(context, argc, argv).ToLocalChecked();``,是調用js類的構造方法並做一些屬性的初始化操作,返回的就是js類的對象了,接下來就是用這個對象來調用js類的方法了,與之前C++調用js全局方法的方法是一樣的。注意prototype是JavaScript類的一個關鍵字它可以指定類的屬性,腳本中是把show()方法當做類的方法。可以用類的對象調用這個方法。



注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com