zoukankan      html  css  js  c++  java
  • JavaScript引擎研究与C、C++与互调用(转)

    本文转自:ice6015的专栏。为什么有些招聘需要熟悉JS和C++,这或许就是原因。

    1.  概要

    JavaScript是一种广泛用于Web客户端开发脚本语言,常用来控制浏览器的DOM树,给HTML网页添加动态功能。目前JavaScript遵循的web标准的是ECMAScript262。由于JavaScript提供了丰富的内置函数、良好的对象机制。所以JavaScript还可以嵌入到某一种宿主语言中,弥补宿主语言的表现力,从而实现快速、灵活、可定制的开发。

    现有的主流浏览器基本上都实现了一个自己的JavaScript引擎。这些JavaScript引擎可以分析、编译和执行JavaScript脚本。这些JavaScript引擎都是用C或者C++语言写的,都对外提供了API接口。所以在C、C++语言中使用这些JavaScript引擎,嵌入JavaScript是非常方便的。有一些著名的开源项目都使用了这一种方式,来进行混合的编程,比如Node.js, K-3D等。

    已知著名的JavaScript引擎有Google的V8引擎、IE的Trident引擎、Firefox的SpiderMonkey引擎、Webkit的JavaScriptCore引擎、Opera的Carakan引擎(非开源的,本文没有分析)等。这些JavaScript引擎对外提供的API接口在细节上各不相同,但是这些API的一个基本的设计思路都类似。C、C++要使用这些引擎,首先要获得一个全局的Global对象。这个全局的Global对象有属性、方法、事件。比如在JavaScript环境中有一个window窗口对象。它描述的是一个浏览器窗口。一般JavaScript要引用它的属性和方法时,不需要用“window.xxx”这种形式,而直接使用“xxx”。 它是JavaScript中最大的对象,所有的其他JavaScript对象、函数或者是它的子对象,或者是子对象的子对象。C、C++通过对这个最大的Global对象调用get、set操作就可以实现与JavaScript进行双向交互了。

    下面的介绍涉及到比较多的代码细节,先给个结论吧,不想看C++细节代码可以不看了。

     

    编写语言

    API接口

    C、C++与JavaScript交互(变量、函数、类)

    windows xp

    vc2005编译

    静态库的大小

    示例EXE的大小

    执行、解析JavaScript的速度

    Google V8

    C++

    C++

    可以

    23.1M

    1.1M

    最快

    Firefox3.5以前 SpiderMonkey

    C

    C

    可以

    1.3M

    500K

    Firefox高版本SpiderMonkey

    C++

    C

    可以

    15.3M

    1.7M

    一般

    Webkit  JavaScriptCore

    C++

    C

    可以

    26.2M

    1.4M

    一般

    IE

    未知

    COM

    可以

    未知

    100K(没有链接库)

    一般

    如果优先考虑库的体积,建议使用Firefox的老版本。对执行效率有要求的话,建议使用V8。

    2.  Google V8

    2.1. 介绍

    Google Chrome是google 2008年9月发布的浏览器,Chrome的网页渲染部分使用的是Webkit的渲染引擎,Chrome的JavaScript引擎就是大名鼎鼎的V8了。V8是C++语言编写的,是开放源码的,是所有的JavaScript引擎中速度最块的。其开源项目地址为:http://code.google.com/p/v8

    V8对外的API接口是C++的接口。V8的API定义了几个基本概念:句柄(handle),作用域(scope),上下文环境(Context)。模板(Templates),了解这些基本的概念才可以使用V8。

    l  上下文环境Context就是脚本的运行环境,JavaScript的变量、函数等都存在于上下文环境Context中。Context可以嵌套,即当前函数有一个Context,调用其它函数时如果又有一个Context,则在被调用的函数中javascript是以最近的Context为准的,当退出这个函数时,又恢复到了原来的Context。

    l  句柄(handle)就是一个指向V8对象的指针,有点像C++的智能指针。所有的v8对象必须使用句柄来操作。没有句柄指向的V8对象,很快会被垃圾回收器回收了。

    l  作用域(scope)是句柄的容器,一个作用域(scope)可以有很多句柄(handle)。当离开一个作用域(scope)时,所有在作用域(scope)里的句柄(handle)都会被释放了。

    l  模板(Templates)分为函数模板和对象模板,是V8对JavaScript的函数和对象的封装。方便C++语言操作JavaScript的函数和对象。

    l  V8 API定义了一组类或者模板,用来与JavaScript的语言概念一一对应。比如:

    V8的 Function模板与JavaScript的函数对应

    V8的Object类与JavaScript的对象对应

    V8的String类与JavaScript的字符对应

    V8的Script类与JavaScript的脚本文本对应,它可以编译并执行一段脚本。

    2.2. C++调用JavaScript

    使用V8,在C++中访问Javascript脚本中的内容,首先要调用Context::GetCurrent()->Global()获取到Global全局对象,再通过Global全局对象的Get函数来提取Javascript的全局变量、全局函数、全局复杂对象。C++代码示例如下:

    //获取Global对象

    Handle<Object>globalObj = Context::GetCurrent()->Global();

     

    //获取Javascrip全局变量

    Handle<Value>value = globalObj->Get(String::New("JavaScript变量名"));

    intn = value ->ToInt32()->Value();

     

    //获取Javascrip全局函数,并调用全局函数

    Handle<Value>value = globalObj->Get(String::New("JavaScript函数名"));

    Handle<Function> func = Handle<Function>::Cast(value) ;//转换为函数

    Local<Value> v1 = Int32::New(0);
        Local<Value> v2 = Int32::New(1);

    Handle<Value> args[2] = { v1, v2 }; //函数参数

    func->Call(globalObj, 2, args);

    //获取Javascrip全局对象,并调用对象的函数

    Handle<Value>value = globalObj->Get(String::New("JavaScript复杂对象名"));

    Handle<Object> obj = Handle<Object>::Cast(value)//转换为复杂对象

    Handle<Value> objFunc = obj ->Get(String::New("JavaScript对象函数名"));

    Handle<Value> args[] = {String::New("callobject function ")};//函数参数

    objFunc->Call(globalObj, 1, args);

    2.3. JavaScript调用C++

    使用V8,在Javascript脚本中想要访问C++中的内容,必须先将C++的变量、函数、类注入到Javacsript中。注入时,首先要调用Context::GetCurrent()->Global()获取到Global对象,再通过Global对象的Set函数来注入全局变量、全局函数、类对象。

    全局变量、全局函数的注入过程与上一节的代码类似,这里就省略不写了。比较麻烦的是将C++的类、类对象注入到Javascript中。其基本的过程如下:

    1.   首先定义一个C++类,定义一个类对象指针

    2.  定义一组C++全局函数,封装V8对C++类的调用,提供给V8进行CALLBACK回调。

    3.   最后调用V8 API,将定义的C++类和C++函数注入到Javascript中

     

    在V8的API接口中,还提供了一个“内部数据”(Internal Field)的概念,“内部数据”就是允许V8对象保存一个C++的void*指针。当V8回调C++全局函数时,C++可以设置或者获取该void*指针。

     

    下面是一个将C++类、类变量注入到Javascript中的C++代码示例。

    //一个C++类test、

    class test

    {

    public:

        test(){number=0;};

        voidfunc(){number++;}

        int number;

    };

    //一个全局对象g_test

    //目的是:在Javascript中可以直接使用这个对象,例如g_test.func()

    test g_test;

     

    //封装V8调用test类构造函数

    //在Javascript中如果执行:var t = new test;V8就会调用这个C++函数

    //在C++中执行NewInstance函数注入对象时,也会调用这个函数

    //默认的test类的构造函数没有参数,C++注入对象时,提供一个额外的参数

    v8::Handle<v8::Value> testConstructor(constv8::Arguments& args)

    {

        v8::Local<v8::Object>self = args.Holder();

        //这里假定有两个“内部数据”(Internal Field)

        //第一个“内部数据”保存test对象的指针

        //第二个“内部数据”为1 就表示这个对象是由C++注入的

        //第二个“内部数据”为0 就表示这个对象是JS中自己建立的

        if(args.Length())

        {

            //默认为0,当C++注入对象时,会填充这个“内部数据”

            self->SetInternalField(0,v8::External::New(0));

            self->SetInternalField(1,v8::Int32::New(1));

        }

        else

        {

            self->SetInternalField(0,v8::External::New(new test));

            self->SetInternalField(1,v8::Int32::New(0));

        }

        return self;

    }

     

    //封装V8调用test类func方法

    //在Javascript中如果执行:t.func();V8就会调用这个C++函数

    v8::Handle<v8::Value> testFunc(constv8::Arguments& args)

    {

        //获取构造函数testConstructor时,设置的对象指针

        v8::Local<v8::Object>self = args.Holder();

    v8::Local<v8::External> wrap =v8::Local<v8::External>::Cast(self->GetInternalField(0));

        void* ptr =wrap->Value();

        //调用类方法

        static_cast<test*>(ptr)->func();

        returnv8::Undefined();

    }

     

    //封装V8调用test类成员变量number

    //在Javascript中如果执行:t.number;V8就会调用这个C++函数

    v8::Handle<v8::Value>getTestNumber(v8::Local<v8::String> property, const v8::AccessorInfo& info)

    {

        //获取构造函数testConstructor时,设置的对象指针

        v8::Local<v8::Object>self = info.Holder();

    v8::Local<v8::External> wrap =v8::Local<v8::External>::Cast(self->GetInternalField(0));

        void* ptr =wrap->Value();

        //返回类变量

        returnv8::Int32::New(static_cast<test*>(ptr)->number);

    }

     

    //C++类和全局的函数定义好以后,就可以开始将类、变量注入V8 Javascript中了

    //获取global对象

    v8::Handle<v8::Object> globalObj =context->Global();

     

    //新建一个函数模板,testConstructor是上面定义的全局函数

    v8::Handle<v8::FunctionTemplate> test_templ =v8::FunctionTemplate::New(testConstructor);

     

    //设置类名称

    test_templ->SetClassName(v8::String::New("test"));

     

    //获取Prototype,

    v8::Handle<v8::ObjectTemplate> test_proto =test_templ->PrototypeTemplate();

     

    //增加类成员函数,testFunc是上面定义的全局函数

    test_proto->Set("func",v8::FunctionTemplate::New(testFunc));

     

    //设置两个内部数据,用于构造函数testConstructor时,存放类对象的指针。

    v8::Handle<v8::ObjectTemplate> test_inst =test_templ->InstanceTemplate();

    test_inst->SetInternalFieldCount(2)

     

    //增加类成员变量

    //getTestNumber是上面定义的全局函数

    //只提供了成员变量的get操作,最后一个参数是成员变量的set操作,这里省略了

    test_inst->SetAccessor(v8::String::New("number"),getTestNumber, 0);

     

    //将test类的定义注入到Javascript中

    v8::Handle<v8::Function> point_ctor =test_templ->GetFunction();

    globalObj->Set(v8::String::New("test"),point_ctor);

     

    //新建一个test对象,并使得g_test绑定新建的对象

    v8::Handle<v8::Value> flag = v8::Int32::New(1);

    v8::Handle<v8::Object> obj =point_ctor->NewInstance(1, &flag);

    obj->SetInternalField(0, v8::External::New(&g_test));

    globalObj->Set(v8::String::New("g_test"), obj);

     

    将C++类和类指针注入到V8 JavaScript后,在JavaScript中就可以这样使用了:

    g_test.func();

    var n = g_test.number;

    var t = new test;

     

    3.  Firefox SpiderMonkey

    3.1. 介绍

    Firefox的JavaScript引擎是SpiderMonkey,SpiderMonkey包括解析器、编译器和JIT即时编译器。JIT即时编译器是JavaScript引擎的核心部分,它决定了一个JavaScript引擎的效率和速度。Firefox的JIT即时编译器有很多个版本,最早的JIT名称是TraceMonkey,现在使用的是JägerMonkey,未来准备开发的是IonMonkey。在一些通用的JavaScript测试标准(比如SunSpider)中,Firefox的JavaScript引擎表现都不好,比IE、V8的执行速度差。Firefox的JS引擎源码地址:http://ftp.mozilla.org/pub/mozilla.org/js/

    SpiderMonkey对外的API接口是C语言的。与WebkitJavaScriptCore的API比较类似,SpiderMonkeyAPI的主要数据结构有:

    JSRuntime:JSRuntime是内存空间,在执行JS函数或脚本之前,首先要调用JS_NewRunTime来初始化一个JSRuntime,JS_NewRunTime只有一个参数,就是内存的大小,当JS引擎使用的内存超出了指定的大小,垃圾回收器就会启动运行。

    JSContext:JavaScript全局上下文。也就是JavaScript的执行环境。

    jsval:JavaScript中的变量

    JSObject:JavaScript中的对象

    JSFunction:JavaScript中的函数

    JSString:JavaScript中的字符

    SpiderMonkey API的主要函数有:

    l  JS_NewRuntime JS_DestroyRuntime:新建和销毁JSRuntime

    l  JS_NewContext JS_DestroyContext:新建和销毁JSContext

    l  JS_NewObject:新建一个对象

    l  JS_SetGlobalObject JS_GetGlobalObject:设置和获取全局对象

    l  JS_GetProperty JS_SetProperty:JavaScript对象的属性操作

    l  JS_CallFunctionName:调用JavaScript函数

    l  JS_DefineFunctions:定义一组JavaScript函数

    l  JS_InitClass:定义一个JavaScript

    l  JS_ExecuteScript:执行JavaScript脚本

     

    SpiderMonkey API还定义了一些宏,用来在jsval与C++类型之间装换

    l  JSVAL_TO_OBJECT  JSVAL_TO_STRING JSVAL_TO_INT:将jsval装换为C++类型

    l  OBJECT_TO_JSVAL STRING_TO_JSVAL JSVAL_TO_INT:将C++类型装换为jsval

    3.2. C++调用JavaScript

    使用SpiderMonkey,在C++中访问Javascript脚本中的内容,首先要调用JS_GetGlobalObject获取到Global全局对象,再调用JS_GetProperty函数来提取Javascript的全局变量、全局函数、全局复杂对象。示例如下:

    JSRuntime *rt;

        JSContext*cx;

    JSObject *glob;

    //新建JSRuntime

    rt = JS_NewRuntime(64L * 1024L* 1024L);

    //新建JavaScript全局上下文

    cx = JS_NewContext(rt,gStackChunkSize);

    //新建JavaScript全局Global对象

    glob = JS_NewObject(cx,&global_classNULL,NULL);

    JS_SetGlobalObject(cxglob);

    //获取JavaScript的全局变量

    jsval var;

    JSBool success = JS_GetProperty(cx, glob, "JavaScript全局变量名", &var);

    long value = JSVAL_TO_INT(var);

     

    //调用全局函数

    jsval args[1]; //函数参数

    arg[0] = INT_TO_JSVAL(1);

    jsval ret;

    success= JS_CallFunctionName(cx,glob, "JavaScript全局函数名", 1, args, &ret);

    //处理返回值

    long value = JSVAL_TO_INT(ret);

     

    //获取JS复杂对象

    sval object;

        success = JS_GetProperty(cx,glob"JS复杂对象名", & object);

        JSObjectobj = JSVAL_TO_OBJECT(var);

    jsval retValue;

    //调用对象的方法,这里省略了参数和返回值的处理

    success = JS_CallFunctionName(cx,obj"JS复杂对象的方法名", 0, 0, &retValue);

    3.3. JavaScript调用C++

    在Javascript脚本中想要访问C++中的内容,必须先将C++的变量、函数、类注入到Javacsript中。注入时,首先要调用JS_GetGlobalObjec获取到Global全局对象,调用JS_SetProperty函数来注入全局变量,调用JS_DefineFunctions注入全局函数、调用JS_InitClass注入类。

    全局变量注入过程与上一节的代码类似,这里就省略不写了。

    注入全局函数的示例:

    //C++全局函数,功能:将传入的两个参数相加

    //cx 上下文,obj 目标对象,argc 参数个数,argv参数,rval返回值

    JSBool Add (JSContext *cx, JSObject *obj, uintN argc, jsval*argv, jsval *rval){

        long value = JSVAL_TO_INT(argv[0]) + JSVAL_TO_INT(argv[1);

        *rval = INT_TO_JSVAL(value);

        return JS_TRUE;

    }

    //需要注入到JS中的函数,可以有多个,最后一个必须是{0}

    static JSFunctionSpec functions[] =

    {

    {" Add ", Add, 0},

            {0}

    };

    //注入C++全局函数函数

    JS_DefineFunctions(cx, glob, functions);

    函数注入后,在JavaScript中可以直接调用这个函数,例如:

    var n = Add(100, 100);

    使用SpiderMonkey注入C++类的过程和使用V8注入C++类的过程类似。SpiderMonkey的API也提供了一个类似于V8的“内部数据”的结构。可以给某个JavaScript对象指定一个void*数据,当SpiderMonkey CallBack回调C++时,就可以获取该void*数据进行操作了。

    下面是一个示例代码:

    //C++ 类定义

    class test

    {         

    public:

        test(){number=0;};

        voidfunc(){number++;}

        int number;

    };

    test g_test;//变量定义

    //定义一个结构,表示对象的“内部数据”

    struct testPrivate

    {

        test* t;    //test指针

        bool externObject;//是否是C++注入的对象

    };

    //test类构造函数

    //在Javascript中如果执行:var t = new test;V8就会CallBack调用这个C++函数

    //在C++中执行NewInstance函数注入对象时,也会调用这个函数

    //默认的test类的构造函数没有参数,C++注入对象时,提供一个额外的参数,这个参数就是C++类的指针

    JSBool testConstructor(JSContext *cx, JSObject *obj, uintNargc, jsval *argv, jsval *rval)

    {

        testPrivate* tp =new testPrivate;

        if(argc)

        {

            tp->t =(test*)JSVAL_TO_PRIVATE(argv[0]);//注入的对象

            tp->externObject= true;

        }

        else

        {

            tp->t = newtest;//不是注入的对象就新建一个

            tp->externObject= false;

        }

        //设置“内部数据”

        if ( !JS_SetPrivate(cx, obj, tp) )

            return JS_FALSE;

        *rval =OBJECT_TO_JSVAL(obj);

        return JS_TRUE;

    }

    //test类析构造函数,JavaScript垃圾回收时,会CallBack调用这个函数

    void testDestructor(JSContext *cx, JSObject *obj)

    {

        //获取设置“内部数据

        testPrivate* tp =(testPrivate*)JS_GetPrivate(cx, obj);

        if(tp==NULL)

            return;

        if(!tp->externObject)//注入的对象,不删除

            delete tp->t;

        delete tp;

    }

    //封装test类func方法

    //在Javascript中如果执行:t.func();就会CallBack调用这个C++函数

    JSBool testFunc(JSContext *cx, JSObject *obj, uintN argc,jsval *argv, jsval *rval)

    {

        //获取设置“内部数据

        testPrivate* tp =(testPrivate*)JS_GetPrivate(cx, obj);

        tp->t->func();

        return JS_TRUE;

    }

    //定义一个枚举,每个枚举值表示一个test类的成员变量。

    enum

    {

        test_number,

    };

    //test类的成员变量,可以有多个

    JSPropertySpec testProperties[] =

    {

        {"number", test_number, JSPROP_ENUMERATE },

        { 0 }

    };

    //test类的成员变量的get操作

    //在Javascript中如果执行:var n= t.number; 就会调用这个C++函数

    JSBool testGetProperty(JSContext *cx, JSObject *obj, jsvalid, jsval *vp)

    {

        if(JSVAL_IS_INT(id))

        {

            testPrivate* tp= (testPrivate*)JS_GetPrivate(cx, obj);

            switch(JSVAL_TO_INT(id))

            {

            casetest_number:

                *vp =INT_TO_JSVAL(tp->t->number);

                break;

            }

        }

        return JS_TRUE;

    }

    //test类的成员变量的set操作

    //在Javascript中如果执行:t.number= 100; 就会调用这个C++函数

    JSBool testSetProperty(JSContext *cx, JSObject *obj, jsvalid, jsval *vp)

    {

        if(JSVAL_IS_INT(id))

        {

            testPrivate* tp= (testPrivate*)JS_GetPrivate(cx, obj);

            switch(JSVAL_TO_INT(id))

            {

            casetest_number:

                tp->t->number= JSVAL_TO_INT(*vp);

                break;

            }

        }

        return JS_TRUE;

    }

    //test类方法列表,可以有多个

    static JSFunctionSpec testMethods[] = {

        {"func",testFunc, 0},

        {0}

    };

    JSClass testClass =

    {

        "test",JSCLASS_HAS_PRIVATE,

        JS_PropertyStub,JS_PropertyStub,

        testGetProperty,testSetProperty,

        JS_EnumerateStub,JS_ResolveStub,

        JS_ConvertStub,testDestructor

    };

    //将test类定义注入JavaScript中

    JSObject *newTestObj = JS_InitClass(cx, glob, NULL,&testClass,

            testConstructor,0,

            NULL,testMethods,

            NULL, NULL);

    JS_DefineProperties(cx, newTestObj, testProperties);

    //在JavaScript脚本中注入一个新对象,并使这个新对象与g_test绑定

    jsval arg = PRIVATE_TO_JSVAL(&g_test);

    JSObject* gtestObj = JS_ConstructObjectWithArguments(cx,&testClass, NULL, NULL, 1, &arg);

    jsval vp = OBJECT_TO_JSVAL(gtestObj);

    JS_SetProperty(cx, glob, "g_test", &vp);

     

    将C++类和类指针注入到V8 JavaScript后,在JavaScript中就可以这样使用了:

    g_test.func();

    var n = g_test.number;

    var t = new test;

    4.  Webkit JavaScriptCore

    4.1. 介绍

    WebKit是一个开源浏览器引擎。很多浏览器都使用了WebKit浏览器引擎,比如苹果的Safari、Google的Chrome(只使用了排版和渲染部分)。WebKit包含一个网页排版渲染引擎WebCore和一个脚本引擎JavaScriptCore。JavaScriptCore引擎的API接口是C语言的API接口。关于JavaScriptCoreAPI的文档资料比较少,不过可以参考苹果公司的JSCocoa文档(基于Objective-C语言的)。苹果的Safari浏览器重写了JavaScriptCore,新的项目名称是SquirrelFish Extreme。SquirrelFish Extreme的对外API与JavaScriptCore是一样的。Webkit源码SVN地址:http://svn.webkit.org/repository/webkit/trunk

    JavaScriptCore API的主要数据结构有:

    JSGlobalContextRefJavaScript全局上下文。也就是JavaScript的执行环境。

    JSValueRefJavaScript的一个值,可以是变量、object、函数。

    JSObjectRefJavaScript的一个object或函数。

    JSStringRefJavaScript的一个字符串。

    JSClassRefJavaScript的类。

    l JSClassDefinitionJavaScript的类定义,使用这个结构,C、C++可以定义和注入JavaScript的类。

    JavaScriptCore API的主要函数有:

    JSGlobalContextCreateJSGlobalContextRelease:创建和销毁JavaScript全局上下文。

    JSContextGetGlobalObject:获取JavaScript的Global对象。

    JSObjectSetPropertyJSObjectGetPropertyJavaScript对象的属性操作。

    JSEvaluateScript:执行一段JS脚本。

    JSClassCreate:创建一个JavaScript类。

    JSObjectMake:创建一个JavaScript对象。

    JSObjectCallAsFunction:调用一个JavaScript函数。

    JSStringCreateWithUTF8CstringJSStringRelease:创建、销毁一个JavaScript字符串

    JSValueToBooleanJSValueToNumber JSValueToStringCopy:JSValueRef转为C++类型

    JSValueMakeBooleanJSValueMakeNumber JSValueMakeString:C++类型转为JSValueRef

    4.2. C++调用JavaScript

    使用JavaScriptCore,在C++中访问Javascript脚本中的内容,首先要调用JSContextGetGlobalObject获取到Global全局对象,再调用JSObjectGetProperty函数来提取Javascript的全局变量、全局函数、全局复杂对象。示例如下:

             //获取Global对象

        JSGlobalContextRef ctx = JSGlobalContextCreate(NULL);

        JSObjectRef globalObj = JSContextGetGlobalObject(ctx); 

    //获取全局变量

        JSStringRef varName = JSStringCreateWithUTF8CString("JavaScript变量名");

    JSValueRef var = JSObjectGetProperty(ctx, globalObj, varName,NULL); JSStringRelease(varName);

        //转化为C++类型

        int n = JSValueToNumber(ctx, var, NULL);

       

        //获取全局函数

    JSStringRef funcName = JSStringCreateWithUTF8CString("JavaScript函数名");

    JSValueRef func = JSObjectGetProperty(ctx, globalObj, funcName,NULL); JSStringRelease(funcName);

        //装换为函数对象

        JSObjectRef funcObject = JSValueToObject(ctx,func, NULL);

        //组织参数,将两个数值1和2作为两个参数

        JSValueRef args[2];

        args[0] = JSValueMakeNumber(ctx, 1);

        args[1] = JSValueMakeNumber(ctx, 2);

        //调用函数

    JSValueRef returnValue = JSObjectCallAsFunction(ctx, funcObject,NULL, 2, args, NULL);

    //处理返回值

        int ret = JSValueToNumber(ctx, returnValue, NULL);

       

        //获取复杂的对象

    JSStringRef objName=JSStringCreateWithUTF8CString("JavaScript复杂对象名");

    JSValueRef obj = JSObjectGetProperty(ctx, globalObj, objName,NULL); JSStringRelease(objName);

        //装换为对象

        JSObjectRef object = JSValueToObject(ctx,obj, NULL);

    //获取对象的方法

    JSStringRef funcObjName =JSStringCreateWithUTF8CString("JavaScript复杂对象的方法");

    JSValueRef objFunc = JSObjectGetProperty(ctx, object, funcObjName,NULL); JSStringRelease(funcObjName);

    //调用复杂对象的方法,这里省略了参数和返回值

    JSObjectCallAsFunction(ctx, objFunc, NULL, 0, 0, NULL);

    4.3. JavaScript调用C++

    在Javascript脚本中想要访问C++中的内容,必须先将C++的变量、函数、类注入到Javacsript中。注入时,首先要调用JSContextGetGlobalObject获取到Global全局对象,再调用JSObjectSetProperty函数来注入全局变量、全局函数、类对象。

    全局变量、全局函数的注入过程与上一节的代码类似,这里就省略不写了。

    将C++的类、类对象注入到Javascript中。其基本的过程如下:

    1.   首先定义一个C++类,

    2.  定义一组C++全局函数,封装JavaScriptCore对C++类的调用,提供给JavaScriptCore进行CALLBACK回调。

    3.   最后调用JSClassCreate函数,将定义的C++类和C++函数注入到Javascript中

     

    C++示例代码如下

    //C++ 类定义

    class test

    {         

    public:

        test(){number=0;};

        voidfunc(){number++;}

        int number;

    };

    test g_test;//变量定义

     

    //全局函数,封装test类的func方法调用

    JSValueRef testFunc(JSContextRef ctx, JSObjectRef ,JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[],JSValueRef*)

    {

        test* t =static_cast<test*>(JSObjectGetPrivate(thisObject));

        t->func();

        returnJSValueMakeUndefined(ctx);

    }

    //全局函数,封装test类的成员变量number的get操作

    JSValueRef getTestNumber(JSContextRef ctx, JSObjectRefthisObject, JSStringRef, JSValueRef*)

    {

        test* t =static_cast<test*>(JSObjectGetPrivate(thisObject));

        returnJSValueMakeNumber(ctx, t->number);

    }

     

    //使用一个函数, 创建JavaScript类

    JSClassRef createTestClass()

    {

        //类成员变量定义,可以有多个,最后一个必须是{ 0, 0, 0 }

    //也可以指定set操作

        static JSStaticValuetestValues[] = {

            {"number", getTestNumber, 0, kJSPropertyAttributeNone },

            { 0, 0, 0, 0}

        };

        //类的方法定义,可以有多个,最后一个必须是{ 0, 0, 0 }

        staticJSStaticFunction testFunctions[] = {

            {"func", testFunc, kJSPropertyAttributeNone },

            { 0, 0, 0 }

        };

        //定义一个类

        staticJSClassDefinition classDefinition = {

            0,kJSClassAttributeNone, "test", 0, testValues, testFunctions,

            0, 0, 0, 0,0, 0, 0, 0, 0, 0, 0

        };

        // JSClassCreate执行后,就创建一个了JavaScript test类

        staticJSClassRef t = JSClassCreate(&classDefinition);

        return t;

    }

    //创建JavaScript类

    createTestClass ();

    JSGlobalContextRef ctx = JSGlobalContextCreate(NULL);

        JSObjectRef globalObj = JSContextGetGlobalObject(ctx); 

       

    //新建一个JavaScript类对象,并使之绑定g_test变量

      JSObjectRef classObjJSObjectMake(ctx,testClass(), &g_test);

     

    //将新建的对象注入JavaScript中

      JSStringRef objNameJSStringCreateWithUTF8CString("g_test");

    JSObjectSetProperty(ctx,globalObj,objName,classObj,kJSPropertyAttributeNone,NULL);

    将C++类和类指针注入到JavaScript后,在JavaScript中就可以这样使用了:

    g_test.func();

    var n = g_test.number;

    var t = new test;

    5.  微软JavaScript

    5.1. 介绍

    IE的Trident引擎是非开源的,微软JavaScript引擎也是非开源的。微软对外提供了一组COM接口。使用这组COM接口,能够将微软的JavaScript、VBScript嵌入到C、C++宿主语言中。

    这组COM接口主要有如下一些定义:

    l  IDispatch

    IDispatch是COM跨语言调用的基本接口。

    l  命名空间

    这里的“命名空间”的意思相当于JavaScript中的Global对象,宿主语言必须要实现一个“命名空间”。当JavaScript执行脚本,遇见未知的变量、函数、类时候,就会调用该“命名空间”的IDispatch接口,来进行解析和执行。

    l  IActiveScript

    IActiveScript是由JavaScript脚本引擎所实现的接口,C、C++宿主语言调用该COM接口,就可以建立和初始化一个脚本的执行环境。

    l  IActiveScriptParse

    IActiveScriptParse是由JavaScript脚本引擎所实现的接口,该接口可以分析、执行一段JavaScript脚本,并且还可以控制JavaScript脚本的执行过程。

    l  IActiveScriptSite

    IActiveScriptSite是由C、C++宿主语言所实现的接口,该接口可以给JavaScript脚本提供“命名空间”,并且当JavaScript引擎出现错误、异常时,就会使用该接口进行回调通知。

    在C++中使用这组COM接口的示例代码如下:

    //获取JavaScript的CLSID

    CLSID clsid;

    CLSIDFromProgID(_T("JScript"), &clsid);

    //创建JavaScript对象,获取IActiveScript接口指针

    CComPtr<IActiveScriptactiveScript = NULL;

    CoCreateInstance(clsidNULLCLSCTX_ALLIID_IActiveScript,(void **)&activeScript);

    //创建宿主对象,CMyScriptSite实现了IActiveScriptSite接口

    IActiveScriptSitesite = new CComObject<CMyScriptSite>;

            site->AddRef();

           

            //将宿主对象IActiveScriptSiteIActiveScript绑定

    hr = activeScript->SetScriptSite(site);

    //增加一个命名空间,会触发IActiveScriptSite的GetItemInfo方法被调用

    _bstr_t name = “test”

    hr=activeScript->AddNamedItem(name,SCRIPTITEM_ISVISIBLE|SCRIPTITEM_GLOBALMEMBERS);

    //获取脚本解析和执行接口

    CComQIPtr<IActiveScriptParseactiveScriptParseactiveScript;

    //初始化一个执行环境

    activeScriptParse->InitNew();

    //解析一段JS

    _bstr_t srcipt = "g_test.func()";

    activeScriptParse->ParseScriptText(srcipt,name,NULL,NULL,0,0,0,NULL,NULL);

            //开始执行JS

    activeScript->SetScriptState(SCRIPTSTATE_STARTED);

    //宿主对象CMyScriptSite,必须要实现的一个方法

    //与IActiveScript的AddNamedItem对应

    //返回给JavaScript一个命名空间

    HRESULT CMyScriptSite::GetItemInfo(LPCOLESTR pstrName, DWORDdwReturnMask, IUnknown **ppunkItem, ITypeInfo **ppti){

    if (name == _bstr_t(pstrName) )

                {   //这里简单将宿主对象自己当做一个“命名空间”返回给JavaScript

                    HRESULT hr =QueryInterface(IID_IUnknown, (void**)ppunkItem);

                    return hr;

                }

                return E_FAIL;

    }

    5.2. JavaScript与C++的交互

    在上一节中,宿主对象返回给JavaScript一个“命名空间”,这个“命名空间”必须实现IDispatch接口。同样的,宿主对象通过调用IActiveScript的GetScriptDispatch方法,也可以获得JavaScript的IDispatch接口指针。C、C++宿主对象和JavaScript脚本对象都互相持有对方的IDispatch接口。所以C、C++调用JavaScript,实际上就转化为IDispatch接口的调用。JavaScript调用宿主C、C++,实际上就转化为宿主语言C、C++对IDispatch接口的实现。

    IDispatch是微软跨语言调用的一个标准COM接口。对于这个接口的调用、实现,网上到处都有,这里就忽略不写了。

  • 相关阅读:
    Spock
    Spock
    Spock
    Spock
    Spock
    Spock
    Python3 与 Python2 的不同
    python 文件处理
    Django 数据迁移
    Python 特殊方法
  • 原文地址:https://www.cnblogs.com/muyun/p/3565780.html
Copyright © 2011-2022 走看看