傲慢让别人无法来爱我,偏见让我无法去爱别人。——简·奥斯汀《傲慢与偏见》

很简单啊,先创建项目:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
(base) 192:WebstormProjects achao$ npx create-electron-app@latest simple-electron
Need to install the following packages:
create-electron-app@7.8.0
Ok to proceed? (y)

✔ Resolving package manager: npm
✔ Resolving template: base
› Using @electron-forge/template-base (local module)
✔ Initializing directory
✔ Initializing git repository
✔ Preparing template
✔ Initializing template
✔ Installing template dependencies
(base) 192:WebstormProjects achao$ cd simple-electron/
(base) 192:simple-electron achao$ npm i

up to date, audited 468 packages in 1s

73 packages are looking for funding
run `npm fund` for details

found 0 vulnerabilities
(base) 192:simple-electron achao$ npm run start

> simple-electron@1.0.0 start
> electron-forge start

✔ Checking your system
✔ Locating application
✔ Loading configuration
✔ Preparing native dependencies [0.1s]
✔ Running generateAssets hook
✔ Running preStart hook

2025-03-25 18:04:52.907 Electron[28401:8132103] +[IMKClient subclass]: chose IMKClient_Modern
2025-03-25 18:04:52.907 Electron[28401:8132103] +[IMKInputSession subclass]: chose IMKInputSession_Modern
[28401:0325/180452.984208:ERROR:CONSOLE(1)] "Request Autofill.enable failed. {"code":-32601,"message":"'Autofill.enable' wasn't found"}", source: devtools://devtools/bundled/core/protocol_client/protocol_client.js (1)
[28401:0325/180452.984249:ERROR:CONSOLE(1)] "Request Autofill.setAddresses failed. {"code":-32601,"message":"'Autofill.setAddresses' wasn't found"}", source: devtools://devtools/bundled/core/protocol_client/protocol_client.js (1)
^C✔ Locating application
✔ Loading configuration
✔ Preparing native dependencies [0.1s]
✔ Running generateAssets hook
✔ Running preStart hook
✔ Checking your system
(base) 192:simple-electron achao$

修改index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>simple-electron</title>
</head>
<body>
<h1>调用 C++ 模块</h1>
<button id="add">调用 add(1, 2)</button>
<button id="multiply">调用 multiply(2, 3)</button>
<p id="result"></p>

<script>
document.getElementById('add').onclick = async () => {
const result = await window.api.add(1, 2);
document.getElementById('result').textContent = `Add Result: ${result}`;
};

document.getElementById('multiply').onclick = async () => {
const result = await window.api.multiply(2, 3);
document.getElementById('result').textContent = `Multiply Result: ${result}`;
};
</script>
</body>
</html>

以及index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
const {app, BrowserWindow, Menu, ipcMain} = require('electron');
const path = require('node:path');
const native = require('../build/Release/hello.node');

// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require('electron-squirrel-startup')) {
app.quit();
}

console.log("app name", app.getName())

const template = [
{
label: '文件(F)',
role: 'services',
submenu: [
{
label: '',
}
]
}
]

const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)

const createWindow = () => {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 1200,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
},
});

// and load the index.html of the app.
mainWindow.loadFile(path.join(__dirname, 'index.html'));

