英文:
Run C++ server in Electron
问题
I created an application with electron and react. This application has an internal c++ server. In dev modality I don't have problems but after packaging the server not starts.
This is my folder tree:
├── dist-electron
│ ├── main
│ └── preload
├── electron
│ ├── main
│ └── preload
├── public
│ └── tmp
├── server
└── src
├── @types
├── api
├── assets
├── common
├── components
├── context
├── routes
└── theme
The server folder is included after react build into the dist folder.
In the electron main i wrote this function:
try {
const appPath = app.isPackaged
? join(process.env.PUBLIC, '../../server')
: join(process.env.DIST_ELECTRON, '../server');
const server = spawn(
join(appPath, 'operations'),
[
join(appPath, 'settings.json'),
join(appPath, '../public/tmp'),
],
{ cwd: appPath }
);
server.stdout.on('data', (data) => {
console.log(`Server stdout: ${data}`);
});
server.stderr.on('data', (data) => {
console.error(`Server stderr: ${data}`);
});
server.on('close', (code) => {
console.log(`Server process exited with code ${code}`);
});
}
catch (err) {
dialog.showMessageBox(DialDesigner.main!, { title: 'Errore', message: process.env.PUBLIC });
app.quit();
}
where:
process.env.DIST_ELECTRON = join(__dirname, '../');
process.env.DIST = join(process.env.DIST_ELECTRON, '../dist');
process.env.PUBLIC = process.env.VITE_DEV_SERVER_URL ?
join(process.env.DIST_ELECTRON, '../public') :
process.env.DIST;
I always have spawn ENOTDIR
as error.
I extract the app.asar file and the operations executable exists in the correct directory dist/server
This is the electron-builder.json5
configuration file:
/**
* @see https://www.electron.build/configuration/configuration
*/
{
"appId": "it.softwaves.dial-designer",
"productName": "Dial Designer",
"asar": true,
"directories": {
"output": "release/${version}"
},
"files": [
"dist-electron",
"dist",
"server"
],
"mac": {
"artifactName": "Dial_Designer_${version}.${ext}",
"target": [
"dmg",
"zip"
]
},
"win": {
"target": [
{
"target": "nsis",
"arch": [
"x64"
]
}
],
"artifactName": "Dial_Designer_${version}.${ext}"
},
"nsis": {
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": true,
"deleteAppDataOnUninstall": false
},
"publish": {
"provider": "generic",
"channel": "latest",
"url": "https://github.com/electron-vite/electron-vite-react/releases/download/v0.9.9/"
}
}
And this it the DialDesigner.ts
class that instance and configure the electron application:
import { join, dirname } from 'node:path';
import { release } from 'node:os';
import { spawn } from 'node:child_process';
import { copySync } from 'fs-extra';
import {
app, BrowserWindow, screen,
shell, Menu, MenuItemConstructorOptions, dialog
} from 'electron';
import { update } from './update';
interface IWin {
title: string,
width: number,
height: number,
preload?: any,
resizable?: boolean,
}
export default class DialDesigner {
public static main: BrowserWindow | null;
public static settings: BrowserWindow | null;
public static url: string | undefined;
public static indexHtml: string;
public static IS_MAC: boolean = process.platform === 'darwin';
constructor() {
process.env.DIST_ELECTRON = join(__dirname, '../');
process.env.DIST = join(process.env.DIST_ELECTRON, '../dist');
process.env.PUBLIC = process.env.VITE_DEV_SERVER_URL ?
join(process.env.DIST_ELECTRON, '../public') :
process.env.DIST;
DialDesigner.url = process.env.VITE_DEV_SERVER_URL;
DialDesigner.indexHtml = join(process.env.DIST, 'index.html');
if (release().startsWith('6.1')) app.disableHardwareAcceleration();
if (process.platform === 'win32') app.setAppUserModelId(app.getName());
if (!app.requestSingleInstanceLock()) {
app.quit();
process.exit(0);
}
}
public static newWindow = (win: IWin): BrowserWindow => {
return new BrowserWindow({
title: win.title,
icon: join(process.env.PUBLIC, 'favicon.ico'),
width: win.width,
height: win.height,
center: true,
resizable: win.resizable ?? true,
webPreferences: {
preload: win?.preload,
nodeIntegration: true,
contextIsolation: false,
},
});
}
private runServer = () => {
let appPath = null;
try {
appPath = app.isPackaged
? join(process.env.DIST, '../server')
: join(process.env.DIST_ELECTRON, '../server');
const server = spawn(
join(appPath, 'operations'),
[
join(appPath, 'settings.json'),
join(appPath, '../public/tmp'),
],
{ cwd: appPath }
);
server.stdout.on('data', (data) => {
console.log(`Server stdout: ${data}`);
});
server.stderr.on('data', (data) => {
console.error(`Server stderr: ${data}`);
});
server.on('close', (code) => {
console.log(`Server process exited with code ${code}`);
});
}
catch (err) {
dialog.showMessageBox(DialDesigner.main!, { title: 'Errore', message:
`PUBLIC: ${process.env.PUBLIC}
DIST: ${process.env.DIST}
EXE: ${join(appPath!, 'operations')}
`
});
app.quit();
}
}
private createWindow = () => {
const { width, height } = screen.getPrimaryDisplay().size;
DialDesigner.main = DialDesigner.newWindow({
title: 'Dial Designer',
width, height,
preload: join(__dirname, '../preload/index.js'),
});
if (DialDesigner.url) {
DialDesigner.main.loadURL(DialDesigner.url);
DialDesigner.main.webContents.openDevTools();
}
else {
DialDesigner.main.loadFile(DialDesigner.indexHtml
<details>
<summary>英文:</summary>
I created an application with electron and react. This application has an internal c++ server. In dev modality I don't have problems but after packaging the server not starts.
This is my folder tree:
├── dist-electron
│ ├── main
│ └── preload
├── electron
│ ├── main
│ └── preload
├── public
│ └── tmp
├── server
└── src
├── @types
├── api
├── assets
├── common
├── components
├── context
├── routes
└── theme
The server folder is included after react build into the dist folder.
In the electron main i wrote this function:
try {
const appPath = app.isPackaged
? join(process.env.PUBLIC, '../../server')
: join(process.env.DIST_ELECTRON, '../server');
const server = spawn(
join(appPath, 'operations'),
[
join(appPath, 'settings.json'),
join(appPath, '../public/tmp'),
],
{ cwd: appPath }
);
server.stdout.on('data', (data) => {
console.log(`Server stdout: ${data}`);
});
server.stderr.on('data', (data) => {
console.error(`Server stderr: ${data}`);
});
server.on('close', (code) => {
console.log(`Server process exited with code ${code}`);
});
}
catch (err) {
dialog.showMessageBox(DialDesigner.main!, { title: 'Errore', message: process.env.PUBLIC });
app.quit();
}
where:
process.env.DIST_ELECTRON = join(__dirname, '../');
process.env.DIST = join(process.env.DIST_ELECTRON, '../dist');
process.env.PUBLIC = process.env.VITE_DEV_SERVER_URL ?
join(process.env.DIST_ELECTRON, '../public') :
process.env.DIST;
I always have `spawn ENOTDIR` as error.
I extract the app.asar file and the operations executable exists in the correct directory `dist/server`
This is the `electron-builder.json5` configuration file:
/**
* @see https://www.electron.build/configuration/configuration
*/
{
"appId": "it.softwaves.dial-designer",
"productName": "Dial Designer",
"asar": true,
"directories": {
"output": "release/${version}"
},
"files": [
"dist-electron",
"dist",
"server"
],
"mac": {
"artifactName": "Dial_Designer_${version}.${ext}",
"target": [
"dmg",
"zip"
]
},
"win": {
"target": [
{
"target": "nsis",
"arch": [
"x64"
]
}
],
"artifactName": "Dial_Designer_${version}.${ext}"
},
"nsis": {
"oneClick": false,
"perMachine": false,
"allowToChangeInstallationDirectory": true,
"deleteAppDataOnUninstall": false
},
"publish": {
"provider": "generic",
"channel": "latest",
"url": "https://github.com/electron-vite/electron-vite-react/releases/download/v0.9.9/"
}
}
And this it the `DialDesigner.ts` class that instance and configure the electron application:
import { join, dirname } from 'node:path';
import { release } from 'node:os';
import { spawn } from 'node:child_process';
import { copySync } from 'fs-extra';
import {
app, BrowserWindow, screen,
shell, Menu, MenuItemConstructorOptions, dialog
} from 'electron';
import { update } from './update';
interface IWin {
title: string,
width: number,
height: number,
preload?: any,
resizable?: boolean,
}
export default class DialDesigner {
public static main: BrowserWindow | null;
public static settings: BrowserWindow | null;
public static url: string | undefined;
public static indexHtml: string;
public static IS_MAC: boolean = process.platform === 'darwin';
constructor() {
process.env.DIST_ELECTRON = join(__dirname, '../');
process.env.DIST = join(process.env.DIST_ELECTRON, '../dist');
process.env.PUBLIC = process.env.VITE_DEV_SERVER_URL ?
join(process.env.DIST_ELECTRON, '../public') :
process.env.DIST;
DialDesigner.url = process.env.VITE_DEV_SERVER_URL;
DialDesigner.indexHtml = join(process.env.DIST, 'index.html');
if (release().startsWith('6.1')) app.disableHardwareAcceleration();
if (process.platform === 'win32') app.setAppUserModelId(app.getName());
if (!app.requestSingleInstanceLock()) {
app.quit();
process.exit(0);
}
}
public static newWindow = (win: IWin): BrowserWindow => {
return new BrowserWindow({
title: win.title,
icon: join(process.env.PUBLIC, 'favicon.ico'),
width: win.width,
height: win.height,
center: true,
resizable: win.resizable ?? true,
webPreferences: {
preload: win?.preload,
nodeIntegration: true,
contextIsolation: false,
},
});
}
private runServer = () => {
let appPath = null;
try {
appPath = app.isPackaged
? join(process.env.DIST, '../server')
: join(process.env.DIST_ELECTRON, '../server');
const server = spawn(
join(appPath, 'operations'),
[
join(appPath, 'settings.json'),
join(appPath, '../public/tmp'),
],
{ cwd: appPath }
);
server.stdout.on('data', (data) => {
console.log(`Server stdout: ${data}`);
});
server.stderr.on('data', (data) => {
console.error(`Server stderr: ${data}`);
});
server.on('close', (code) => {
console.log(`Server process exited with code ${code}`);
});
}
catch (err) {
dialog.showMessageBox(DialDesigner.main!, { title: 'Errore', message:
`PUBLIC: ${process.env.PUBLIC}
DIST: ${process.env.DIST}
EXE: ${join(appPath!, 'operations')}
`
});
app.quit();
}
}
private createWindow = () => {
const { width, height } = screen.getPrimaryDisplay().size;
DialDesigner.main = DialDesigner.newWindow({
title: 'Dial Designer',
width, height,
preload: join(__dirname, '../preload/index.js'),
});
if (DialDesigner.url) {
DialDesigner.main.loadURL(DialDesigner.url);
DialDesigner.main.webContents.openDevTools();
}
else {
DialDesigner.main.loadFile(DialDesigner.indexHtml);
}
DialDesigner.main.webContents.on('did-finish-load', () => {
DialDesigner.main?.webContents.send('main-process-message', new Date().toLocaleString())
});
// Make all links open with the browser, not with the application
DialDesigner.main.webContents.setWindowOpenHandler(({ url }) => {
if (url.startsWith('https:')) shell.openExternal(url)
return { action: 'deny' }
});
update(DialDesigner.main);
}
public initialize = () => {
try {
app.setPath('userData', `${app.getPath('appData')}/Dial Designer`);
app.whenReady()
.then(this.createWindow);
app.on('ready', this.runServer);
app.on('window-all-closed', () => {
DialDesigner.main = null
if (process.platform !== 'darwin') app.quit()
});
app.on('second-instance', () => {
if (DialDesigner.main) {
if (DialDesigner.main.isMinimized()) DialDesigner.main.restore();
DialDesigner.main.focus();
}
});
app.on('activate', () => {
const allWindows = BrowserWindow.getAllWindows()
if (allWindows.length) allWindows[0].focus();
else this.createWindow();
});
}
catch (err) {
dialog.showMessageBox(DialDesigner.main!, { title: 'Errore', message: `${(err as Error).stack}` });
app.quit();
}
}
public applicationMenu = (template: MenuItemConstructorOptions[]) => {
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
}
}
</details>
# 答案1
**得分**: 1
我怀疑这是由于打包和未打包路径不同而导致的错误路径引起的。
**跟踪问题的步骤:**
1. 打开打包后的应用程序目录,找到你的 `app.asar` 文件。
2. 使用 `npx @electron/asar extract app.asar appAsar` 进行解压。
3. 确保你的预加载脚本位于预期的目录中 - 很可能由于打包的结果,它位于一个子目录中,因此你需要修复路径以适应应用程序是打包还是未打包的情况。
例如,在我们的代码中,我们使用类似以下的代码:
```js
export const preloadPath = path.join(
__dirname,
IS_DEV_ENV ? 'dist' : '',
'preload.build.js'
);
如果仍然无法解决问题 - 通过在你的 main
和 preload
进程脚本中添加以下代码来添加 electron-unhandled
到你的应用程序中:
import unhandled from 'electron-unhandled';
unhandled();
确保还运行了 npm install --save electron-unhandled
。
这应该会显示错误对话框,显示出现的错误(如果有的话)。
英文:
I suspect this is caused by a bad path resulting from packaged vs unpackaged paths differing.
To trace the issue:
- Open up the packaged app directory and find your
app.asar
- Extract it using
npx @electron/asar extract app.asar appAsar
- Ensure your preload script is in the expected directory - it's likely it's in a subdirectory as a result of packaging and that you need to patch the path to adjust for whether the app is packaged or not
For example, in our code we use something like this:
export const preloadPath = path.join(
__dirname,
IS_DEV_ENV ? 'dist' : '',
'preload.build.js'
);
If that still doesn't work - add electron-unhandled
to your application by adding these lines to your main
and preload
process scripts.
import unhandled from 'electron-unhandled';
unhandled();
Be sure to also run npm install --save electron-unhandled
This should show you a dialog box with the error that's occurring, if any.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论