読み進めていったときのメモです。JavaScriptエンジンを組み込むためのガイド。次の2つの方法を紹介するC++のオブジェクトやメソッドをJavaScriptで使えるようにするJavaScriptのオブジェクトやメソッドをC++で使えるようにする重要な概念Isolate(アイソレート)自らのヒープをもった、VMインスタンス。Handle(ローカルハンドル)オブジェクトを参照するもの。V8のオブジェクトはすべてハンドルを介して利用される(GCの仕組みと関係がある)。Scope(ハンドルスコープ)複数のハンドルの集合。使い終わったハンドルを一つ一つ削除するのではなく、スコープごと削除する、といったことができる。Context(コンテキスト)実行環境。複数の(関係のない)JavaScriptコードを一つのV8インスタンス内で走らせることを可能にする。どのコンテキスト内でJavaScriptコードを走らせたいのか、常に明示的に指定する必要がある。ハンドルとGCハンドルは、(ヒープ内にある)JavaScriptのオブジェクトの位置への参照を提供する。V8のGCは、「今後アクセスされないオブジェクトによって使われているメモリ」を再利用できるようにする。その過程で、オブジェクトがヒープ内の別の位置に移動されることがあるが、そういった場合にハンドルは更新される。オブジェクトがJavaScript内で参照されていない、かつどのハンドルからも参照されていないとき、オブジェクトはGarbageとみなされる。(適当なタイミングで)GCによって削除される。ハンドルの種類ハンドルにはいくつか種類がある。Handle(ローカルハンドル)ローカルハンドルはスタックに保持され、適当なデストラクタによって削除される。このハンドルのライフタイムは、「ハンドルスコープ」によって決定される。ハンドルスコープは、一般的には関数呼び出し時に作成される。ハンドルスコープが削除されるとき、ハンドルスコープ内のハンドルによって参照されていたオブジェクト群は不要とみなされ、それらよって使われていたメモリは再利用可能となる。(JavaScriptコードまたは他のハンドルから参照されている場合にはオブジェクトは不要にならない。ハンドルスタックはC++のコールスタックの中にはないが、ハンドルスコープはC++のコールスタックの中にある。用いてアロケートしない)。Handle(永続ハンドル)ローカルハンドルと同様、ヒープに置かれたJavaScriptオブジェクトへの参照を提供する。2つのバリエーションが有り、それによって参照のライフタイムが異なる。Handleは、以下のような場合に用いる。Handleを用いている。HandleはWeakにすることもできる。Handleからしか参照されていない場合には、GCによるコールバックを発動させる」といったことができる。Handleの2つのバリエーションは、以下の通りである。UniquePersistent対象オブジェクトのライフタイムがC++のコンストラクタとデストラクタに依存する。よって明示的にクリアしないといけない。メモPersistentとUniquePersistentはコピーすることができず、C++11以前の標準ライブラリのコンテナ(std::vectorなど)とともに用いることには向いていない。そのため、PersistentValueMapとPersistentValueVectorが用意されている。オブジェクトが生きているかをGCが判定する手間を省くことができるため、軽量。ハンドルスコープオブジェクトを作成するときに、毎回ローカルハンドルを作っていては、ハンドルの数が膨大になってしまう。そこで便利なのが、ハンドルスコープである。ハンドルスコープのデストラクタが呼ばれたとき、そのスコープ内に作られた全てのハンドルがスタックから削除される。(そして、参照されていたオブジェクトはGCによってヒープから削除することができるようになる。ローカルハンドルを返しており、それを用いて永続ハンドルを作っている。handle_scopeのデストラクタが呼ばれたとき、source_objやscript_objは不要になるpersistent_contextは永続ハンドルなので、ハンドルスコープから抜けても削除されないpersistent_contextを削除するには、persistent_context->Reset()をする必要がある注(ハンドルスコープを宣言している)関数内で定義したローカルハンドルを返り値として用いることはできない。それに対処するためには、HandleScopeの代わりにEscapableHandleScopeクラスを用いる。以下のように、用いたいオブジェクトのローカルハンドルを引数として、Escapeメソッドを用いる。handle_scope.Escape(array);}Escapeメソッドは、引数に渡された値を、現在のスコープを含んでいるスコープにコピーし、ローカルハンドルを全て削除する。新しいハンドルを返す。コンテキストコンテキストは、実行環境を意味する。V8の一つのインスタンス内で、独立した(関連性のない)複数のJavaScriptのアプリケーションを実行するために用意されている。JavaScriptコードを実行するときには、どのコンテキストで実行するのかを明示的に指定する必要がある。JavaScriptでは、ビルトインのユーティリティ関数やオブジェクトを、コード内で変更することができる。2つの全く関係ないJavaScriptの関数が、(共有している)グローバルオブジェクトを同じように変更しようとしたら、予期しない結果を引き起こすはずである。いった問題が、コンテキストによって解消される。多くのビルトインオブジェクトがその中に含まれることを考えると、CPUやメモリへの負荷が大きいと思うかもしれない。V8のキャッシュ機構のおかげで、初回以降のコンテキストの作成は初回のコンテキストの作成よりもかなり軽量である。ビルトインのJavaScriptコードのパースは、初回のみで十分なためである。ビルドオプションで有効になる。デフォルトで有効)は、ビルトインのJavaScriptコードを予めコンパイルし、シリアル化されたヒープを含んだスナップショットを作成する。これを用いれば、初回のコンテキストの作成も高速化される。コンテキストは一度作った後、何度も入ったり出たりできる。コンテキストAからコンテキストBに入り、その後にコンテキストBを抜けると、コンテキストAに復帰する。それぞれのウィンドウ、そしてiframeがそれぞれ個別の(他に干渉されない)JavaScriptの環境を作りたいというモチベーションにより、V8はコンテキストの仕組みを持っている。テンプレートテンプレートは、コンテキスト内での関数やオブジェクトの設計図のようなものである。テンプレートでC++の関数やデータ構造をラップすることで、JavaScriptから扱えるオブジェクトを作ることができるようになる。Chromeは、テンプレートを用いてC++のDOMノードをラップし、JavaScriptオブジェクトとして利用できるようにしている。テンプレートは一度作れば、複数のコンテキストで用いることができる。テンプレートは必要なだけ作成することができるが、一つのコンテキスト内で、各テンプレートはそれぞれ一つのインスタンスしか持つことができない。JavaScriptでは、関数とオブジェクトに強い二重性がある。C++やJavaでは、新しい種類のオブジェクトを作成する際にはクラスを定義する。JavaScriptでは関数を作り、その関数をコンストラクタとしてインスタンスを作成する。そのため、JavaScriptオブジェクトのレイアウトや機能は、それを作成した関数に強く結びついている。この特徴は、V8のテンプレートの動作に反映されている。テンプレートには2つの種類がある。Templateは、関数のための下書きである。コンテキスト内でGetFunctionメソッドを呼ぶことで、JavaScriptの関数をインスタンス化することができる。JavaScriptの関数(インスタンス)が呼ばれたときにC++コールバックが呼び出されるようにすることができる。Templateを持っている。関数をコンストラクタとして、オブジェクトを作成するように設計されている。Templateには(以下の)2種類のC++のコールバックを関連付けることができる。Callbacks(インターセプタコールバック)オブジェクトのプロパティがアクセスされた際に必ず呼ばれるコールバック以下の例では、グローバルオブジェクトのテンプレートを作成し、ビルトインのグローバル関数を設定している。global);Accessors(アクセッサ)JavaScriptによって、オブジェクトのプロパティがアクセスされたときに、計算を行い値を返すC++のコールバックのことをアクセッサという。アクセッサは(SetAccessorメソッドを用いて)オブジェクトテンプレートで設定される。SetAccessorメソッドは、関連付けたいプロパティの名前と、読み/書きの際にそれぞれ呼ばれる2つのコールバックを引数として受け付ける。yをコンテキスト内のグローバル変数としてJavaScriptからアクセス可能にしたいケースを考える。この場合には、スクリプトがそれらの値を読んだり書いたりする際に、C++のアクセッサを呼ぶ必要がある。アクセッサ関数(読み/書きの2つ)は、C++の整数を(Integer::Newを用いて)JavaScriptの整数に変換し、JavaScriptの整数を(Int32Value)を用いて、C++の整数に変換する。コードは以下のようになる。上のコードで、オブジェクトテンプレートはコンテキストと同じタイミングで作られているが、「テンプレートは事前に作っておいて、それを後に複数のコンテキストで用いる」ということもできる。ダイナミック変数へのアクセス上の例では、変数が静的でグローバルなものだった。ブラウザのDOMツリーのように、操作されるデータがダイナミックだったらどうなるのであろうか。xとyが、C++のPointクラスのフィールドとしよう。y_;}C++のpointインスタンスを(ほしい分だけ)JavaScriptで使えるようにしたい。そこで、C++のpointインスタンスそれぞれに対してひとつのJavaScriptオブジェクトをつくり、それらの間の関連付けをしないといけない。fieldsによって実現される。pointのラッパーオブジェクトのオブジェクトテンプレートを作成する。ObjectTemplate::New(isolate);各JavaScriptのpointオブジェクトは、それをラップし、内部フィールドを持っているC++オブジェクトへの参照を保持するようになっている。内部フィールドというのは、JavaScriptからはアクセスできずC++からアクセスできるフィールドである。オブジェクトはいくつでも内部フィールドを持つことができ、その数は以下のように(オブジェクトテンプレートにおいて)設定できる。point_templ->SetInternalFieldCount(1);ここで、内部フィールドの数は1に設定されており、その内部フィールドのインデックス0は、C++のオブジェクトを指している。次に、xとyのアクセッサをテンプレートに追加する。SetPointY);そして以下のように、オブジェクトテンプレートからインスタンスを作成し、それを内部フィールドに設定してラップする。p));Externalオブジェクトはvoid*のラッパーである。Externalオブジェクトは内部フィールドに参照を保存するためにのみ用いられる。JavaScriptオブジェクトはC++オブジェクトへの参照を直接持つことはできず、橋渡しとして、Externalバリューが用いられる。HandleがC++からJavaScriptオブジェクトへの参照を提供していたのに対し、ExternalはJavaScriptからC++オブジェクトへの参照を提供しているという点で、HandleとExternalは対照的である。以下はxのための読み込み/書き込みアクセッサの定義である。value->Int32Value();}アクセッサはJavaScriptオブジェクトにラップされているpointオブジェクトへの参照を取り出し、対象のフィールドを読み書きしている。このようにして、何個のpointオブジェクトに対しても使えるジェネリックなアクセッサを作成できる。(インターセプター)オブジェクトのどのプロパティがアクセスされたときにも呼び出されるようなコールバックを指定することができる。これはインターセプターと呼ばれている。インターセプターには2つの種類が存在する。(名前付きプロパティのインターセプタ)文字列の名前を持ったプロパティがアクセスされたときに呼ばれるインターセプタ。(インデックスプロパティのインターセプタ)インデックスプロパティがアクセスされたときに呼ばれるインターセプタ。V8のソースコードにサンプルとして含まれているprocess.ccはインターセプターを使用している。以下のコードで、SetNamedPropertyHandlerはMapGetとMapSetをインターセプタとして指定している。policyは、あるオリジンから得たドキュメントやスクリプトが、他のオリジンから得たドキュメントを読み込んだり書き換えたりすることを防ぐためのものである。ここで、オリジンというのは、ドメイン名、プロトコル(httpsなど)、そしてポートの組み合わせとして定義されている。www.example.com:81とwww.example.comは異なったオリジンであり、同じオリジンとして扱われるには、ドメイン名、プロトコル、ポートの全てが一致している必要がある。このポリシーが無いと、悪意のあるウェブページが(ブラウザ内で)他のウェブページを書き換えたりすることができてしまう。V8では、オリジンはコンテキストとして定義されている。呼び出し元のコンテキスト以外のコンテキストへのアクセスはデフォルトで禁止されている。呼び出し元のコンテキスト以外のコンテキストにアクセスしたい場合には、セキュリティトークンまたはセキュリティコールバックを用いる必要がある。セキュリティトークンは任意の値を取れるが、シンボル(他で使われていないカノニカルな文字列)を用いることが多い。コンテキストを用意する際に、SetSecurityTokenを通して明示的にセキュリティトークンを指定することができる。ない場合には、V8が自動で作成してくれる。グローバル変数へのアクセスが試みられたとき、V8のセキュリティシステムは(アクセスの対象となっている)グローバルオブジェクトのセキュリティトークンと、(アクセスをしようとしている)コードのセキュリティトークンをチェックしている。一致していた場合、アクセスは許可される。一致していない場合には、V8はアクセスを許可すべきか否かを判定するためのコールバックを呼び出す。(オブジェクトテンプレートの)SetAccessCheckCallbacks関数を用いてオブジェクトに対してセキュリティコールバックを設定することで、オブジェクトへのアクセスを許可するか否かを指定することができる。V8のセキュリティシステムは、(アクセスの対象となっている)オブジェクトのセキュリティコールバックを取得し、それを呼ぶことで他のコンテキストがそれにアクセス可能か否かを判定する。このコールバックには、「アクセスされているオブジェクト」「アクセスされているプロパティ」そして「行われようとしている操作の種類(書き込み、読み込み、削除など)」が与えられ、「アクセスを許可するか」を返す。Chromeでは次のものに対して特別なコールバックが使用されている。history.go()例外V8は、エラーが発生した際に例外を発生させる。スクリプトや関数が、存在しないプロパティを読み込んだり、関数じゃないものを関数として用いたりしたときである。V8は、操作が成功しなかった場合に空のハンドルを返す。そのため、コード内で関数の返り値が空のハンドルで無いか逐一確かめる必要がある。それは、LocalクラスのIsEmpty関数を用いることによって可能である。(以下のように)TryCatchを用いて例外をキャッチすることができる。*exception_str);}もしも返り値として空のハンドルが返ってきて、TryCatchが存在しなかった場合、コードの実行はは終了する必要がある。TryCatchが存在しており(例外がキャッチされているのであれば)、コードは終了しなくても良い。継承JavaScriptは、クラスのないオブジェクト指向言語である。伝統的なクラス継承の仕組みではなく、プロトタイプを用いた継承を用いる。C++やJavaなどの、一般的なオブジェクト指向の言語に慣れているプログラマにとって混乱しやすいかもしれない。(JavaやC++などの)クラスに基づいたオブジェクト指向プログラミング言語は、クラスとインスタンスという異なった2つの概念をベースにしている。プロトタイプベースのJavaScriptにはこの区別がない(オブジェクトがあるだけである)。JavaScriptはクラスのヒエラルキーの宣言に対応していない。JavaScriptはプロトタイプの仕組みによって、カスタムのプロパティやメソッドをオブジェクトに追加することを簡単にしている。以下のようにしてオブジェクトにカスタムのプロパティを追加することができる。2;このようにして追加されたカスタムプロパティは、当該オブジェクトにのみ存在する。ほかのbicycle()インスタンス(mountainbikeとする)を作ったときには、mountainbike.wheelsはundefinedとなる。このような動作が好ましいときもあるが、全てのオブジェクトインスタンスに共通してカスタムプロパティを追加したい場合も存在する。その際に便利なのが、JavaScriptのプロトタイプオブジェクトである。プロトタイプオブジェクトは、以下のようにして利用することができる。2;これにより、bicycleインスタンスはすべてwheelsプロパティを持つようになった。このような機能は、V8のテンプレートにも用いられている。関数テンプレートにはPrototypeTemplateというメソッドがあり、それによってテンプレートがプロトタイプの機能を持つことができる。プロパティを追加し、そしてそれらにPrototypeTemplate関数を用いてC++の関数を関連付けることで、対応するFunctionTemplateのインスタンスからプロパティが利用できるようになる。例としては、次のようになる。MyWheelsCallback)->GetFunction());これにより、biketemplateは、プロトタイプチェーンにwheelsメソッドを持つようになり、それが呼ばれたときにはC++の関数MyWheelsMethodCallbackが呼ばれるようになる。V8のFunctionTemplateクラスは、メンバー関数としてInheritを持っている。これにより次の例のように、ある関数テンプレートを継承して関数テンプレートを作ることができる。(1)リンクホームページfeedレビューのまとめスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。