electron 如何在preload.js中使用电子窗口访问窗口的本地hwnd?

92vpleto  于 2022-12-28  发布在  Electron
关注(0)|答案(1)|浏览(317)

这里我想在电子窗口中显示BrowserWindow的hwnd:

// preload.js
window.addEventListener('DOMContentLoaded', () => {
    const replaceText = (selector, text) => {
      const element = document.getElementById(selector)
      if (element) element.innerText = text
    }
  
    for (const dependency of ['chrome', 'node', 'electron']) {
      replaceText(`${dependency}-version`, process.versions[dependency])
    }

    replaceText('hwnd-version', window.getNativeWindowHandle().readInt32LE())
  })

console.log("xxxxxxx")
// index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
    <meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'">
    <title>Native Editor</title>
  </head>
  <body>
    <h1>World Editor running</h1>
    We are using Node.js <span id="node-version"></span>,
    Chromium <span id="chrome-version"></span>,
    and Electron <span id="electron-version"></span>,
    based on hwnd <span id="hwnd-version"></span>.

    <script src="./renderer.js"></script>
  </body>
</html>

我得到的是hwnd版本不会被hwnd替换。如何访问它?

ctehm74n

ctehm74n1#

问题:

问题是你使用的是globalThis和它的window对象,这与在main中定义的Electron的BrowserWindow对象不同。你的预加载脚本应该使用Electron的BrowserWindow对象来访问getNativeWindowHandle()函数。然而,这很棘手,所以请继续阅读。

障碍物:

启用nodeIntegrationenableRemoteModule都是不安全的。在创建时,您的电子窗口中的这两个都应该设置为false,就像您已经做的那样。我还启用了contextIsolation,并在下面的堆栈溢出答案中使用了类似的内容:How to use preload.js properly in Electron

分辨率:

预加载脚本

在预加载脚本中需要contextBridgeipcRenderer,并按如下方式使用:

// preload.js
const { contextBridge, ipcRenderer} = require('electron')

contextBridge.exposeInMainWorld("api", {
    send: (channel, data) => {
        // whitelist channels
        let validChannels = ["getBrowserWindowFromMain"]
        if (validChannels.includes(channel)) {
            ipcRenderer.send(channel, data);
        }
    },
    receive: (channel, func) => {
        let validChannels = ["sendBrowserWindowToRenderer"]
        if (validChannels.includes(channel)) {
            ipcRenderer.on(channel, (event, ...args) => {
                func(...args)
            })
        }
    }
})

window.addEventListener('DOMContentLoaded', () => {
    const replaceText = (selector, text) => {
        const element = document.getElementById(selector)
        if (element) {
            element.innerText = text
        }
    }

    for (const dependency of ['chrome', 'node', 'electron']) {
        replaceText(`${dependency}-version`, process.versions[dependency])
    }
}) 

console.log("xxxxxxx")

主脚本

从Electron请求ipcMain,并将const win = new BrowserWindow(...)更改为win = new BrowserWindow(...),确保定义var win = null,以便在main.js中的任何位置都可以访问它,并在以下代码中按如下方式使用:

// main.js
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')

var win = null;

function createWindow () {
  win = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: false, // false is default value after Electron v5
      contextIsolation: true, // true is default value since Electron v12
      preload: path.join(__dirname, 'preload.js'),
      enableRemoteModule: false
    }
  })

  win.loadFile('index.html')
}

app.whenReady().then(() => {
  createWindow()

  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
      createWindow()
    }
  })
})

ipcMain.on("getBrowserWindowFromMain", (event, args) => {
  win.webContents.send("sendBrowserWindowToRenderer", win.getNativeWindowHandle().readInt32LE());
})

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

索引HTML文件

此文件很好,可以保持原样。

渲染器脚本

除了您已经拥有的代码之外,将以下代码添加到renderer.js文件中。我发现从globalThis访问新的api对象时,它会"在preload.js中的contextBridge中定义的window对象(如window.api.receivewindow.api.send)将失败,因为在DomContentLoaded事件。
下面是renderer.js的代码:

// renderer.js
window.api.receive("sendBrowserWindowToRenderer", (windowHandle) => {
    console.log("Window object received.");
    const replaceText = (selector, text) => {
        const element = document.getElementById(selector)
        if (element) {
            element.innerText = text
        }
    }

    if (windowHandle) {
        replaceText('hwnd-version', windowHandle)
    } else {
        replaceText('hwnd-version', '*** Could not retrieve window handle for window at index [0] ***')
    }
})

window.api.send("getBrowserWindowFromMain", null)

我已经在VS代码中使用NodeJS版本18.12.1、NPM版本9.2.0和Electron版本^22.0.0在我自己的工作区中测试了此代码。

注意事项:

  • win.webContents.send()中,对于第二个参数,我只发送调用win.getNativeWindowHandle().readInt32LE()函数返回的整数。原因是win.webContents.send()序列化了第二个参数,而对象win是不可序列化的。我认为最好还是避免在渲染器和主进程之间发送大型对象。
  • 我不禁注意到title标签的文本为Native Editor。您是否将此窗口句柄传递给渲染器进程中导入的C++模块,以传递给DirectX或Vulkan等图形API?如果是,请告诉我具体情况。因为我不确定它是否会工作,因为需要创建具有某些功能的窗口才能与这些API一起使用,但是我想试试,但是一般不支持,而且破坏了Electron依赖Windows的跨平台能力,然后是每个窗口系统(X11,MacOSX,Windows等)将需要有自己的方式访问电子窗口,我保证这将因操作系统而异。

相关问题