在Electron中运行C++服务器

rqqzpn5f  于 2023-06-20  发布在  Electron
关注(0)|答案(1)|浏览(180)

回答此问题可获得+50声望bounty。赏金宽限期已经结束。th3g3ntl3man正在寻找一个规范答案
我创建了一个电子和React的应用程序。此应用程序有一个内部C++服务器。在开发模式下,我没有问题,但在打包后,服务器无法启动。
这是我的文件夹树:

├── dist-electron
│   ├── main
│   └── preload
├── electron
│   ├── main
│   └── preload
├── public
│   └── tmp
├── server
└── src
    ├── @types
    ├── api
    ├── assets
    ├── common
    ├── components
    ├── context
    ├── routes
    └── theme

server文件夹在react build后包含在dist文件夹中。
在electron main中我写了这个函数:

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();
}

其中:

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;

我总是把spawn ENOTDIR作为错误。我解压缩app.asar文件,操作可执行文件存在于正确的目录dist/server
下面是electron-builder.json5配置文件:

/**
 * @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/"
  }
}

这是一个DialDesigner.ts类,用于示例和配置电子应用程序:

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);
  }
}
0s7z1bwu

0s7z1bwu1#

我怀疑这是由于打包的路径与未打包的路径不同而导致的错误路径造成的。

追踪问题:

1.打开打包的应用程序目录并找到您的app.asar
1.使用npx @electron/asar extract app.asar appAsar提取
1.确保您的预加载脚本位于预期目录中-很可能由于打包而位于子目录中,并且您需要修补路径以调整应用程序是否打包
例如,在我们的代码中,我们使用这样的东西:

export const preloadPath = path.join(
  __dirname,
  IS_DEV_ENV ? 'dist' : '',
  'preload.build.js'
);

如果仍然不起作用-将electron-unhandled添加到您的应用程序中,方法是将这些行添加到您的mainpreload进程脚本中。

import unhandled from 'electron-unhandled';

unhandled();

确保同时运行npm install --save electron-unhandled
这将显示一个对话框,其中包含发生的错误(如果有)。

相关问题