V8 javascript引擎初步體驗
v8 引擎,不必多說,大名鼎鼎,迄今最快的js引擎。
這麼好的東西,如果盡在瀏覽器中使用,太浪費了,也希望用到其他地方,比如一些客戶端開發中。
閒言少講,這就開始
下載原始碼
去官方網站:https://code.google.com/p/v8/
我喜歡用git,所以從這裡下載
git clone git://github.com/v8/v8.git v8 && cd v8
編譯
我為android平臺編譯的,折騰了好久,記錄一些重點。你首先要看這個:http://code.google.com/p/v8/wiki/BuildingWithGYP
其實關鍵是
make dependencies
這需要花很長時間(呃,沒辦法,國內網速不給力,我下載了好幾次)。。。
然後,在android上編譯(其他平臺?so easy,看
我自己的編譯方法,是用交叉編譯的
Cross-compiling
Similar to building with Clang, you can also use a cross-compiler. Just export your toolchain (CXX/LINK environment variables should be enough) and compile. For example:
export CXX=/path/to/cross-compile-g++export LINK=/path/to/cross-compile-g++ make arm.release
(編譯等待中.....)
完了之後,你就可以使用了。
在android上使用
首先,建立一個工程(怎麼建立?這個。。。還是自己找吧),一個Activity工程就行,然後在工程目錄下建立一個jni目錄。
具體怎麼使用ndk,我就不說,自己找吧,我說重點的:
1. 拷貝標頭檔案和庫檔案
為了方便,我把標頭檔案和庫檔案放在jni下的include和lib下。標頭檔案直接從v8/include資料夾下拷貝就可以,庫檔案比較麻煩一些,必須拷貝這些:
libicudata.a libicui18n.a libicuuc.a libv8.a libv8_snapshot.a
2. 注意Android.mk的寫法
在寫Android.mk的時候,必須注意:
1. 庫的引用順序很重要,順序錯了,一樣找不到符號
2. 要用stl,
Android.mk和Application.mk(STL需要的)的程式碼如下,給大家和參考,省得走彎路
Android.mk:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_CFLAGS += -I$(LOCAL_PATH)/include
LOCAL_LDFLAGS += \
$(LOCAL_PATH)/lib/libv8.a \
$(LOCAL_PATH)/lib/libv8_snapshot.a \
$(LOCAL_PATH)/lib/libicui18n.a \
$(LOCAL_PATH)/lib/libicuuc.a \
$(LOCAL_PATH)/lib/libicudata.a \
/<path>/android-ndk-r9d//sources/cxx-stl/stlport/libs/armeabi/libstlport_static.a
LOCAL_MODULE := jswrapper
LOCAL_LDLIBS := -llog
LOCAL_SRC_FILES := \
registers.cpp \
JavaScriptContextWrap.cpp \
javautils.cpp \
JsJavaClassProxy.cpp \
javawrap.cpp
#sample.cpp sample-jni.cpp \
LOCAL_ALLOW_UNDEFINED_SYMBOLS := true
LOCAL_WHOLE_STATIC_LIBRARIES += android_support
include $(BUILD_SHARED_LIBRARY)
$(call import-module, android/support)
(我很笨啊,stl不全路徑連結就找不到,呃,難道上天又一次中意我,要給我磨練嗎......)
記得lib的順序和stl噢。。。
Application.mk
APP_CFLAGS += -fexceptions
APP_CFLAGS += -frtti
APP_STL := stlport_static
#gnustl_static
編譯用ndk-build就好了。
程式碼編寫
至於jni部分的程式碼,我就不寫了,不懂的baidu和google,你懂的。
標頭檔案?用一個 "v8.h"就好了。
1. 初始化
v8::V8::InitializeICU();
(完了?是的,就這麼簡單)
2. 建立Isolate和Context
isolate_ = v8::Isolate::New();
isolate_->Enter();
v8::HandleScope handle_scope(isolate_);
v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate_);
//create Log object
v8::Handle<v8::ObjectTemplate> log = v8::ObjectTemplate::New(isolate_);
log->Set(v8::String::NewFromUtf8(isolate_, "e"),
v8::FunctionTemplate::New(isolate_, js_func_log_e));
log->Set(v8::String::NewFromUtf8(isolate_, "i"),
v8::FunctionTemplate::New(isolate_, js_func_log_i));
log->Set(v8::String::NewFromUtf8(isolate_, "v"),
v8::FunctionTemplate::New(isolate_, js_func_log_v));
log->Set(v8::String::NewFromUtf8(isolate_, "d"),
v8::FunctionTemplate::New(isolate_, js_func_log_d));
global->Set(v8::String::NewFromUtf8(isolate_, "Log"), log);
//TODO others
global->Set(v8::String::NewFromUtf8(isolate_, "import_java_class"),
v8::FunctionTemplate::New(isolate_, js_func_import_java_class));
context_.Reset(isolate_, v8::Context::New(isolate_, NULL, global));
內容有點多哈。彆著急,聽我慢慢道來。
首先,建立一個v8::Isolate, 用v8::Isolate::New()。 isolate是什麼東東?這個嘛,其實我也不太清楚,大概類似一個虛擬機器,所有的javascript物件都在這個isolate上,不太的isolate老死不相往來。
然後需要呼叫一下isolate_->Enter(),當然要銷燬時,呼叫一下isolate_->Leave()。
對了,v8是c++的,成員方法首字母都是大寫的。
第二,我們可以建立一個ObjectTemplate了。ObjectTemplate望文生義,是物件的模版,怎麼說呢,對應到C++和java裡面,你可以認為是靜態類,有一堆靜態方法。
各位請先看最後一句:v8::Context::New(isolate_, NULL, global)。這句話註冊一個全域性的模版,也就是說,如果我在global中註冊一個print函式,那麼可以直接在js中這樣寫:
print ("Hi, I\'m from native");
我們的例子裡面,是註冊了一個Log的子ObjectTempl,Log又包含e, i, v, d幾個方法,其實就是對應的android.util.Log.e, i, v, d。
註冊方法是呼叫Set,C++函式指標需要用
v8::FunctionTemplate::New
來包裝。這些知道就行了,不必深究(其實我也不懂)
還有一點必須說明:上面的程式碼實際上是一個類的一部分,這裡有兩個成員變數:
v8::Persistent<v8::Context> context_;
v8::Isolate* isolate_;
isolate直接使用指標,而Context必須用Persistent,這是一個可以持久保持物件的指標(防止被v8當垃圾回收了),使用方法是用 Rest:
context_.Reset(isolate_, v8::Context::New(isolate_, NULL, global));
3. 定義一個函式
凡事先看程式碼:(我用js_func_log_x來實現js_func_log_i等函式的公共部分)
void js_func_log_x(int level, const v8::FunctionCallbackInfo<v8::Value>& args) {
if (args.Length() == 0) {
return ;
}
//get tag
char szTag[1024] = "";
{
v8::HandleScope handle_scope(args.GetIsolate());
v8::String::Utf8Value str(args[0]);
const char* cstr = ToCString(str);
strcpy(szTag, cstr);
}
char szLog[1024*8] = "";
bool first = true;
int len = 0;
for (int i = 1; i < args.Length(); i++)
{
v8::HandleScope handle_scope(args.GetIsolate());
if (first) {
first = false;
} else {
len += sprintf(szLog + len, " ");
}
v8::String::Utf8Value str(args[i]);
const char* cstr = ToCString(str);
len += sprintf(szLog + len, "%s", cstr);
}
__android_log_print(level, szTag, "%s", szLog);
}
args 包含了引數和執行時。在每次取引數處理引數時,必須呼叫
v8::HandleScope handle_scope(args.GetIsolate());
(why?I don't known 。。。。)
從Value中取字串,則需要用ToCString,他也很簡單:
static const char* ToCString(const v8::String::Utf8Value& value) {
return *value ? *value : "<string conversion failed>";
}
正經的給v8使用的函式是這樣的:
void js_func_log_i(const v8::FunctionCallbackInfo<v8::Value>& args) {
js_func_log_x(ANDROID_LOG_INFO, args);
}
void js_func_log_v(const v8::FunctionCallbackInfo<v8::Value>& args) {
js_func_log_x(ANDROID_LOG_VERBOSE, args);
}
void js_func_log_d(const v8::FunctionCallbackInfo<v8::Value>& args) {
js_func_log_x(ANDROID_LOG_DEBUG, args);
}
void js_func_log_e(const v8::FunctionCallbackInfo<v8::Value>& args) {
js_func_log_x(ANDROID_LOG_ERROR, args);
}
好了,初體驗到此結束
一些重要的知識點
1. 關聯一個物件到Isolate上
Isolate可以有多個,如果有一個物件關聯到Isolate,該如何從Isolate找到該物件呢?
使用Isolate的GetData/SetData
比如
JavaWrapper* JavaWrapper::fromIsolate(Isolate* isolate) {
if (isolate == NULL) {
return NULL;
}
void *data = isolate->GetData(ISOLATE_SLOT_JAVA_WRAPPER);
JavaWrapper* javawrap = reinterpret_cast<JavaWrapper*>(data);
return javawrap && javawrap->isolate_ == isolate ? javawrap : NULL;
}
JavaWrapper::JavaWrapper(Isolate* isolate)
:isolate_(isolate)
{
isolate_->SetData(ISOLATE_SLOT_JAVA_WRAPPER, (void*)this);
}
2. 怎樣從v8::Value中得到其他型別的引數
v8::Value是個複合物件,我們可以這樣做:
for (int i = 0; i < count; i++) {
HandleScope handle_scope(args.GetIsolate());
Local<Value> v = args[i];
if (v->IsUndefined() || v->IsNull()) {
types[i] = JsJavaClassProxy::NullClass();
values[i] = JsJavaClassProxy::NullObject();
LOGV(" arg(%d) is null\n", i);
} else if (v->IsTrue() || v->IsFalse()) {
types[i] = JsJavaClassProxy::BooleanClass();
jboolean jv = (jboolean)(v->IsTrue() ? true : false);
values[i] = jreflect::ValueOf(env, jv);
LOGV(" arg(%d) is boolean:%d", i, jv);
} else if (v->IsString()) {
types[i] = JsJavaClassProxy::StringClass();
String::Utf8Value str(v);
const char* cstr = ToCString(str);
values[i] = env->NewStringUTF(cstr);
LOGV(" arg(%d) is String:%s", i, cstr);
} else if (v->IsBoolean()) {
types[i] = JsJavaClassProxy::BooleanClass();
jboolean jv = v->ToBoolean()->Value();
values[i] = jreflect::ValueOf(env, jv);
LOGV(" arg(%d) is boolean:%d", i, jv);
} else if (v->IsInt32() || v->IsUint32()) {
types[i] = JsJavaClassProxy::IntClass();
jint jv = v->ToInt32()->Value();
values[i] = jreflect::ValueOf(env, jv);
LOGV(" arg(%d) is int:%d", i, jv);
} else if (v->IsNumber()) {
types[i] = JsJavaClassProxy::DoubleClass();
jdouble jv = v->ToNumber()->Value();
values[i] = jreflect::ValueOf(env, jv);
LOGV(" arg(%d) is double:%f", i, jv);
} /** TODO MORE **/ else {
LOGE("Invalidate Arg type!");
ASSERT(false);
}
}
更多的,從v8.h中看就可以了。
好了,先到這裡吧。