简体   繁体   中英

How to access native hwnd of window with electron on windows in preload.js?

Here I want show the hwnd of a BrowserWindow in a electron window:

// 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>

What I got is the hwnd-version will not be replaced by the hwnd. How to access it?

Issue:

The issue is you are using the globalThis and its window object, which is different from Electron's BrowserWindow object defined in main. Your preload script should use Electron's BrowserWindow object to access that getNativeWindowHandle() function. However, this is tricky, so read on.


Obstacle:

It is not secure to enable nodeIntegration nor enableRemoteModule . Both of those in your Electron window should be set to false when creating, as you have already done. I also enable contextIsolation and use something similar in the following Stack Overflow answer: How to use preload.js properly in Electron


Resolution:

Preload Script

Require contextBridge and ipcRenderer in your preload script and use as follows:

// 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")

Main Script

Require ipcMain from Electron and change your const win = new BrowserWindow(...) to win = new BrowserWindow(...) and be sure to define var win = null so that it is accessible anywhere in main.js , and use like so in the following code:

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

Index HTML File

This file is fine and can remain as is.


Renderer Script

Add the following code to your renderer.js file in addition to what you already have. I found that accessing the new api object from the globalThis and it's window object like window.api.receive or window.api.send as defined in the contextBridge in preload.js would fail because api would be undefined during the DomContentLoaded event.

Here is the code for 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)

I have tested this code using NodeJS version 18.12.1, NPM version 9.2.0 with Electron version ^22.0.0 in my own workspace in VS Code.


Notice:

  • In win.webContents.send() for the second parameter, I only send the integer returned from calling the win.getNativeWindowHandle().readInt32LE() function. The reason is because win.webContents.send() serializes the second parameter and the object win is not serializable. I would think it's for the best, too, to avoid sending large objects to and from the renderer and main processes.
  • I couldn't help but notice you have the text Native Editor for the title tag. Are you passing this window handle to, say, a C++ module imported in the renderer process to pass to some graphics API like DirectX or Vulkan? If so, let me know how that goes, cause I'm not sure if it will work or not due to the window needing to be created with certain capabilities to use with those APIs, but I wanted to try it. But it is generally not something that's supported, and it breaks cross-platform abilities of Electron becoming dependent on Windows, and then each windowing system (X11, MacOS X, Windows, etc...) would need to have their own way of accessing the electron window, which I guarantee will vary from OS to OS.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM