SOLFIND
Web Lens
Portal home

プロセス間通信 | Electron

https://www.electronjs.org/ja/docs/latest/tutorial/ipc • 275 KB fetched
Open original page


プロセス間通信 | Electron メインコンテンツへ飛ぶ Electron ドキュメント API ブログ ツール * Electron Forge * Electron Fiddle コミュニティ * ガバナンス * 事例紹介 * リソース リリース 日本語 * English * Deutsch * Español * Français * 日本語 * Português * Русский * 中文 検索 * はじめよう * Electron のプロセス * プロセスモデル * コンテキストの分離 * プロセス間通信 * プロセスのサンドボックス化 * Electron での MessagePort * ベストプラクティス * サンプル * 開発 * Native Node Modules * 配布方法 * テストとデバッグ * リファレンス * コントリビューション * * Electron のプロセス * プロセス間通信 目次 プロセス間通信 Electron で機能豊かなデスクトップアプリケーションを構築するには、プロセス間通信 (IPC) が重要な要素です。 なぜなら、Electron のプロセスモデルではメインプロセスとレンダラープロセスが異なる責務を担っており、UI からネイティブ API を呼び出したり、ネイティブメニューからウェブコンテンツの変更をトリガーしたりといった多くの共同タスクの実行には、IPC が唯一の方法となるからです。 IPC チャンネル ​ In Electron, processes communicate by passing messages through developer-defined "channels" with the ipcMain and ipcRenderer modules. これらのチャンネルは 任意 (好きな名称を指定可能) かつ 双方向的 (両方のモジュールで同じチャンネル名を使用可能)です。 このガイドでは、アプリのコードの参考になる基本的な IPC のパターンを具体的な例で説明します。 コンテキスト分離されたプロセスを理解する ​ Before proceeding to implementation details, you should be familiar with the idea of using a preload script to import Node.js and Electron modules in a context-isolated renderer process. * For a full overview of Electron's process model, you can read the process model docs . * For a primer into exposing APIs from your preload script using the contextBridge module, check out the context isolation tutorial . パターン 1: レンダラーからメインへ (片方向) ​ To fire a one-way IPC message from a renderer process to the main process, you can use the ipcRenderer.send API to send a message that is then received by the ipcMain.on API. 通常このパターンは、ウェブコンテンツからメインプロセスの API を呼び出すために使用します。 ここでは、プログラムによってウインドウのタイトルを変更できる簡単なアプリを作成することで、このパターンを実証しようと思います。 このデモでは、メインプロセス、レンダラープロセス、プリロードスクリプトにコードを追加する必要があります。 コード全体は以下のとおりですが、以降の節で各ファイルを個別に説明します。 docs/fiddles/ipc/pattern-1 ( 43.4.0 ) Open in Fiddle * main.js * preload.js * index.html * renderer.js const { app , BrowserWindow , ipcMain } = require ( 'electron/main' ) const path = require ( 'node:path' ) function handleSetTitle ( event , title ) { const webContents = event . sender const win = BrowserWindow . fromWebContents ( webContents ) win . setTitle ( title ) } function createWindow ( ) { const mainWindow = new BrowserWindow ( { webPreferences : { preload : path . join ( __dirname , 'preload.js' ) } } ) mainWindow . loadFile ( 'index.html' ) } app . whenReady ( ) . then ( ( ) => { ipcMain . on ( 'set-title' , handleSetTitle ) createWindow ( ) app . on ( 'activate' , function ( ) { if ( BrowserWindow . getAllWindows ( ) . length === 0 ) createWindow ( ) } ) } ) app . on ( 'window-all-closed' , function ( ) { if ( process . platform !== 'darwin' ) app . quit ( ) } ) const { contextBridge , ipcRenderer } = require ( 'electron/renderer' ) contextBridge . exposeInMainWorld ( 'electronAPI' , { setTitle : ( title ) => ipcRenderer . send ( 'set-title' , title ) } ) <! DOCTYPE html > < html > < head > < meta charset = " UTF-8 " > <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --> < meta http-equiv = " Content-Security-Policy " content = " default-src 'self'; script-src 'self' " > < title > Hello World! </ title > </ head > < body > Title: < input id = " title " /> < button id = " btn " type = " button " > Set </ button > < script src = " ./renderer.js " > </ script > </ body > </ html > const setButton = document . getElementById ( 'btn' ) const titleInput = document . getElementById ( 'title' ) setButton . addEventListener ( 'click' , ( ) => { const title = titleInput . value window . electronAPI . setTitle ( title ) } ) 1. Listen for events with ipcMain.on ​ In the main process, set an IPC listener on the set-title channel with the ipcMain.on API: main.js (Main Process) const { app , BrowserWindow , ipcMain } = require ( 'electron' ) const path = require ( 'node:path' ) // ... function handleSetTitle ( event , title ) { const webContents = event . sender const win = BrowserWindow . fromWebContents ( webContents ) win . setTitle ( title ) } function createWindow ( ) { const mainWindow = new BrowserWindow ( { webPreferences : { preload : path . join ( __dirname , 'preload.js' ) } } ) mainWindow . loadFile ( 'index.html' ) } app . whenReady ( ) . then ( ( ) => { ipcMain . on ( 'set-title' , handleSetTitle ) createWindow ( ) } ) // ... The above handleSetTitle callback has two parameters: an IpcMainEvent structure and a title string. メッセージが set-title チャンネルからやってくる度に、この関数がメッセージ送信者として付属する BrowserWindow インスタンスを取り出し、その中の win.setTitle API を使用します。 info 次のステップで index.html と preload.js のエントリポイントをロードしていることを確認してください。 2. プリロード経由で ipcRenderer.send を公開する ​ 先ほど作成したリスナーにメッセージを送るには、 ipcRenderer.send API を使用することで可能です。 デフォルトでは、レンダラープロセスは Node.js や Electron のモジュールへアクセスできません。 アプリ開発者として、 contextBridge API を使用し、プリロードスクリプトから API を限定して公開する必要があります。 プリロードスクリプトに、以下のコードを追加します。これは window.electronAPI グローバル変数をレンダラープロセスに公開します。 preload.js (Preload Script) const { contextBridge , ipcRenderer } = require ( 'electron' ) contextBridge . exposeInMainWorld ( 'electronAPI' , { setTitle : ( title ) => ipcRenderer . send ( 'set-title' , title ) } ) こうすることで、レンダラープロセスで window.electronAPI.setTitle() 関数が使用できるようになります。 セキュリティ警告 We don't directly expose the whole ipcRenderer.send API for security reasons . レンダラーの Electron API へのアクセスをできるだけ制限するようにしてください。 3. レンダラープロセスの UI を構築する ​ BrowserWindow に読み込まれる HTML ファイルに、テキスト入力とボタンからなる基本的なユーザーインターフェイスを追加します。 index.html <! DOCTYPE html > < html > < head > < meta charset = " UTF-8 " > <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --> < meta http-equiv = " Content-Security-Policy " content = " default-src 'self'; script-src 'self' " > < title > Hello World! </ title > </ head > < body > Title: < input id = " title " /> < button id = " btn " type = " button " > Set </ button > < script src = " ./renderer.js " > </ script > </ body > </ html > これらの要素を動作させるために、インポートされる renderer.js ファイルに数行のコードを追加して、プリロードスクリプトで公開した window.electronAPI 機能を利用します。 renderer.js (Renderer Process) const setButton = document . getElementById ( 'btn' ) const titleInput = document . getElementById ( 'title' ) setButton . addEventListener ( 'click' , ( ) => { const title = titleInput . value window . electronAPI . setTitle ( title ) } ) これにより、このデモは完全に機能しているはずです。 入力フィールドを使用すると BrowserWindow のタイトルに何が起こるのか、試してみてください! パターン 2: レンダラーからメインへ (双方向) ​ 双方向 IPC のよくある応用方法は、レンダラープロセスのコードからメインプロセスのモジュールを呼び出して、結果を待つことです。 This can be done by using ipcRenderer.invoke paired with ipcMain.handle . 以下の例では、レンダラープロセスからネイティブのファイルダイアログを開き、選択されたファイルのパスを返すことにします。 このデモでは、メインプロセス、レンダラープロセス、プリロードスクリプトにコードを追加する必要があります。 コード全体は以下のとおりですが、以降の節で各ファイルを個別に説明します。 docs/fiddles/ipc/pattern-2 ( 43.4.0 ) Open in Fiddle * main.js * preload.js * index.html * renderer.js const { app , BrowserWindow , ipcMain , dialog } = require ( 'electron/main' ) const path = require ( 'node:path' ) async function handleFileOpen ( ) { const { canceled , filePaths } = await dialog . showOpenDialog ( ) if ( ! canceled ) { return filePaths [ 0 ] } } function createWindow ( ) { const mainWindow = new BrowserWindow ( { webPreferences : { preload : path . join ( __dirname , 'preload.js' ) } } ) mainWindow . loadFile ( 'index.html' ) } app . whenReady ( ) . then ( ( ) => { ipcMain . handle ( 'dialog:openFile' , handleFileOpen ) createWindow ( ) app . on ( 'activate' , function ( ) { if ( BrowserWindow . getAllWindows ( ) . length === 0 ) createWindow ( ) } ) } ) app . on ( 'window-all-closed' , function ( ) { if ( process . platform !== 'darwin' ) app . quit ( ) } ) const { contextBridge , ipcRenderer } = require ( 'electron/renderer' ) contextBridge . exposeInMainWorld ( 'electronAPI' , { openFile : ( ) => ipcRenderer . invoke ( 'dialog:openFile' ) } ) <! DOCTYPE html > < html > < head > < meta charset = " UTF-8 " > <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --> < meta http-equiv = " Content-Security-Policy " content = " default-src 'self'; script-src 'self' " > < title > Dialog </ title > </ head > < body > < button type = " button " id = " btn " > Open a File </ button > File path: < strong id = " filePath " > </ strong > < script src = ' ./renderer.js ' > </ script > </ body > </ html > const btn = document . getElementById ( 'btn' ) const filePathElement = document . getElementById ( 'filePath' ) btn . addEventListener ( 'click' , async ( ) => { const filePath = await window . electronAPI . openFile ( ) filePathElement . innerText = filePath } ) 1. ipcMain.handle でイベントをリッスンする ​ メインプロセスでは、 dialog.showOpenDialog を呼び出してユーザーが選択したファイルパスの値を返す、 handleFileOpen() 関数を作成することになります。 This function is used as a callback whenever an ipcRenderer.invoke message is sent through the dialog:openFile channel from the renderer process. そして、その戻り値は元の invoke 呼び出しに対する Promise として返されます。 エラーハンドリングの小話 メインプロセスの handle から送出されたエラーはシリアライズされ、元のエラーのうち message プロパティのみがレンダラープロセスに提供されるため、不透過です。 詳細は #24427 をご参照ください。 main.js (Main Process) const { app , BrowserWindow , dialog , ipcMain } = require ( 'electron' ) const path = require ( 'node:path' ) // ... async function handleFileOpen ( ) { const { canceled , filePaths } = await dialog . showOpenDialog ( { } ) if ( ! canceled ) { return filePaths [ 0 ] } } function createWindow ( ) { const mainWindow = new BrowserWindow ( { webPreferences : { preload : path . join ( __dirname , 'preload.js' ) } } ) mainWindow . loadFile ( 'index.html' ) } app . whenReady ( ) . then ( ( ) => { ipcMain . handle ( 'dialog:openFile' , handleFileOpen ) createWindow ( ) } ) // ... チャンネル名について IPC チャンネル名の dialog: という接頭辞は、コードに効果をもたらすものではありません。 これはコードの可読性を向上する名前空間として機能するだけです。 info 次のステップで index.html と preload.js のエントリポイントをロードしていることを確認してください。 2. プリロード経由で ipcRenderer.invoke を公開する ​ プリロードスクリプトでは、 ipcRenderer.invoke('dialog:openFile') を呼び出してその値を返す、1 行の関数 openFile を公開しています。 次のステップでは、この API を使用することでレンダラーのユーザーインターフェースからネイティブのダイアログを呼び出します。 preload.js (Preload Script) const { contextBridge , ipcRenderer } = require ( 'electron' ) contextBridge . exposeInMainWorld ( 'electronAPI' , { openFile : ( ) => ipcRenderer . invoke ( 'dialog:openFile' ) } ) セキュリティ警告 We don't directly expose the whole ipcRenderer.invoke API for security reasons . レンダラーの Electron API へのアクセスをできるだけ制限するようにしてください。 3. レンダラープロセスの UI を構築する ​ 最後に、BrowserWindow に読み込む HTML ファイルを構築しましょう。 index.html <! DOCTYPE html > < html > < head > < meta charset = " UTF-8 " > <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --> < meta http-equiv = " Content-Security-Policy " content = " default-src 'self'; script-src 'self' " > < title > Dialog </ title > </ head > < body > < button type = " button " id = " btn " > Open a File </ button > File path: < strong id = " filePath " > </ strong > < script src = ' ./renderer.js ' > </ script > </ body > </ html > この UI は、プリロード API をトリガするために使う単一の #btn ボタン要素と、選択したファイルのパスを表示するために使う #filePath 要素で構成されます。 これらの部品を動作させるには、レンダラープロセスのスクリプトに以下の数行のコードを記述する必要があります。 renderer.js (Renderer Process) const btn = document . getElementById ( 'btn' ) const filePathElement = document . getElementById ( 'filePath' ) btn . addEventListener ( 'click' , async ( ) => { const filePath = await window . electronAPI . openFile ( ) filePathElement . innerText = filePath } ) 上記スニペットでは、 #btn ボタンのクリックをリッスンし、 window.electronAPI.openFile() API を呼び出してネイティブのファイルを開くダイアログをアクティブにしています。 そして、選択されたファイルパスを #filePath 要素に表示します。 注意: レガシーなアプローチ ​ ipcRenderer.invoke API は、レンダラープロセスから双方向 IPC に取りかかるための開発者向けの手段として Electron 7 で追加されました。 ただし、この IPC のパターンにはいくつかの代替アプローチが存在します。 できる限りレガシーなアプローチは避ける できる限り ipcRenderer.invoke の使用を推奨します。 以下のレンダラーからメインへの双方向パターンは、歴史的な目的のために文書化されたものです。 info 以下の例では、コードサンプルを小さく保つために、プリロードスクリプトから直接 ipcRenderer を呼び出しています。 ipcRenderer.send を使用する ​ 片方向通信で使用した ipcRenderer.send API は、双方向通信を行う際にも活用できます。 Electron 7 以前の IPC による非同期双方向通信では、この方法が推奨されていました。 preload.js (Preload Script) // このコードを `contextBridge` API を用いて // レンダラープロセスに公開することもできます。 const { ipcRenderer } = require ( 'electron' ) ipcRenderer . on ( 'asynchronous-reply' , ( _event , arg ) => { console . log ( arg ) // デベロッパー ツールのコンソールに「pong」と出力する } ) ipcRenderer . send ( 'asynchronous-message' , 'ping' ) main.js (Main Process) ipcMain . on ( 'asynchronous-message' , ( event , arg ) => { console . log ( arg ) // Node のコンソール「ping」と出力する // これは `send` のように動作しますが、メッセージの送信元の // レンダラーにメッセージを返します event . reply ( 'asynchronous-reply' , 'pong' ) } ) このアプローチには以下のようないくつかの欠点があります。 * レンダラープロセスでレスポンスを処理するために、2 つ目の ipcRenderer.on リスナーを用意する必要があります。 invoke ならば、元の API コールに対して Promise として返されるレスポンスの値を得られます。 * asynchronous-reply メッセージが元の asynchronous-message メッセージとペアであると明示する方法がありません。 これらのチャンネルで非常に頻繁にメッセージが行き来する場合、各コールとレスポンスを個別に追跡することになり、さらなるアプリコードを追加する必要があります。 ipcRenderer.sendSync を使用する ​ ipcRenderer.sendSync API は、メインプロセスにメッセージを送信し、応答を 同期的に 待機します。 main.js (Main Process) const { ipcMain } = require ( 'electron' ) ipcMain . on ( 'synchronous-message' , ( event , arg ) => { console . log ( arg ) // Node のコンソールに「ping」と出力する event . returnValue = 'pong' } ) preload.js (Preload Script) // このコードを `contextBridge` API を用いて // レンダラープロセスに公開することもできます const { ipcRenderer } = require ( 'electron' ) const result = ipcRenderer . sendSync ( 'synchronous-message' , 'ping' ) console . log ( result ) // デベロッパー ツールのコンソールに「pong」と出力する このコードの構造は invoke のモデルと非常に似ていますが、パフォーマンス上の理由から この API は避ける ことを推奨します。 これは同期的であるため、応答があるまでレンダラープロセスをブロックしてしまいます。 パターン 3: メインからレンダラーへ ​ メインプロセスからレンダラープロセスにメッセージを送信する場合、どのレンダラーがメッセージを受信するかを指定する必要があります。 Messages need to be sent to a renderer process via its WebContents instance. This WebContents instance contains a send method that can be used in the same way as ipcRenderer.send . このパターンを実証するために、オペレーティングシステムのネイティブメニューで制御される数値カウンターを構築することにします。 このデモでは、メインプロセス、レンダラープロセス、プリロードスクリプトにコードを追加する必要があります。 コード全体は以下のとおりですが、以降の節で各ファイルを個別に説明します。 docs/fiddles/ipc/pattern-3 ( 43.4.0 ) Open in Fiddle * main.js * preload.js * index.html * renderer.js const { app , BrowserWindow , Menu , ipcMain } = require ( 'electron/main' ) const path = require ( 'node:path' ) function createWindow ( ) { const mainWindow = new BrowserWindow ( { webPreferences : { preload : path . join ( __dirname , 'preload.js' ) } } ) const menu = Menu . buildFromTemplate ( [ { label : app . name , submenu : [ { click : ( ) => mainWindow . webContents . send ( 'update-counter' , 1 ) , label : 'Increment' } , { click : ( ) => mainWindow . webContents . send ( 'update-counter' , - 1 ) , label : 'Decrement' } ] } ] ) Menu . setApplicationMenu ( menu ) mainWindow . loadFile ( 'index.html' ) // Open the DevTools. mainWindow . webContents . openDevTools ( ) } app . whenReady ( ) . then ( ( ) => { ipcMain . on ( 'counter-value' , ( _event , value ) => { console . log ( value ) // will print value to Node console } ) createWindow ( ) app . on ( 'activate' , function ( ) { if ( BrowserWindow . getAllWindows ( ) . length === 0 ) createWindow ( ) } ) } ) app . on ( 'window-all-closed' , function ( ) { if ( process . platform !== 'darwin' ) app . quit ( ) } ) const { contextBridge , ipcRenderer } = require ( 'electron/renderer' ) contextBridge . exposeInMainWorld ( 'electronAPI' , { onUpdateCounter : ( callback ) => ipcRenderer . on ( 'update-counter' , ( _event , value ) => callback ( value ) ) , counterValue : ( value ) => ipcRenderer . send ( 'counter-value' , value ) } ) <! DOCTYPE html > < html > < head > < meta charset = " UTF-8 " > <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP --> < meta http-equiv = " Content-Security-Policy " content = " default-src 'self'; script-src 'self' " > < title > Menu Counter </ title > </ head > < body > Current value: < strong id = " counter " > 0 </ strong > < script src = " ./renderer.js " > </ script > </ body > </ html > const counter = document . getElementById ( 'counter' ) window . electronAPI . onUpdateCounter ( ( value ) => { const oldValue = Number ( counter . innerText ) const newValue = oldValue + value counter . innerText = newValue . toString ( ) window . electronAPI . counterValue ( newValue ) } ) 1. webContents モジュールでメッセージを送信する ​ このデモでは、まず Electron の Menu モジュールを使い、メインプロセスでカスタムメニューを作成します。このモジュールは webContents.send API を使ってメインプロセスからターゲットレンダラーに IPC メッセージを送信します。 main.js (Main Process) const { app , BrowserWindow , Menu , ipcMain } = require ( 'electron' ) const path = require ( 'node:path' ) function createWindow ( ) { const mainWindow = new BrowserWindow ( { webPreferences : { preload : path . join ( __dirname , 'preload.js' ) } } ) const menu = Menu . buildFromTemplate ( [ { label : app . name , submenu : [ { click : ( ) => mainWindow . webContents . send ( 'update-counter' , 1 ) , label : 'Increment' } , { click : ( ) => mainWindow . webContents . send ( 'update-counter' , - 1 ) , label : 'Decrement' } ] } ] ) Menu . setApplicationMenu ( menu ) mainWindow . loadFile ( 'index.html' ) } // ... このチュートリアルで重要なのは、 click ハンドラが update-counter チャンネルを介してメッセージ ( 1 または -1 ) をレンダラープロセスに送信することです。 click : ( ) => mainWindow . webContents . send ( 'update-counter' , - 1 ) info 次のステップで index.html と preload.js のエントリポイントをロードしていることを確認してください。 2. プリロード経由で ipcRenderer.on を公開する ​ 以前のレンダラーからメインへのサンプルのように、プリロードスクリプトで contextBridge と ipcRenderer モジュールを使用し、IPC 機能をレンダラープロセスに公開します。 preload.js (Preload Script) const { contextBridge , ipcRenderer } = require ( 'electron' ) contextBridge . exposeInMainWorld ( 'electronAPI' , { onUpdateCounter : ( callback ) => ipcRenderer . on ( 'update-counter' , ( _event , value ) => callback ( value ) ) } ) プリロードスクリプトのロード後、レンダラープロセスは window.electronAPI.onUpdateCounter() リスナー関数にアクセスできるようになるでしょう。 セキュリティ警告 We don't directly expose the whole ipcRenderer.on API for security reasons . レンダラーの Electron API へのアクセスをできるだけ制限するようにしてください。 また、コールバックを単に ipcRenderer.on へ渡さないでください。これは event.sender を介して ipcRenderer を漏洩してしまいます。 Use a custom handler that invokes the callback only with

