Skip to content

Windows addon crashes Electron because node.exe is not delay-loaded #27

Description

@JackCaow

Summary

The published Windows x64 addon in @arcships/light-ocr@0.3.0 imports node.exe through the normal PE import directory instead of the delay-import directory.

That works when the host executable is actually node.exe, but it is not safe for renamed Node-API hosts such as Electron. In the affected Electron application the host executable is electron.exe in development and echoo.exe in production. Because neither basename satisfies the normal node.exe import, Windows resolves another node.exe from PATH. The addon then calls N-API exports in that separately mapped, uninitialized Node image and terminates the Electron process with an execute access violation.

The crash is independent of OCR image size, GPU vendor, WebGPU, and execution-provider selection. Explicit execution.provider = "cpu" crashes in the same way because the failure occurs while loading the native addon, before ONNX Runtime session creation or recognition.

Affected environment

  • @arcships/light-ocr: 0.3.0
  • Host: Electron 37.10.0, embedded Node.js 22
  • OS/architecture: Windows x64
  • Application executable: electron.exe in development, echoo.exe when packaged
  • Reproduced with explicit CPU execution and an 800 x 180 fixture

Reproduction

const { createEngine } = require('@arcships/light-ocr');

await createEngine({
  bundlePath,
  execution: { provider: 'cpu' },
});
  • Running the same package under node.exe succeeds.
  • Running it under Electron terminates the complete process before the promise settles.
  • No OcrError, rejected promise, or application-level error marker is emitted.

Evidence

  1. Application diagnostics end at ocr.engine_init.start; there is no ocr.engine_init.success, ocr.engine_init.error, or recognition marker.
  2. A ProcDump capture reports EXCEPTION_ACCESS_VIOLATION with execute address 0x0569e006 and process exit code 0xFFFF7003.
  3. The crashed process module list contains:
    • the expected light_ocr_node.node and bundled onnxruntime.dll;
    • an unexpected @archshipsdim-desktop\\runtime\\node\\node.exe loaded from PATH.
  4. PE inspection of the published addon shows node.exe in its normal import directory with the required N-API symbols.
  5. Electron's executable exports all N-API symbols required by the addon, but its basename does not match the normal node.exe import.
  6. Rebuilding the addon with node.exe delay-loaded and a host redirection hook makes the same Electron 37 CPU OCR smoke recognize HELLO 123 successfully.

Root cause

Linking a Windows Node-API addon with node.lib is normal; the import library commonly records node.exe as the provider of Node/N-API exports. The defect is that this addon leaves node.exe as a normal import and does not link an effective Windows delay-load hook.

Node-API provides ABI stability, but it does not by itself redirect a normal PE import to a differently named host executable. Electron-compatible Windows addons must delay-load node.exe and redirect that request to the current host module, following the established win_delay_load_hook pattern.

Expected behavior

The same Windows .node artifact should load under supported Node-API hosts without assuming that the host executable basename is node.exe. Failure should reject with a structured error rather than loading an unrelated executable from PATH or terminating the process.

Proposed upstream fix

  • Link the Windows addon with /DELAYLOAD:node.exe and delayimp.
  • Link a delay-load notification hook that resolves node.exe to:
    1. an already loaded libnode.dll, when present; otherwise
    2. GetModuleHandleW(nullptr), the current process executable.
  • Fail the build if PE validation finds node.exe in the normal import directory or cannot find it in the delay-import directory.
  • Add both Node.js and renamed-host Electron smoke coverage on Windows.

A product-specific PE rewrite from node.exe to echoo.exe confirms the diagnosis and is usable as a temporary downstream workaround, but it is not a general upstream fix because it binds the addon to one executable name.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions