自研C++ Node Addon在Electron中加载失败,报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自动管理资源:
- 在项目根目录创建
forge.config.js:
module.exports = { packagerConfig: { extraResource: ["./helloJS.node"] } };
- 修改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

