【Electron連載】第４回 基本編－メイン／レンダラープロセスの話

[↑【Electron連載（目次）】Electronでアプリ完成までのメモ](http://qiita.com/nullpointer_t/items/f74edc1f59cac42946ba)

やっぱり、基本編を始める前にElectronのプロセスについては書いておかんと後で詰まる。
ということで、早速スタートや。

# Electronで使われているプロセスのアーキテクチャ

プロセスについての良い説明と画像がったから紹介しておくわ。
![00_s.gif](https://qiita-image-store.s3.amazonaws.com/0/72717/b0356ab7-4afd-a69c-7a7b-7e11a1733e14.gif)
こちらから拝借させてもらったで。
http://www.buildinsider.net/enterprise/electron/01

## メインプロセス

まずはメインプロセスな。
Electron（っていうかNode.jsか）のプロジェクトのフォルダにあるpackage.jsonの中身で
mainで定義されているファイルを実行するプロセスがメインプロセスやで。
メインというだけあって、このメインプロセスが終了＝アプリケーションの終了ということやな。

## レンダラープロセス

次はレンダラープロセスな。
いわゆる、画面ごとに作成されるプロセスの事。
メインプロセスからxxxx.htmlを読み込んで画面を表示したら、その画面自体が一つのプロセスで動作しているってことやで。
メインプロセスからいくらでも作ることが出来るで。

## ここでポイント✌

Electronで利用できるAPIにはメインプロセスで使えるもの、レンダラープロセスで使えるもの、両方で使えるものあるさかい注意な。
というわけで、次回以降でAPIの説明の際にはどっちか明記する事にする。

# プロセス間通信

んで、ここからが本題。
Electronでウィンドウ間のデータ通信はどうやるの？って時にプロセス間通信を利用するんやで。
まずは、Electronのプロセス間通信のルールがあるから気を付けてや。

* プロセス間通信はメインプロセスとレンダラープロセスの間のみ可能。
* レンダラープロセスとレンダラープロセスの通信は不可。
* レンダラープロセスからはメインプロセスが一つしか存在しないため、宛先不要。
* メインプロセスからはどのレンダラープロセスへ通信するか宛先が必要。
* レンダラープロセスからはメインプロセスへは同期・非同期通信が可能
* メインプロセスからレンダラープロセスへは非同期通信のみ可能。

んじゃ、さっき言った、ウィンドウ間（レンダラープロセス間）はどうするの？
という場合は・・・。

一旦、メインプロセスを間に入れる事によって通信を可能にするってこと。
__レンダラープロセス__
　　　　__↓__
__メインプロセス__
　　　　__↓__
__レンダラープロセス__
ということやね。

## ipc(Inter-process Communication：プロセス間通信)の使い方

プロセス間通信を使う場合はメインプロセスとレンダラープロセスでお作法があるみたいやからそれに準じてやるで。

### レンダラープロセス→メインプロセス。

まずは、このパターン。
アプリケーション作成していたらまず、ここからスタートすることになると思うで。

### 非同期通信

```javascript:renderer-procss.ts
// レンダラープロセスでやりとりするipcRenderer
const {ipcRenderer} = require('electron');
// asynchronous-messageチャンネルで文字列"ping"を非同期通信で送信
ipcRenderer.send('asynchronous-message', 'ping');

//非同期通信の受信の応答処理（asynchronous-replyチャンネル）
ipcRenderer.on('asynchronous-reply', (event, arg) => {
  // "pong"が出力される
  console.log(arg);
});
```

```javascript:main-procss.ts
// メインプロセスでやりとりするipcMain
const {ipcMain} = require('electron');
//asynchronous-messageチャンネルの受信処理
ipcMain.on('asynchronous-message', (event, arg) => {
  // "ping"が出力される
  console.log(arg);
  // event.senderに送信元のプロセスが設定されているので、asynchronous-replyチャンネルで文字列"pong"を非同期通信で送信元に送信
  event.sender.send('asynchronous-reply', 'pong');
  // ※event.senderはwebContentsオブジェクトな
});
```

### 同期通信

今度は同期通信やで。
送信元の関数がSendからSendSyncになってて、戻り値が取得できるで。

```javascript:renderer-procss.ts
// レンダラープロセスでやりとりするipcRenderer
const {ipcRenderer} = require('electron');
// synchronous-messageチャンネルで文字列"ping"を同期通信で送信、戻り値を取得
var ret: string = ipcRenderer.sendSync('synchronous-message', 'ping');
// 戻り値"pong"が出力される
console.log(ret);
```

```javascript:main-procss.ts
// メインプロセスでやりとりするipcMain
const {ipcMain} = require('electron');
//synchronous-messageチャンネルの受信処理
ipcMain.on('synchronous-message', (event, arg) => {
  // "ping"が出力される
  console.log(arg)
  // 呼び出し元の戻り値に文字列"pong"を設定
  event.returnValue = 'pong'
})
```

###メインプロセス→レンダラープロセス

メインプロセスからはどのレンダラープロセスへ送信するか選択する必要があるってのはさっき書いたんやけど、送信する際にどのレンダラープロセスかを選択するんじゃなくて、送りたいプロセスのwebContentsオブジェクトを取得して、そこで関数呼び出しやからな。

```javascript:main-process.ts
// BrowserWindow読み込み
const {BrowserWindow} = require('electron');
// indexとsubウィンドウ生成（コードは略）
var indexWindow: BrowserWindow = new BrowserWindow(・・・略・・・);
var subWindow: BrowserWindow = new BrowserWindow(・・・略・・・);

//indexウィンドウに対してい文字列"ping"を送信
indexWindow.webContents.send('asynchronous-message', 'ping');
//subウィンドウに対してい文字列"pong"を送信
subWindow.webContents.send('asynchronous-message', 'pong');
```

```javascript:index-process.ts
// レンダラープロセスでやりとりするipcRenderer
const {ipcRenderer} = require('electron');
// asynchronous-messageチャンネルの受信処理
ipcRenderer.on('asynchronous-message', (msg) => {
  // アラートダイアログに"ping"が表示される  
  alert(msg);
});
```

```javascript:sub-process.ts
// レンダラープロセスでやりとりするipcRenderer
const {ipcRenderer} = require('electron');
// asynchronous-messageチャンネルの受信処理
ipcRenderer.on('asynchronous-message', (msg) => {
  // アラートダイアログに"pong"が表示される  
  alert(msg);
});
```

## プロセス間通信の使いどころ

実際のところ、メインとレンダラーでどのように処理をわけて、どうやって通信を使うかって話。
参考のページな。
https://electron.atom.io/docs/api/

* 上の表でMainとあるAPIはメインプロセスで使う。
* 上の表でRendererとあるAPIはレンダラープロセスで使う。
* レンダラープロセスでイベントからメインプロセスへ通信して処理を返してもらう。
* メインプロセスでレンダラープロセスを管理しておいて、メインプロセスで発生したイベントで対象のレンダラープロセスに通信する。

例えば・・・

* 画面（レンダラープロセス）でボタンクリックしてメインプロセスに通信、処理結果を返してもらう。
* メニュークリックイベント（メインプロセス）から画面（レンダラープロセス）に通信して処理を行う。

### レンダラープロセス間の通信

上に書いたとおり、一旦メインプロセスを介して通信するで。

```javascript:index-procss.ts
// レンダラープロセスでやりとりするipcRenderer
const {ipcRenderer} = require('electron');
// メイン→レンダラーは非同期通信なので非同期通信で行う
ipcRenderer.send('message', {win: 'sub', value: 'ping'});
```

```javascript:main-procss.ts
// メインプロセスでやりとりするipcMain
const {ipcMain} = require('electron');
//synchronous-messageチャンネルの受信処理
ipcMain.on('message', (event, arg) => {
  if (arg.win === 'sub') {
    subWindow.webContents.send('message', arg.value);
  }
})
```

```javascript:sub-procss.ts
// レンダラープロセスでやりとりするipcRenderer
const {ipcRenderer} = require('electron');
// 受信処理
ipcRenderer.on('message', (event, arg) => {
  // "ping"が出力される
  console.log(arg);
});
```

# ※注意

上に書いたソースコードでは、複数のファイルに同じモジュールの定義を書くと
Cannot redeclare block-scoped variable
というエラーが発生するさかい注意な。

こっちを参照。
https://trueman-developer.blogspot.jp/2017/05/nodejs-typescriptcannot-redeclare-block.html

# 次回

＞ [第５回　基本編－ウィンドウを作って表示](http://qiita.com/nullpointer_t/items/0131109fa49185a34fa4)