メインコンテンツへ飛ぶ

Electron でネイティブから JavaScript へ

· 読むのにかかる時間 1 分

C++ や Objective-C で記述された Electron の機能は、どのように JavaScript となってエンドユーザーが利用できるのでしょうか。


背景

Electron は、開発者の参入障壁を取り下げることを主目的とした JavaScript プラットフォームで、プラットフォーム固有の実装を気にせずに堅牢なデスクトップアプリを構築できます。 ただし、Electron 自身の中核には、特定システムの言語で記述するようなプラットフォーム固有の機能も必要です。

実際には Electron がネイティブコードを扱うので、単一の JavaScript API に集中できます。

一体、どのように動作しているのでしょうか。 C++ や Objective-C で記述された Electron の機能は、どのように JavaScript となってエンドユーザーが利用できるのでしょうか。

この道筋を追いかけるために、app モジュール から始めましょう。

lib/ ディレクトリ内の app.ts ファイルを開くと、その上部に以下のようなコードの行があります。

const binding = process.electronBinding('app');

この行は、開発者が使用している C++/Objective-C モジュールを JavaScript にバインドする Electron の仕組みをまさに表しています。 この関数は ElectronBindings クラスの、ヘッダーと 実装ファイル によって作成されます。

process.electronBinding

これらのファイルは Node.js の process.binding のように動作する process.electronBinding 関数を追加します。 process.binding は Node.js の require() メソッドよりローレベルの実装です。ただし、他の JS で書かれたコードではなくネイティブコードを require することができます。 このカスタム process.electronBinding 関数は Electron からネイティブコードをロードする機能を与えます。

トップレベルの JavaScript モジュール (app など) がこのネイティブコードを require する場合、そのネイティブコードの状態はどのように決定および設定されるのでしょうか。 そのメソッドはどこまで JavaScript に公開されるのでしょうか。 プロパティではどうなのでしょうか。

native_mate

現時点では、この疑問には native_mate が答えてくれます。これは、C++ と JavaScript の間で型をマーシャリングしやすくする Chromium の gin ライブラリ のフォークです。

native_mate/native_mate の中には、object_template_builder のヘッダーと実装ファイルがあります。 これにより、JavaScript 開発者が望むように適合する形式のネイティブコードでモジュールを形成します。

mate::ObjectTemplateBuilder

すべての Electron モジュールを object として見ると、object_template_builder で構築する理由がわかりやすくなります。 このクラスは、C++ で記述された Google によるオープンソースで高性能の JavaScript および WebAssembly エンジン、V8 が公開するクラスの上に構築されます。 V8 は JavaScript (ECMAScript) の仕様を実装しているため、ネイティブ機能の実装を JavaScript の実装に直接関連付けることができます。 たとえば、v8::ObjectTemplate は専用のコンストラクタ関数とプロトタイプなしで JavaScript オブジェクトを提供します。 Object[.prototype] を使用するため、JavaScript での Object.create() と等価です。

この動作確認は、アプリモジュールの実装ファイル atom_api_app.cc を参照してください。 下部には以下のようなものがあります。

mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
.SetMethod("getGPUInfo", &App::GetGPUInfo)

上記の行では、.SetMethodmate::ObjectTemplateBuilder で呼び出されます。 .SetMethodObjectTemplateBuilder クラスの任意のインスタンスで呼び出し、以下の構文で JavaScript の Object プロトタイプ にメソッドを設定できます。

.SetMethod("method_name", &function_to_bind)

これは以下の JavaScript と等価です。

function App{}
App.prototype.getGPUInfo = function () {
// ここに実装
}

このクラスには以下のようなモジュールにプロパティをセットする関数も含まれます。

.SetProperty("property_name", &getter_function_to_bind)

または

.SetProperty("property_name", &getter_function_to_bind, &setter_function_to_bind)

これらは、以下のような Object.defineProperty による JavaScript 実装になります。

function App {}
Object.defineProperty(App.prototype, 'myProperty', {
get() {
return _myProperty
}
})

aND

function App {}
Object.defineProperty(App.prototype, 'myProperty', {
get() {
return _myProperty
}
set(newPropertyValue) {
_myProperty = newPropertyValue
}
})

これによって開発者が予期するようなプロトタイプとプロパティで形成された JavaScript オブジェクトを作成することができ、よりローレベルのシステムで実装された関数とプロパティでもよりはっきりと推論します!

特定のモジュールメソッドの実装場所に関する決定は、それ自体が複雑かつ多くの場合に置いて非決定的です。これについては今後の記事で補います。