Appearance
Kapitel 6: Electron-Prozesskommunikation (IPC) im Detail
In diesem Kapitel lernen Sie die IPC-Kommunikation in Electron im Detail kennen - ein Kernkonzept für die Entwicklung.
6.1 IPC-Kernmodule (ipcMain, ipcRenderer)
Grundlegendes Konzept
IPC (Inter-Process Communication) ermöglicht den Datenaustausch zwischen Haupt- und Renderer-Prozess.
┌─────────────────┐ IPC ┌─────────────────┐
│ Renderer │ ◄─────────────────► │ Hauptprozess │
│ (Renderer) │ Nachrichten │ (Main) │
└─────────────────┘ └─────────────────┘
│ │
│ ipcRenderer │ ipcMain
│ (Renderer-Seite) │ (Main-Seite)ipcMain-Modul (Hauptprozess)
javascript
// main.js
const { ipcMain } = require('electron');
// Nachricht vom Renderer empfangen
ipcMain.on('channel-name', (event, ...args) => {
console.log('Nachricht empfangen:', ...args);
});
// Antwort an Renderer senden
ipcMain.on('get-data', (event, data) => {
event.reply('data-response', { result: 'Daten vom Hauptprozess' });
});
// Handle fürinvoke (Promise-basiert)
ipcMain.handle('do-something', async (event, ...args) => {
const result = await someAsyncOperation(...args);
return result; // Wird zum Renderer zurückgegeben
});ipcRenderer-Modul (Renderer-Prozess)
javascript
// preload.js oder renderer.js (mit nodeIntegration: true)
const { ipcRenderer } = require('electron');
// Nachricht an Hauptprozess senden
ipcRenderer.send('channel-name', 'Hallo Hauptprozess!');
// Antwort vom Hauptprozess empfangen
ipcRenderer.on('response-channel', (event, data) => {
console.log('Antwort:', data);
});
// Handle aufrufen (Promise-basiert, empfohlen)
async function doSomething() {
const result = await ipcRenderer.invoke('do-something', 'Parameter');
console.log('Ergebnis:', result);
}6.2 Einweg-Kommunikation
Renderer → Hauptprozess (einweg)
javascript
// preload.js (sichere Brücke)
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('api', {
sendToMain: (channel, data) => {
ipcRenderer.send(channel, data);
}
});javascript
// main.js
ipcMain.on('renderer-message', (event, data) => {
console.log('Vom Renderer:', data);
// Keine Antwort erforderlich
});html
<!-- renderer/index.html -->
<script>
// Nachricht senden (einweg)
window.api.sendToMain('renderer-message', { text: 'Hallo!' });
</script>Hauptprozess → Renderer (einweg)
javascript
// main.js
const win = new BrowserWindow({ /* ... */ });
// Nachricht an Renderer senden
win.webContents.send('main-message', { text: 'Hallo vom Hauptprozess!' });javascript
// preload.js
contextBridge.exposeInMainWorld('api', {
onMainMessage: (callback) => {
ipcRenderer.on('main-message', (event, data) => {
callback(data);
});
}
});html
<!-- renderer/index.html -->
<script>
// Auf Nachricht vom Hauptprozess hören
window.api.onMainMessage((data) => {
console.log('Vom Hauptprozess:', data);
});
</script>6.3 Zweiweg-Kommunikation (Request-Response-Muster)
invoke/handle (empfohlene Methode)
javascript
// main.js - Handler definieren
ipcMain.handle('get-user-data', async (event, userId) => {
// Daten abrufen (z.B. aus Datenbank)
const userData = await fetchUserData(userId);
return userData; // Wird zum Renderer zurückgegeben
});
ipcMain.handle('save-file', async (event, content) => {
try {
await fs.promises.writeFile('data.txt', content);
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
});javascript
// preload.js - Sichere Brücke
contextBridge.exposeInMainWorld('api', {
getUserData: (userId) => ipcRenderer.invoke('get-user-data', userId),
saveFile: (content) => ipcRenderer.invoke('save-file', content)
});html
<!-- renderer/index.html -->
<script>
async function loadUser() {
try {
const userData = await window.api.getUserData(123);
console.log('Benutzerdaten:', userData);
} catch (error) {
console.error('Fehler:', error);
}
}
async function save() {
const result = await window.api.saveFile('Hallo Welt');
if (result.success) {
alert('Datei gespeichert!');
} else {
alert('Fehler: ' + result.error);
}
}
</script>Send/Reply (Alternative Methode)
javascript
// main.js
ipcMain.on('get-config', (event, key) => {
const config = getConfig(key);
event.reply('config-response', config);
});javascript
// renderer.js
ipcRenderer.send('get-config', 'theme');
ipcRenderer.on('config-response', (event, config) => {
console.log('Konfiguration:', config);
});6.4 Häufige Methoden zum Senden und Empfangen
Übersicht der IPC-Methoden
| Methode | Prozess | Richtung | Rückgabe | Verwendung |
|---|---|---|---|---|
send() | Renderer | → Main | Keine | Einweg-Nachricht senden |
on() | Main/Renderer | ← | Keine | Nachricht empfangen |
invoke() | Renderer | ↔ Main | Promise | Zweiweg-Kommunikation (empfohlen) |
handle() | Main | ↔ | Promise | Handler für invoke definieren |
event.reply() | Main | → Renderer | Keine | Antwort auf send() senden |
event.sender.send() | Main | → Renderer | Keine | An Renderer senden |
Detaillierte Erklärungen
send() und on()
javascript
// Renderer → Main (einweg)
ipcRenderer.send('channel', data1, data2);
// Main empfängt
ipcMain.on('channel', (event, data1, data2) => {
console.log(data1, data2);
// Antwort senden
event.reply('response-channel', 'Antwort');
});
// Renderer empfängt Antwort
ipcRenderer.on('response-channel', (event, response) => {
console.log(response);
});invoke() und handle() (modern & empfohlen)
javascript
// Main: Handler registrieren
ipcMain.handle('calculate', async (event, a, b) => {
const result = a + b;
return result; // Promise wird automatisch aufgelöst
});
// Renderer: Aufrufen
async function calculate() {
try {
const result = await ipcRenderer.invoke('calculate', 5, 3);
console.log('Ergebnis:', result); // 8
} catch (error) {
console.error('Fehler:', error);
}
}event.sender.send()
javascript
// Main: Antwort an spezifischen Renderer senden
ipcMain.on('request', (event, data) => {
// Antwort an den sendenden Renderer
event.sender.send('response', { result: 'Daten' });
});6.5 Praxisbeispiel: Renderer sendet Befehl, Hauptprozess führt aus und gibt Ergebnis zurück
Projekt: Datei-Explorer mit IPC
main.js (Hauptprozess)
javascript
const { app, BrowserWindow, ipcMain } = require('electron');
const fs = require('fs').promises;
const path = require('path');
function createWindow() {
const win = new BrowserWindow({
width: 1000,
height: 700,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false
}
});
win.loadFile('index.html');
}
// Dateiliste abrufen
ipcMain.handle('list-files', async (event, dirPath) => {
try {
const files = await fs.readdir(dirPath);
const fileStats = await Promise.all(
files.map(async (file) => {
const filePath = path.join(dirPath, file);
const stats = await fs.stat(filePath);
return {
name: file,
path: filePath,
isDirectory: stats.isDirectory(),
size: stats.size,
modified: stats.mtime
};
})
);
return { success: true, files: fileStats };
} catch (error) {
return { success: false, error: error.message };
}
});
// Datei lesen
ipcMain.handle('read-file', async (event, filePath) => {
try {
const content = await fs.readFile(filePath, 'utf-8');
return { success: true, content };
} catch (error) {
return { success: false, error: error.message };
}
});
// Datei schreiben
ipcMain.handle('write-file', async (event, filePath, content) => {
try {
await fs.writeFile(filePath, content, 'utf-8');
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
});
app.whenReady().then(createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});preload.js (IPC-Brücke)
javascript
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('fileAPI', {
listFiles: (dirPath) => ipcRenderer.invoke('list-files', dirPath),
readFile: (filePath) => ipcRenderer.invoke('read-file', filePath),
writeFile: (filePath, content) => ipcRenderer.invoke('write-file', filePath, content)
});index.html (UI)
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Datei-Explorer</title>
<style>
body { font-family: Arial; padding: 20px; }
.toolbar { margin-bottom: 10px; }
button { padding: 8px 16px; margin-right: 5px; }
#fileList { border: 1px solid #ccc; padding: 10px; height: 300px; overflow-y: auto; }
.file-item { padding: 5px; cursor: pointer; }
.file-item:hover { background: #f0f0f0; }
.directory { font-weight: bold; color: blue; }
#editor { width: 100%; height: 200px; margin-top: 10px; }
</style>
</head>
<body>
<h1>Datei-Explorer</h1>
<div class="toolbar">
<input type="text" id="dirPath" value="." style="width: 300px;">
<button onclick="loadDirectory()">Laden</button>
</div>
<div id="fileList"></div>
<h3>Datei-Editor</h3>
<textarea id="editor" placeholder="Dateiinhalt hier bearbeiten..."></textarea>
<div>
<button onclick="saveFile()">Speichern</button>
<span id="status"></span>
</div>
<script>
let currentFilePath = null;
// Verzeichnis laden
async function loadDirectory() {
const dirPath = document.getElementById('dirPath').value;
const result = await window.fileAPI.listFiles(dirPath);
if (result.success) {
displayFiles(result.files);
} else {
alert('Fehler: ' + result.error);
}
}
// Dateien anzeigen
function displayFiles(files) {
const fileList = document.getElementById('fileList');
fileList.innerHTML = '';
files.forEach(file => {
const div = document.createElement('div');
div.className = 'file-item ' + (file.isDirectory ? 'directory' : '');
div.textContent = (file.isDirectory ? '📁 ' : '📄 ') + file.name;
div.onclick = () => openFile(file);
fileList.appendChild(div);
});
}
// Datei öffnen
async function openFile(file) {
if (file.isDirectory) {
document.getElementById('dirPath').value = file.path;
loadDirectory();
} else {
const result = await window.fileAPI.readFile(file.path);
if (result.success) {
document.getElementById('editor').value = result.content;
currentFilePath = file.path;
document.getElementById('status').textContent = 'Datei geöffnet: ' + file.name;
} else {
alert('Fehler: ' + result.error);
}
}
}
// Datei speichern
async function saveFile() {
if (!currentFilePath) {
alert('Keine Datei geöffnet!');
return;
}
const content = document.getElementById('editor').value;
const result = await window.fileAPI.writeFile(currentFilePath, content);
if (result.success) {
document.getElementById('status').textContent = 'Datei gespeichert!';
} else {
alert('Fehler: ' + result.error);
}
}
// Beim Laden
loadDirectory();
</script>
</body>
</html>6.6 Häufige Anfängerfehler
Fehler 1: IPC-Berechtigungsprobleme
Symptom: ipcRenderer is not defined
Ursache: contextIsolation: true (Standard) verhindert direkten Zugriff.
Lösung: Preload-Skript verwenden
javascript
// main.js - RICHTIG
const win = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js'), // WICHTIG!
contextIsolation: true, // Sicherheit: EIN
nodeIntegration: false // Sicherheit: AUS
}
});Fehler 2: Nachrichten-Listener vergessen
Fehler:
javascript
// Main sendet, aber Renderer hört nicht
win.webContents.send('data', 'Hallo');
// Vergessen: ipcRenderer.on('data', ...) im RendererLösung: Listener immer registrieren
javascript
// preload.js
contextBridge.exposeInMainWorld('api', {
onData: (callback) => {
ipcRenderer.on('data', (event, data) => callback(data));
}
});Fehler 3: Kanalnamen vertippen
Fehler:
javascript
// Main
ipcMain.on('get-data', ...);
// Renderer
ipcRenderer.send('getdate'); // Falsch geschrieben!Lösung: Konstanten verwenden
javascript
// channels.js
export const Channels = {
GET_DATA: 'get-data',
SAVE_FILE: 'save-file'
};
// Main und Renderer importieren channels.jsFehler 4: Speicherlecks durch mehrfache Listener
Fehler:
javascript
// Jedes Mal wenn Komponente gerendert wird
function MyComponent() {
useEffect(() => {
ipcRenderer.on('channel', handler); // Mehrmals registriert!
}, []);
}Lösung: Listener entfernen
javascript
useEffect(() => {
const handler = (event, data) => { /* ... */ };
ipcRenderer.on('channel', handler);
// Cleanup
return () => {
ipcRenderer.removeListener('channel', handler);
};
}, []);Zusammenfassung
In diesem Kapitel haben Sie gelernt:
ipcMainundipcRendererModule zu verwenden- Einweg- und Zweiweg-Kommunikation zu implementieren
invoke/handle(modern) undsend/on(klassisch) zu nutzen- Häufige IPC-Methoden zu verstehen
- Ein praktisches IPC-Beispiel zu implementieren
- Häufige IPC-Fehler zu vermeiden
Im nächsten Kapitel werden wir Electron-native Funktionen behandeln.
