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接口。對於這個接口的調用、實現,網上到處都有,這里就忽略不寫了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM