How Electron’s remote Module Mimics Java RMI to Seamlessly Share Objects Between Processes
This article explains the two inter‑process communication methods in Electron, demonstrates why the remote module behaves like a reference rather than a copy, and reveals its internal implementation that mirrors Java RMI by wrapping objects and functions as remote proxies using synchronous IPC messages.
Two Process Communication Methods in Electron
Electron provides two ways to communicate between the main and renderer processes: using the
ipcMainand
ipcRenderermodules, or using the
remotemodule.
Using IPC Modules
Example main‑process code:
<code>const remoteObj = { name: 'remote' };
const getRemoteObject = event => {
// after 1 second modify the object
setTimeout(() => {
remoteObj.name = 'modified name';
win.webContents.send('modified');
}, 1000);
event.returnValue = remoteObj;
};
app.getRemoteObject = getRemoteObject;</code>Renderer‑process code:
<code>const { ipcRenderer } = require('electron');
const container = document.querySelector('#container');
const remoteObj = ipcRenderer.sendSync('getRemoteObject');
container.innerText = `Before modified\n${JSON.stringify(remoteObj, null, ' ')}`;
ipcRenderer.on('modified', () => {
container.innerText = `After modified\n${JSON.stringify(remoteObj, null, ' ')}`;
});</code>The output shows that the renderer receives a copy of the object; after the main process changes the value, the renderer’s copy does not update automatically.
Using the remote Module
Example main‑process code:
<code>const remoteObj = { name: 'remote' };
const getRemoteObject = event => {
setTimeout(() => {
remoteObj.name = 'modified name';
win.webContents.send('modified');
}, 1000);
return remoteObj;
};
app.getRemoteObject = getRemoteObject;</code>Renderer‑process code:
<code>const { remote } = require('electron');
const remoteObj = remote.app.getRemoteObject();
// remoteObj now references the same object in the main process
</code>The result shows that the renderer can access the same object instance, and changes made in the main process are reflected instantly.
Why remote Works Like RMI
The
remotemodule still uses IPC under the hood, but it converts every transferred
Objectinto a remote reference, similar to Java’s Remote Method Invocation (RMI). This hides the message‑passing details from developers.
Java RMI Overview
RMI allows a client to invoke methods on a remote object as if it were local. It uses a registry (like DNS) to bind a name to a server‑side object and communicates via the JRMP protocol.
Simple RMI implementation in Java:
<code>public interface HelloRMI extends Remote {
String sayHi(String name) throws RemoteException;
}
public class HelloRMIImpl extends UnicastRemoteObject implements HelloRMI {
protected HelloRMIImpl() throws RemoteException { super(); }
public String sayHi(String name) throws RemoteException {
System.out.println("Server: Hi " + name + " " + getClientHost());
return "Server";
}
}
public class Server {
public static void main(String[] args) {
try {
HelloRMI hr = new HelloRMIImpl();
LocateRegistry.createRegistry(9999);
Naming.bind("rmi://localhost:9999/hello", hr);
System.out.println("RMI Server bind success");
} catch (Exception e) { e.printStackTrace(); }
}
}
public class Client {
public static void main(String[] args) {
try {
HelloRMI hr = (HelloRMI) Naming.lookup("rmi://localhost:9999/hello");
System.out.println("Client: Hi " + hr.sayHi("Client"));
} catch (Exception e) { e.printStackTrace(); }
}
}
</code>Electron remote Implementation Details
The remote module adds built‑in properties that forward requests to the main process via synchronous IPC messages.
<code>const addBuiltinProperty = name => {
Object.defineProperty(exports, name, {
get: () => exports.getBuiltin(name)
});
};
const browserModules = require('../../common/api/module-list')
.concat(require('../../browser/api/module-list'))
.filter(m => !m.private)
.map(m => m.name)
.forEach(addBuiltinProperty);
exports.getBuiltin = module => {
const command = 'ELECTRON_BROWSER_GET_BUILTIN';
const meta = ipcRenderer.sendSync(command, module);
return metaToValue(meta);
};
function metaToValue(meta) {
const types = {
value: () => meta.value,
array: () => meta.members.map(member => metaToValue(member)),
buffer: () => bufferUtils.metaToBuffer(meta.value),
promise: () => resolvePromise({ then: metaToValue(meta.then) }),
error: () => { throw metaToException(meta); },
date: () => new Date(meta.value),
exception: () => { throw metaToException(meta); }
};
if (meta.type in types) return types[meta.type]();
if (remoteObjectCache.has(meta.id)) return remoteObjectCache.get(meta.id);
if (meta.type === 'function') {
const remoteFunction = function(...args) {
const command = (this && this.constructor === remoteFunction)
? 'ELECTRON_BROWSER_CONSTRUCTOR'
: 'ELECTRON_BROWSER_FUNCTION_CALL';
const obj = ipcRenderer.sendSync(command, meta.id, wrapArgs(args));
return metaToValue(obj);
};
const ret = remoteFunction;
setObjectMembers(ret, ret, meta.id, meta.members);
setObjectPrototype(ret, ret, meta.id, meta.proto);
Object.defineProperty(ret.constructor, 'name', { value: meta.name });
v8Util.setRemoteObjectFreer(ret, meta.id);
v8Util.setHiddenValue(ret, 'atomId', meta.id);
remoteObjectCache.set(meta.id, ret);
return ret;
}
// object handling
const ret = {};
setObjectMembers(ret, ret, meta.id, meta.members);
setObjectPrototype(ret, ret, meta.id, meta.proto);
v8Util.setRemoteObjectFreer(ret, meta.id);
v8Util.setHiddenValue(ret, 'atomId', meta.id);
remoteObjectCache.set(meta.id, ret);
return ret;
}
</code>Both objects and functions are cached in
remoteObjectCacheso that repeated accesses return the same proxy, providing reference‑like behavior.
Conclusion
The
remotemodule not only abstracts IPC but also wraps main‑process objects into remote proxies, achieving reference semantics similar to Java RMI. This explains why modifications in the main process are instantly visible in the renderer and why the module can expose main‑process APIs without explicit message handling.
Tencent IMWeb Frontend Team
IMWeb Frontend Community gathering frontend development enthusiasts. Follow us for refined live courses by top experts, cutting‑edge technical posts, and to sharpen your frontend skills.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.