// Open the DevTools.
// mainWindow.webContents.openDevTools();
};

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.whenReady().then(() => {
createWindow();

// ✅ 注册 handler!
ipcMain.handle('add', (_, a, b) => {
console.log('native.add called with:', a, b);
return native.add(a, b);
});

ipcMain.handle('multiply', (_, a, b) => {
console.log('native.multiply called with:', a, b);
return native.multiply(a, b);
});

// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});

// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and import them here.

这里放上我们的c++代码src/hello.cc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <napi.h>

Napi::Number Add(const Napi::CallbackInfo& info) {
double a = info[0].As<Napi::Number>().DoubleValue();
double b = info[1].As<Napi::Number>().DoubleValue();
return Napi::Number::New(info.Env(), a + b);
}

Napi::Number Multiply(const Napi::CallbackInfo& info) {
double a = info[0].As<Napi::Number>().DoubleValue();
double b = info[1].As<Napi::Number>().DoubleValue();
return Napi::Number::New(info.Env(), a * b);
}

Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set("add", Napi::Function::New(env, Add));
exports.Set("multiply", Napi::Function::New(env, Multiply));
return exports;
}

NODE_API_MODULE(hello, Init)

然后是preload.js

1
2
3
4
5
6
7
8
// See the Electron documentation for details on how to use preload scripts:
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('api', {
add: (a, b) => ipcRenderer.invoke('add', a, b),
multiply: (a, b) => ipcRenderer.invoke('multiply', a, b),
});

还要在项目根目录添加binding.gyp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"targets": [
{
"target_name": "hello",
"sources": [ "src/hello.cc" ],
"include_dirs": [
"node_modules/node-addon-api"
],
"defines": [ "NODE_ADDON_API_CPP_EXCEPTIONS" ],
"cflags_cc": [ "-std=c++17", "-fexceptions" ],
"xcode_settings": {
"GCC_ENABLE_CPP_EXCEPTIONS": "YES",
"CLANG_CXX_LIBRARY": "libc++",
"MACOSX_DEPLOYMENT_TARGET": "10.15"
}
}
]
}

安装node-gyp并编译c++模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
(base) 192:simple-electron achao$ npm install --save-dev node-gyp
npm install --save-dev node-gyp

added 60 packages, and audited 528 packages in 11s

81 packages are looking for funding
run `npm fund` for details

found 0 vulnerabilities
(base) 192:simple-electron achao$ npm install --save node-addon-api
npx node-gyp configure

added 1 package, and audited 529 packages in 2s

81 packages are looking for funding
run `npm fund` for details

found 0 vulnerabilities
(base) 192:simple-electron achao$ npx node-gyp configure
gyp info it worked if it ends with ok
gyp info using node-gyp@11.1.0
gyp info using node@20.6.1 | darwin | arm64
gyp info find Python using Python version 3.12.7 found at "/opt/homebrew/Caskroom/miniconda/base/bin/python3"

gyp info spawn /opt/homebrew/Caskroom/miniconda/base/bin/python3
gyp info spawn args [
gyp info spawn args '/Users/achao/WebstormProjects/simple-electron/node_modules/node-gyp/gyp/gyp_main.py',
gyp info spawn args 'binding.gyp',
gyp info spawn args '-f',
gyp info spawn args 'make',
gyp info spawn args '-I',
gyp info spawn args '/Users/achao/WebstormProjects/simple-electron/build/config.gypi',
gyp info spawn args '-I',
gyp info spawn args '/Users/achao/WebstormProjects/simple-electron/node_modules/node-gyp/addon.gypi',
gyp info spawn args '-I',
gyp info spawn args '/Users/achao/Library/Caches/node-gyp/20.6.1/include/node/common.gypi',
gyp info spawn args '-Dlibrary=shared_library',
gyp info spawn args '-Dvisibility=default',
gyp info spawn args '-Dnode_root_dir=/Users/achao/Library/Caches/node-gyp/20.6.1',
gyp info spawn args '-Dnode_gyp_dir=/Users/achao/WebstormProjects/simple-electron/node_modules/node-gyp',
gyp info spawn args '-Dnode_lib_file=/Users/achao/Library/Caches/node-gyp/20.6.1/<(target_arch)/node.lib',
gyp info spawn args '-Dmodule_root_dir=/Users/achao/WebstormProjects/simple-electron',
gyp info spawn args '-Dnode_engine=v8',
gyp info spawn args '--depth=.',
gyp info spawn args '--no-parallel',
gyp info spawn args '--generator-output',
gyp info spawn args 'build',
gyp info spawn args '-Goutput_dir=.'
gyp info spawn args ]
gyp info ok
(base) 192:simple-electron achao$ npx node-gyp build
gyp info it worked if it ends with ok
gyp info using node-gyp@11.1.0
gyp info using node@20.6.1 | darwin | arm64
gyp info spawn make
gyp info spawn args [ 'BUILDTYPE=Release', '-C', 'build' ]
CXX(target) Release/obj.target/hello/src/hello.o
SOLINK_MODULE(target) Release/hello.node
gyp info ok

可以看到生成了build/Release/hello.node

然后我们运行项目npm start,点击两个按钮测试查看输出和页面表现

完整代码:

阿超/simple-electron