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接口。对于这个接口的调用、实现,网上到处都有,这里就忽略不写了。

  • 相关阅读:
    SQL Server, Timeout expired.all pooled connections were in use and max pool size was reached
    javascript 事件调用顺序
    Best Practices for Speeding Up Your Web Site
    C语言程序设计 使用VC6绿色版
    破解SQL Prompt 3.9的几步操作
    Master page Path (MasterPage 路径)
    几个小型数据库的比较
    CSS+DIV 完美实现垂直居中的方法
    由Response.Redirect引发的"Thread was being aborted. "异常的处理方法
    Adsutil.vbs 在脚本攻击中的妙用
  • 原文地址:https://www.cnblogs.com/muyun/p/3565780.html
Copyright © 2011-2022 走看看