在Electron中运行C++服务器

huangapple go评论110阅读模式
英文:

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:

  1. ├── dist-electron
  2. ├── main
  3. └── preload
  4. ├── electron
  5. ├── main
  6. └── preload
  7. ├── public
  8. └── tmp
  9. ├── server
  10. └── src
  11. ├── @types
  12. ├── api
  13. ├── assets
  14. ├── common
  15. ├── components
  16. ├── context
  17. ├── routes
  18. └── theme

The server folder is included after react build into the dist folder.

In the electron main i wrote this function:

  1. try {
  2. const appPath = app.isPackaged
  3. ? join(process.env.PUBLIC, '../../server')
  4. : join(process.env.DIST_ELECTRON, '../server');
  5. const server = spawn(
  6. join(appPath, 'operations'),
  7. [
  8. join(appPath, 'settings.json'),
  9. join(appPath, '../public/tmp'),
  10. ],
  11. { cwd: appPath }
  12. );
  13. server.stdout.on('data', (data) => {
  14. console.log(`Server stdout: ${data}`);
  15. });
  16. server.stderr.on('data', (data) => {
  17. console.error(`Server stderr: ${data}`);
  18. });
  19. server.on('close', (code) => {
  20. console.log(`Server process exited with code ${code}`);
  21. });
  22. }
  23. catch (err) {
  24. dialog.showMessageBox(DialDesigner.main!, { title: 'Errore', message: process.env.PUBLIC });
  25. app.quit();
  26. }

where:

  1. process.env.DIST_ELECTRON = join(__dirname, '../');
  2. process.env.DIST = join(process.env.DIST_ELECTRON, '../dist');
  3. process.env.PUBLIC = process.env.VITE_DEV_SERVER_URL ?
  4. join(process.env.DIST_ELECTRON, '../public') :
  5. 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:

  1. /**
  2. * @see https://www.electron.build/configuration/configuration
  3. */
  4. {
  5. "appId": "it.softwaves.dial-designer",
  6. "productName": "Dial Designer",
  7. "asar": true,
  8. "directories": {
  9. "output": "release/${version}"
  10. },
  11. "files": [
  12. "dist-electron",
  13. "dist",
  14. "server"
  15. ],
  16. "mac": {
  17. "artifactName": "Dial_Designer_${version}.${ext}",
  18. "target": [
  19. "dmg",
  20. "zip"
  21. ]
  22. },
  23. "win": {
  24. "target": [
  25. {
  26. "target": "nsis",
  27. "arch": [
  28. "x64"
  29. ]
  30. }
  31. ],
  32. "artifactName": "Dial_Designer_${version}.${ext}"
  33. },
  34. "nsis": {
  35. "oneClick": false,
  36. "perMachine": false,
  37. "allowToChangeInstallationDirectory": true,
  38. "deleteAppDataOnUninstall": false
  39. },
  40. "publish": {
  41. "provider": "generic",
  42. "channel": "latest",
  43. "url": "https://github.com/electron-vite/electron-vite-react/releases/download/v0.9.9/"
  44. }
  45. }

