You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自研C++ Node Addon在Electron中加载失败,报DLL初始化错误

问题:Electron环境下加载Node Addon时DLL初始化失败

我开发了一个基于C++的Node Addon,在Node环境中用node test.js执行测试脚本加载正常,但在Electron环境中加载时抛出异常:

Uncaught Exception: Error: A dynamic link library (DLL) initialization routine failed.

我知道Electron使用自身的node.lib库,已经替换为该库而非Node.js的node.lib,但问题依旧存在。

项目基于CMake构建,直接链接node.lib。以下是最小复现示例:通过Visual Studio构建生成helloJS.node文件,使用npm run package打包Electron应用后,手动将二进制文件复制到out/test-win32-x64/resources/app/electron路径,启动out/test-win32-x64/test.exe时出现上述错误。

请问我遗漏了什么?是否需要额外的编译标志,或是使用了错误的Node库?我使用的库来自Electron官方artifacts仓库。


./CMakeLists.txt

cmake_minimum_required (VERSION 3.15)
cmake_policy(SET CMP0091 NEW)
cmake_policy(SET CMP0042 NEW)

project(hello
    VERSION 0.0.1 
    DESCRIPTION "hello world for node-addon in pure cmake"
)

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")

# download Node library and create a cMake target including headers and linking information
include(FetchNode)
FetchNode("Electron" 22.0.0 67af8ac8af0db4ed1fe7af1a8583f1dae9f459be0a37ae9279381a2996cb8426 190aa8035756ea448870547c8123b959031657f739843e6950013df04c0dd119)

# source files
file(GLOB ADDON_SRC_FILES
    "*.cpp"
    "*.hpp"
)

add_definitions(-DNAPI_VERSION=6) 
add_library(helloJS SHARED 
    ${ADDON_SRC_FILES}
)
set_target_properties(helloJS PROPERTIES 
    PREFIX "" 
    SUFFIX ".node"
)
target_link_libraries(helloJS Node)

./hello.hpp

#include <napi.h>

class HelloJS : public Napi::Addon<HelloJS> {
public:
    HelloJS(Napi::Env env, Napi::Object exports);
private:
    Napi::Value sayHello(const Napi::CallbackInfo& info);
};

./hello.cpp

#include "hello.hpp"
#include <iostream>

HelloJS::HelloJS(Napi::Env env, Napi::Object exports) {
    std::cout << "HelloJS::HelloJS called" << std::endl;
    DefineAddon(exports, {
      InstanceMethod("sayHello", &HelloJS::sayHello)
    });
}

Napi::Value HelloJS::sayHello(const Napi::CallbackInfo& info) {
    std::cout << "HelloJS::sayHello called" << std::endl;
    std::string hey = "hello world!";
    return Napi::String::New(info.Env(), hey);
}

NODE_API_ADDON(HelloJS)

./test/test.js

var coreJS = require('./HelloJS');
console.log(coreJS.sayHello());

使用node test.js执行时运行正常

./test/helloJS.node
此文件为test.js中使用的构建输出

./electron/package.json

{
  "name": "test",
  "version": "0.1.0",
  "description": "test Application",
  "main": "electron/electron.js",
  "homepage": "./",
  "author": "your-name",
  "license": "MIT",
  "private": true,
  "devDependencies": {
    "@electron-forge/cli": "^6.0.4",
    "@electron-forge/maker-deb": "^6.0.4",
    "@electron-forge/maker-rpm": "^6.0.4",
    "@electron-forge/maker-squirrel": "^6.0.4",
    "@electron-forge/maker-zip": "^6.0.4",
    "electron": "^22.0.0"
  },
  "dependencies": {
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "bindings": "^1.5.0",
    "electron-squirrel-startup": "^1.0.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "electron": "electron .",
    "package": "react-scripts build && electron-forge package"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

./electron/electron.js

const { app, BrowserWindow } = require('electron')
const path = require('path')
const url = require('url');

const coreJS = require('./HelloJS');

const createWindow = () => {
  // Create the browser window.
  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600
  })
  mainWindow.loadFile(path.join(__dirname, '/../build/index.html'))
  
  // Open the DevTools.
  mainWindow.webContents.openDevTools();
}

app.whenReady().then(() => {
  createWindow()
  
  app.on('activate', function () {
    // On macOS it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    if (BrowserWindow.getAllWindows().length === 0) createWindow()
  })
})

// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
  if (process.platform !== 'darwin') app.quit()
})

./cmake/FetchNode.cmake

# File: FetchNode.cmake
# FetchNodeJS(<NODE_TYPE> <NODE_VERSION> <LIBRARY_SHA256> <HEADERS_SHA256>)
#       <NODE_TYPE>         in: type of requested node library (NodeJS or Electron)
#       <NODE_VERSION>      in: requested node version (node JS version or electron version) (e.g. 18.12.1)
#       <LIBRARY_SHA256>    in: sha256 hash of the node.lib file
#       <HEADERS_SHA256>    in: sha256 hash of headers tar file
#
# The function downloads the node library, its header files as well as the the node-addon-api headers.
# As output it defines a cMake Target **Node**
cmake_minimum_required (VERSION 3.18)

if(__FETCHNODE_INCLUDED)
  return()
endif()
set(__FETCHNODE_INCLUDED TRUE)

include(FetchContent)

function(FetchNode node_TYPE node_VERSION node_LibHash node_HdrHash)
  string(COMPARE EQUAL "${node_TYPE}" "NodeJS" _cmp_NodeJS)
  string(COMPARE EQUAL "${node_TYPE}" "Electron" _cmp_Electron)
  if (_cmp_NodeJS)
    set(NODE_URL "https://nodejs.org/dist/v${node_VERSION}")
    set(NODE_LIBRARY "${NODE_URL}/win-x64/node.lib")
    set(NODE_HEADER "${NODE_URL}/node-v${node_VERSION}-headers.tar.gz")
  elseif(_cmp_Electron)
    set(NODE_URL "https://artifacts.electronjs.org/headers/dist/v${node_VERSION}")
    set(NODE_LIBRARY "${NODE_URL}/win-x64/node.lib")
    set(NODE_HEADER "${NODE_URL}/node-v${node_VERSION}.tar.gz")
  else()
    message(FATAL_ERROR "Unknown Node Type in FetchNode function!")
  endif()

  FetchContent_Declare(nodeLib
    URL ${NODE_LIBRARY}
    URL_HASH SHA256=${node_LibHash}
    DOWNLOAD_NO_EXTRACT true
  )
  FetchContent_Declare(nodeHdr
    URL ${NODE_HEADER}
    URL_HASH SHA256=${node_HdrHash}    
  )
  FetchContent_Declare(nodeAddonAPI
    GIT_REPOSITORY https://github.com/nodejs/node-addon-api.git
    GIT_TAG v5.0.0
  )

  FetchContent_MakeAvailable(nodeLib nodeHdr nodeAddonAPI)

  add_library(Node STATIC IMPORTED)
  set_target_properties(Node PROPERTIES IMPORTED_LOCATION ${nodelib_SOURCE_DIR}/node.lib)
  target_include_directories(Node INTERFACE ${nodehdr_SOURCE_DIR}/include/node ${nodeaddonapi_SOURCE_DIR})

endfunction()

可能的原因及解决方法

1. 运行时库不匹配

Electron使用多线程DLL(MD/MDd)作为运行时库,若你的Addon使用了静态运行时(MT/MTd),会导致CRT版本冲突,触发DLL初始化失败。在CMakeLists.txt中明确指定运行时库:

set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")

2. 二进制文件路径问题

手动复制文件可能导致路径不匹配,建议通过Electron Forge自动管理资源:

  1. 在项目根目录创建forge.config.js:
module.exports = {
  packagerConfig: {
    extraResource: ["./helloJS.node"]
  }
};
  1. 修改electron.js中的加载路径:
const coreJS = require(path.join(process.resourcesPath, 'helloJS.node'));

3. 编译架构不匹配

确保编译Addon时使用x64架构,与Electron打包的win32-x64一致:

cmake -G "Visual Studio 17 2022" -A x64 ..

4. Node-Addon-API版本兼容

Electron 22对应Node.js 16.17.0,建议确认node-addon-api v5.0.0与该版本的兼容性,若存在问题,可尝试升级或降级node-addon-api版本。

5. 缺失依赖DLL

使用Dependency Walker工具检查helloJS.node的依赖项,确保所有系统DLL和Electron相关DLL都存在于应用目录中。


内容的提问来源于stack exchange,提问作者moudi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 09:35:24