Electron 进程间通信原理与实践指南

本文首发同名微信公众号:前端徐徐

大家好,我是徐徐。今天我们要讲的是 Electron 中的进程间通信(IPC)。

前言

在开发 Electron 项目的时候我们会经常遇到进程间通信的场景,进程间通信也是传统桌面端开发中非常常见的场景,我们在这里不仅仅是讲如何使用 IPC,我们更多的是通过源码去了解 IPC 底层的实现,然后通过了解原理去更好的解决我们在实际场景中可能会遇到的一些问题以及一些注意事项。

IPC 原理

我们在使用 IPC 的时候可以看看它是如何实现的,主要是看 ipcMainipcRenderer 这两个的实现。

ipcMain

源码路径:GitHub1s

import { IpcMainInvokeEvent } from 'electron/main';

import { EventEmitter } from 'events';

export class IpcMainImpl extends EventEmitter implements Electron.IpcMain {
  private _invokeHandlers: Map<string, (e: IpcMainInvokeEvent, ...args: any[]) => void> = new Map();

  constructor () {
    super();

    // Do not throw exception when channel name is "error".
    this.on('error', () => {});
  }

  handle: Electron.IpcMain['handle'] = (method, fn) => {
    if (this._invokeHandlers.has(method)) {
      throw new Error(`Attempted to register a second handler for '${method}'`);
    }
    if (typeof fn !== 'function') {
      throw new TypeError(`Expected handler to be a function, but found type '${typeof fn}'`);
    }
    this._invokeHandlers.set(method, fn);
  };

  handleOnce: Electron.IpcMain['handleOnce'] = (method, fn) => {
    this.handle(method, (e, ...args) => {
      this.removeHandler(method);
      return fn(e, ...args);
    });
  };

  removeHandler (method: string) {
    this._invokeHandlers.delete(method);
  }
}

这个 IpcMainImpl 类提供了一个简单的 IPC 处理机制,允许你注册处理函数,管理它们的生命周期(如一次性处理),并确保在错误发生时不会抛出异常。它将所有的 IPC 处理函数保存在一个 Map 中,方便查找和管理。这个实现可以用作 Electron 应用的 IPC 通信的基础,提供了灵活且安全的处理方式。

我们可以看到IpcMainImpl 类继承自 EventEmitter,这使得它可以使用事件触发和监听的功能。以下是事件机制的几个关键点:

  1. 事件触发:在 IpcMainImpl 中,你可以通过 this.emit(eventName, args) 来触发特定的事件,像是错误事件(error)。
  2. 事件监听:通过 this.on(eventName, listener) 方法,你可以注册监听器,处理特定事件的发生。在构造函数中,注册了一个 error 事件的监听器,以避免在处理 IPC 时抛出异常。
  3. 异步处理:通过事件机制,处理函数可以在事件被触发时异步执行,这对于 IPC 通信非常重要,因为它允许主进程和渲染进程之间的消息传递,而不会阻塞其他操作。
  4. 灵活性:事件机制提供了高度的灵活性,你可以根据需要添加、移除或修改事件处理函数,使得 IPC 的管理更加动态。

事件机制为 IpcMainImpl 提供了一个强大的基础,支持异步通信和事件驱动的编程模式。

ipcRenderer

源码路径:GitHub1s

import { EventEmitter } from 'events';

const { ipc } = process._linkedBinding('electron_renderer_ipc');

const internal = false;
class IpcRenderer extends EventEmitter implements Electron.IpcRenderer {
  send (channel: string, ...args: any[]) {
    return ipc.send(internal, channel, args);
  }

  sendSync (channel: string, ...args: any[]) {
    return ipc.sendSync(internal, channel, args);
  }

  sendToHost (channel: string, ...args: any[]) {
    return ipc.sendToHost(channel, args);
  }

  async invoke (channel: string, ...args: any[]) {
    const { error, result } = await ipc.invoke(internal, channel, args);
    if (error) {
      throw new Error(`Error invoking remote method '${channel}': ${error}`);
    }
    return result;
  }

  postMessage (channel: string, message: any, transferables: any) {
    return ipc.postMessage(channel, message, transferables);
  }
}

export default new IpcRenderer();

可以看到 ipcRenderer 的实现也是基于事件的,IpcRenderer 类继承自 EventEmitter,使得它能够利用事件触发和监听的功能。在 Electron 中,IPC 通信的许多操作都是基于事件驱动的。这些事件可以包括消息的发送、接收、错误处理等。了解了原理之后,我们来看看一些 IPC 的实践。

IPC 实践

在开始实践之前,我们需要理清楚在 Electron 中有多少种通信的场景,可以看下面这张图。

在实现进程通信的方法之前,我们需要先了解一下Electron的上下文隔离。这里就不做过多的解释了,Electron的官方网站有相应的解释:上下文隔离 | Electron

了解上下文隔离主要是为了在开始进行跨进程通信之前实现 preload 脚本打下基础,不然不会了解 Electron 中的流程模型,这些知识点都是一个个窜起来的,所以在开始之

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值