Links found on this page

  1. メインコンテンツへ飛ぶ [direct]
  2. Electron [direct]
  3. ドキュメント [direct]
  4. API [direct]
  5. ブログ [direct]
  6. Electron Forge [direct]
  7. Electron Fiddle [direct]
  8. ガバナンス [direct]
  9. 事例紹介 [direct]
  10. リソース [direct]
  11. リリース [direct]
  12. English [direct]
  13. Deutsch [direct]
  14. Español [direct]
  15. Français [direct]
  16. Português [direct]
  17. Русский [direct]
  18. 中文 [direct]
  19. Electron のプロセス [direct]
  20. コンテキストの分離 [direct]
  21. プロセスのサンドボックス化 [direct]
  22. Electron での MessagePort [direct]
  23. ベストプラクティス [direct]
  24. サンプル [direct]
  25. 開発 [direct]
  26. Native Node Modules [direct]
  27. 配布方法 [direct]
  28. テストとデバッグ [direct]
  29. リファレンス [direct]
  30. コントリビューション [direct]
  31. ipcMain [direct]
  32. ipcRenderer [direct]
  33. docs/fiddles/ipc/pattern-1 ( 43.4.0 ) [direct]
  34. Open in Fiddle [direct]
  35. IpcMainEvent [direct]
  36. docs/fiddles/ipc/pattern-2 ( 43.4.0 ) [direct]
  37. Open in Fiddle [direct]
  38. #24427 [direct]
  39. WebContents [direct]
  40. docs/fiddles/ipc/pattern-3 ( 43.4.0 ) [direct]
  41. Open in Fiddle [direct]
  42. 構造化複製アルゴリズム [direct]
  43. このページを編集 [direct]
  44. セキュリティ [direct]
  45. Discord [direct]
  46. Bluesky [direct]
  47. X [direct]
  48. Mastodon [direct]
  49. Stack Overflow [direct]
  50. GitHub [direct]
  51. Open Collective [direct]
  52. Infrastructure Dashboard [direct]
  53. OpenJS Foundation [direct]
  54. Trademark Policy [direct]
  55. Trademark List [direct]
  56. Terms of Use [direct]
  57. Privacy Policy [direct]
  58. Bylaws [direct]
  59. Code of Conduct [direct]
  60. Cookie Policy [direct]