And this it the DialDesigner.ts class that instance and configure the electron application:

  1. import { join, dirname } from 'node:path';
  2. import { release } from 'node:os';
  3. import { spawn } from 'node:child_process';
  4. import { copySync } from 'fs-extra';
  5. import {
  6. app, BrowserWindow, screen,
  7. shell, Menu, MenuItemConstructorOptions, dialog
  8. } from 'electron';
  9. import { update } from './update';
  10. interface IWin {
  11. title: string,
  12. width: number,
  13. height: number,
  14. preload?: any,
  15. resizable?: boolean,
  16. }
  17. export default class DialDesigner {
  18. public static main: BrowserWindow | null;
  19. public static settings: BrowserWindow | null;
  20. public static url: string | undefined;
  21. public static indexHtml: string;
  22. public static IS_MAC: boolean = process.platform === 'darwin';
  23. constructor() {
  24. process.env.DIST_ELECTRON = join(__dirname, '../');
  25. process.env.DIST = join(process.env.DIST_ELECTRON, '../dist');
  26. process.env.PUBLIC = process.env.VITE_DEV_SERVER_URL ?
  27. join(process.env.DIST_ELECTRON, '../public') :
  28. process.env.DIST;
  29. DialDesigner.url = process.env.VITE_DEV_SERVER_URL;
  30. DialDesigner.indexHtml = join(process.env.DIST, 'index.html');
  31. if (release().startsWith('6.1')) app.disableHardwareAcceleration();
  32. if (process.platform === 'win32') app.setAppUserModelId(app.getName());
  33. if (!app.requestSingleInstanceLock()) {
  34. app.quit();
  35. process.exit(0);
  36. }
  37. }
  38. public static newWindow = (win: IWin): BrowserWindow => {
  39. return new BrowserWindow({
  40. title: win.title,
  41. icon: join(process.env.PUBLIC, 'favicon.ico'),
  42. width: win.width,
  43. height: win.height,
  44. center: true,
  45. resizable: win.resizable ?? true,
  46. webPreferences: {
  47. preload: win?.preload,
  48. nodeIntegration: true,
  49. contextIsolation: false,
  50. },
  51. });
  52. }
  53. private runServer = () => {
  54. let appPath = null;
  55. try {
  56. appPath = app.isPackaged
  57. ? join(process.env.DIST, '../server')
  58. : join(process.env.DIST_ELECTRON, '../server');
  59. const server = spawn(
  60. join(appPath, 'operations'),
  61. [
  62. join(appPath, 'settings.json'),
  63. join(appPath, '../public/tmp'),
  64. ],
  65. { cwd: appPath }
  66. );
  67. server.stdout.on('data', (data) => {
  68. console.log(`Server stdout: ${data}`);
  69. });
  70. server.stderr.on('data', (data) => {
  71. console.error(`Server stderr: ${data}`);
  72. });
  73. server.on('close', (code) => {
  74. console.log(`Server process exited with code ${code}`);
  75. });
  76. }
  77. catch (err) {
  78. dialog.showMessageBox(DialDesigner.main!, { title: 'Errore', message:
  79. `PUBLIC: ${process.env.PUBLIC}
  80. DIST: ${process.env.DIST}
  81. EXE: ${join(appPath!, 'operations')}
  82. `
  83. });
  84. app.quit();
  85. }
  86. }
  87. private createWindow = () => {
  88. const { width, height } = screen.getPrimaryDisplay().size;
  89. DialDesigner.main = DialDesigner.newWindow({
  90. title: 'Dial Designer',
  91. width, height,
  92. preload: join(__dirname, '../preload/index.js'),
  93. });
  94. if (DialDesigner.url) {
  95. DialDesigner.main.loadURL(DialDesigner.url);
  96. DialDesigner.main.webContents.openDevTools();
  97. }
  98. else {
  99. DialDesigner.main.loadFile(DialDesigner.indexHtml
  100. <details>
  101. <summary>英文:</summary>
  102. I created an application with electron and react. This application has an internal c++ server. In dev modality I don&#39;t have problems but after packaging the server not starts.
  103. This is my folder tree:
  104. ├── dist-electron
  105. ├── main
  106. └── preload
  107. ├── electron
  108. ├── main
  109. └── preload
  110. ├── public
  111. └── tmp
  112. ├── server
  113. └── src
  114. ├── @types
  115. ├── api
  116. ├── assets
  117. ├── common
  118. ├── components
  119. ├── context
  120. ├── routes
  121. └── theme
  122. The server folder is included after react build into the dist folder.
  123. In the electron main i wrote this function:
  124. try {
  125. const appPath = app.isPackaged
  126. ? join(process.env.PUBLIC, &#39;../../server&#39;)
  127. : join(process.env.DIST_ELECTRON, &#39;../server&#39;);
  128. const server = spawn(
  129. join(appPath, &#39;operations&#39;),
  130. [
  131. join(appPath, &#39;settings.json&#39;),
  132. join(appPath, &#39;../public/tmp&#39;),
  133. ],
  134. { cwd: appPath }
  135. );
  136. server.stdout.on(&#39;data&#39;, (data) =&gt; {
  137. console.log(`Server stdout: ${data}`);
  138. });
  139. server.stderr.on(&#39;data&#39;, (data) =&gt; {
  140. console.error(`Server stderr: ${data}`);
  141. });
  142. server.on(&#39;close&#39;, (code) =&gt; {
  143. console.log(`Server process exited with code ${code}`);
  144. });
  145. }
  146. catch (err) {
  147. dialog.showMessageBox(DialDesigner.main!, { title: &#39;Errore&#39;, message: process.env.PUBLIC });
  148. app.quit();
  149. }
  150. where:
  151. process.env.DIST_ELECTRON = join(__dirname, &#39;../&#39;);
  152. process.env.DIST = join(process.env.DIST_ELECTRON, &#39;../dist&#39;);
  153. process.env.PUBLIC = process.env.VITE_DEV_SERVER_URL ?
  154. join(process.env.DIST_ELECTRON, &#39;../public&#39;) :
  155. process.env.DIST;
  156. I always have `spawn ENOTDIR` as error.
  157. I extract the app.asar file and the operations executable exists in the correct directory `dist/server`
  158. This is the `electron-builder.json5` configuration file:
  159. /**
  160. * @see https://www.electron.build/configuration/configuration
  161. */
  162. {
  163. &quot;appId&quot;: &quot;it.softwaves.dial-designer&quot;,
  164. &quot;productName&quot;: &quot;Dial Designer&quot;,
  165. &quot;asar&quot;: true,
  166. &quot;directories&quot;: {
  167. &quot;output&quot;: &quot;release/${version}&quot;
  168. },
  169. &quot;files&quot;: [
  170. &quot;dist-electron&quot;,
  171. &quot;dist&quot;,
  172. &quot;server&quot;
  173. ],
  174. &quot;mac&quot;: {
  175. &quot;artifactName&quot;: &quot;Dial_Designer_${version}.${ext}&quot;,
  176. &quot;target&quot;: [
  177. &quot;dmg&quot;,
  178. &quot;zip&quot;
  179. ]
  180. },
  181. &quot;win&quot;: {
  182. &quot;target&quot;: [
  183. {
  184. &quot;target&quot;: &quot;nsis&quot;,
  185. &quot;arch&quot;: [
  186. &quot;x64&quot;
  187. ]
  188. }
  189. ],
  190. &quot;artifactName&quot;: &quot;Dial_Designer_${version}.${ext}&quot;
  191. },
  192. &quot;nsis&quot;: {
  193. &quot;oneClick&quot;: false,
  194. &quot;perMachine&quot;: false,
  195. &quot;allowToChangeInstallationDirectory&quot;: true,
  196. &quot;deleteAppDataOnUninstall&quot;: false
  197. },
  198. &quot;publish&quot;: {
  199. &quot;provider&quot;: &quot;generic&quot;,
  200. &quot;channel&quot;: &quot;latest&quot;,
  201. &quot;url&quot;: &quot;https://github.com/electron-vite/electron-vite-react/releases/download/v0.9.9/&quot;
  202. }
  203. }
  204. And this it the `DialDesigner.ts` class that instance and configure the electron application:
  205. import { join, dirname } from &#39;node:path&#39;;
  206. import { release } from &#39;node:os&#39;;
  207. import { spawn } from &#39;node:child_process&#39;;
  208. import { copySync } from &#39;fs-extra&#39;;
  209. import {
  210. app, BrowserWindow, screen,
  211. shell, Menu, MenuItemConstructorOptions, dialog
  212. } from &#39;electron&#39;;
  213. import { update } from &#39;./update&#39;;
  214. interface IWin {
  215. title: string,
  216. width: number,
  217. height: number,
  218. preload?: any,
  219. resizable?: boolean,
  220. }
  221. export default class DialDesigner {
  222. public static main: BrowserWindow | null;
  223. public static settings: BrowserWindow | null;
  224. public static url: string | undefined;
  225. public static indexHtml: string;
  226. public static IS_MAC: boolean = process.platform === &#39;darwin&#39;;
  227. constructor() {
  228. process.env.DIST_ELECTRON = join(__dirname, &#39;../&#39;);
  229. process.env.DIST = join(process.env.DIST_ELECTRON, &#39;../dist&#39;);
  230. process.env.PUBLIC = process.env.VITE_DEV_SERVER_URL ?
  231. join(process.env.DIST_ELECTRON, &#39;../public&#39;) :
  232. process.env.DIST;
  233. DialDesigner.url = process.env.VITE_DEV_SERVER_URL;
  234. DialDesigner.indexHtml = join(process.env.DIST, &#39;index.html&#39;);
  235. if (release().startsWith(&#39;6.1&#39;)) app.disableHardwareAcceleration();
  236. if (process.platform === &#39;win32&#39;) app.setAppUserModelId(app.getName());
  237. if (!app.requestSingleInstanceLock()) {
  238. app.quit();
  239. process.exit(0);
  240. }
  241. }
  242. public static newWindow = (win: IWin): BrowserWindow =&gt; {
  243. return new BrowserWindow({
  244. title: win.title,
  245. icon: join(process.env.PUBLIC, &#39;favicon.ico&#39;),
  246. width: win.width,
  247. height: win.height,
  248. center: true,
  249. resizable: win.resizable ?? true,
  250. webPreferences: {
  251. preload: win?.preload,
  252. nodeIntegration: true,
  253. contextIsolation: false,
  254. },
  255. });
  256. }
  257. private runServer = () =&gt; {
  258. let appPath = null;
  259. try {
  260. appPath = app.isPackaged
  261. ? join(process.env.DIST, &#39;../server&#39;)
  262. : join(process.env.DIST_ELECTRON, &#39;../server&#39;);
  263. const server = spawn(
  264. join(appPath, &#39;operations&#39;),
  265. [
  266. join(appPath, &#39;settings.json&#39;),
  267. join(appPath, &#39;../public/tmp&#39;),
  268. ],
  269. { cwd: appPath }
  270. );
  271. server.stdout.on(&#39;data&#39;, (data) =&gt; {
  272. console.log(`Server stdout: ${data}`);
  273. });
  274. server.stderr.on(&#39;data&#39;, (data) =&gt; {
  275. console.error(`Server stderr: ${data}`);
  276. });
  277. server.on(&#39;close&#39;, (code) =&gt; {
  278. console.log(`Server process exited with code ${code}`);
  279. });
  280. }
  281. catch (err) {
  282. dialog.showMessageBox(DialDesigner.main!, { title: &#39;Errore&#39;, message:
  283. `PUBLIC: ${process.env.PUBLIC}
  284. DIST: ${process.env.DIST}
  285. EXE: ${join(appPath!, &#39;operations&#39;)}
  286. `
  287. });
  288. app.quit();
  289. }
  290. }
  291. private createWindow = () =&gt; {
  292. const { width, height } = screen.getPrimaryDisplay().size;
  293. DialDesigner.main = DialDesigner.newWindow({
  294. title: &#39;Dial Designer&#39;,
  295. width, height,
  296. preload: join(__dirname, &#39;../preload/index.js&#39;),
  297. });
  298. if (DialDesigner.url) {
  299. DialDesigner.main.loadURL(DialDesigner.url);
  300. DialDesigner.main.webContents.openDevTools();
  301. }
  302. else {
  303. DialDesigner.main.loadFile(DialDesigner.indexHtml);
  304. }
  305. DialDesigner.main.webContents.on(&#39;did-finish-load&#39;, () =&gt; {
  306. DialDesigner.main?.webContents.send(&#39;main-process-message&#39;, new Date().toLocaleString())
  307. });
  308. // Make all links open with the browser, not with the application
  309. DialDesigner.main.webContents.setWindowOpenHandler(({ url }) =&gt; {
  310. if (url.startsWith(&#39;https:&#39;)) shell.openExternal(url)
  311. return { action: &#39;deny&#39; }
  312. });
  313. update(DialDesigner.main);
  314. }
  315. public initialize = () =&gt; {
  316. try {
  317. app.setPath(&#39;userData&#39;, `${app.getPath(&#39;appData&#39;)}/Dial Designer`);
  318. app.whenReady()
  319. .then(this.createWindow);
  320. app.on(&#39;ready&#39;, this.runServer);
  321. app.on(&#39;window-all-closed&#39;, () =&gt; {
  322. DialDesigner.main = null
  323. if (process.platform !== &#39;darwin&#39;) app.quit()
  324. });
  325. app.on(&#39;second-instance&#39;, () =&gt; {
  326. if (DialDesigner.main) {
  327. if (DialDesigner.main.isMinimized()) DialDesigner.main.restore();
  328. DialDesigner.main.focus();
  329. }
  330. });
  331. app.on(&#39;activate&#39;, () =&gt; {
  332. const allWindows = BrowserWindow.getAllWindows()
  333. if (allWindows.length) allWindows[0].focus();
  334. else this.createWindow();
  335. });
  336. }
  337. catch (err) {
  338. dialog.showMessageBox(DialDesigner.main!, { title: &#39;Errore&#39;, message: `${(err as Error).stack}` });
  339. app.quit();
  340. }
  341. }
  342. public applicationMenu = (template: MenuItemConstructorOptions[]) =&gt; {
  343. const menu = Menu.buildFromTemplate(template);
  344. Menu.setApplicationMenu(menu);
  345. }
  346. }
  347. </details>
  348. # 答案1
  349. **得分**: 1
  350. 我怀疑这是由于打包和未打包路径不同而导致的错误路径引起的
  351. **跟踪问题的步骤**
  352. 1. 打开打包后的应用程序目录找到你的 `app.asar` 文件
  353. 2. 使用 `npx @electron/asar extract app.asar appAsar` 进行解压
  354. 3. 确保你的预加载脚本位于预期的目录中 - 很可能由于打包的结果它位于一个子目录中因此你需要修复路径以适应应用程序是打包还是未打包的情况
  355. 例如在我们的代码中我们使用类似以下的代码
  356. ```js
  357. export const preloadPath = path.join(
  358. __dirname,
  359. IS_DEV_ENV ? 'dist' : '',
  360. 'preload.build.js'
  361. );

如果仍然无法解决问题 - 通过在你的 mainpreload 进程脚本中添加以下代码来添加 electron-unhandled 到你的应用程序中:

  1. import unhandled from 'electron-unhandled';
  2. 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:

  1. Open up the packaged app directory and find your app.asar
  2. Extract it using npx @electron/asar extract app.asar appAsar
  3. 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:

  1. export const preloadPath = path.join(
  2. __dirname,
  3. IS_DEV_ENV ? &#39;dist&#39; : &#39;&#39;,
  4. &#39;preload.build.js&#39;
  5. );

If that still doesn't work - add electron-unhandled to your application by adding these lines to your main and preload process scripts.

  1. import unhandled from &#39;electron-unhandled&#39;;
  2. 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.

huangapple
  • 本文由 发表于 2023年6月9日 00:30:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/76433966.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定