← Getting Started · Back to README · INodeBridge API →
Complete reference for COM interfaces, configuration structures, and error codes.
Usage pattern:
DN_CreateFactory → INodeFactory
└─► CreateRuntime → INodeRuntime
├─► as INodeLog (QI: BE136A3D-...)
├─► as INodeNpm (QI: A4AEE322-...)
└─► as INodeBridge (QI: E5F6A7B8-...)
- Flat C Exports
- INodeFactory
- INodeRuntime
- INodeLog
- INodeNpm
- INodeNpmRunner
- INodeBridge
- Configuration Structures
- Constants and HRESULT Codes
- Release Order
- Thread-affinity reference
The only stable flat exports of the DLL. Everything else is accessed via vtable.
function DN_CreateFactory(
pConfig : PDNFactoryConfig; // nil → defaults
out ppFactory: INodeFactory // refcount = 1
): HRESULT; cdecl;Creates (or returns the existing) process-wide Node.js factory. One factory per process.
Call once at application startup. A second call without releasing the previous factory returns DN_E_INVALID_STATE.
| Result | Meaning |
|---|---|
S_OK |
Factory created successfully |
E_POINTER |
ppFactory = nil |
E_INVALIDARG |
Invalid configuration |
E_OUTOFMEMORY |
Out of memory |
DN_E_INVALID_STATE |
Factory already exists |
function DN_PathToFileUrl(
pathUtf8 : PAnsiChar; // Windows path, UTF-8
bufUtf8 : PAnsiChar; // output buffer (nil → query length only)
cap : Cardinal; // buffer capacity
pWritten : PCardinal // bytes written (may be nil)
): HRESULT; cdecl;Converts a Windows path to a canonical file: URL (WHATWG/Node.js semantics).
Required for virtual_filename_utf8 with ESM scripts: Node.js requires a file: URL as resource_name for ESM.
var
Buf: array[0..511] of AnsiChar;
begin
DN_PathToFileUrl('C:\scripts\main.mjs', @Buf, SizeOf(Buf), nil);
// Buf → 'file:///C:/scripts/main.mjs'
end;GUID: {790CB9D8-0989-49B0-A1DE-1AC00DF9BDA6} (v2: ShutdownPlatform added)
Manages the process-wide Node.js/V8 state. Created by DN_CreateFactory.
Supports QueryInterface for INodeLog (factory-level log).
function CreateRuntime(
pConfig : PDNRuntimeConfig; // config (copied — may be freed after the call)
out ppRuntime: INodeRuntime // refcount = 1
): HRESULT; stdcall;Creates a new runtime instance. V1 supports one active runtime per process.
function GetLastError(
bufUtf8 : PAnsiChar; // nil → query length
cap : Cardinal;
pWritten : PCardinal
): HRESULT; stdcall;Returns the extended error message for the last failed call (thread-local).
function GetVersion(
bufUtf8 : PAnsiChar;
cap : Cardinal
): HRESULT; stdcall;Returns a version string in the format delphiLibNodeJS/1.0.0 node/26.x.x.
function GetNpmRunner(out ppRunner: INodeNpmRunner): HRESULT; stdcall;Returns (or lazily creates) the singleton embedded npm runner for this factory. npm output is available via QueryInterface(IID_INodeLog) on the returned object.
function ShutdownPlatform: HRESULT; stdcall;Deterministic shutdown of the V8 platform. This is a process-wide operation — must be the last call to the DLL before FreeLibrary / process exit.
Preconditions:
- All
INodeRuntimeinstances must be stopped and released (Shutdown+Release). - After this call, a new
DN_CreateFactorywill returnDN_E_INVALID_STATE.
Usage pattern:
Runtime.Shutdown(5000);
Runtime.Release;
Factory.ShutdownPlatform; // 4. platform shut down
Factory.Release;
// now safe to FreeLibrary / process exit| Result | Meaning |
|---|---|
S_OK |
Platform shut down (idempotent) |
DN_E_INVALID_STATE |
Active runtimes exist; stop them first |
GUID: {7C8D9E0F-1A2B-3C4D-5E6F-7A8B9C0D1E2F}
Manages the lifecycle of a single Node.js event loop.
Recommended lifecycle: CreateRuntime → Start → (work) → Shutdown → (Release)
Classic pattern: Start → Stop → Join → (Release)
Supports QueryInterface for INodeLog, INodeNpm, INodeBridge.
| Constant | Value | Description |
|---|---|---|
DN_RS_CREATED |
0 |
Created, Start not yet called |
DN_RS_RUNNING |
1 |
Event loop running |
DN_RS_STOPPING |
2 |
Stop called, waiting for event loop |
DN_RS_STOPPED |
3 |
Worker thread finished, V8 released |
function Start: HRESULT; stdcall;Starts the event loop on a separate OS thread. Non-blocking — returns immediately after the thread starts.
| Result | Meaning |
|---|---|
S_OK |
Thread started |
DN_E_INVALID_STATE |
Start has already been called |
function Stop: HRESULT; stdcall;Requests graceful shutdown (sends process.exit to the event loop). Non-blocking. Thread-safe.
function Join(
timeoutMs : Cardinal; // DN_TIMEOUT_INFINITE ($FFFFFFFF) = wait forever; 0 = return immediately
pExitCode : PInteger // Node.js exit code (may be nil)
): HRESULT; stdcall;Waits for the event loop thread to finish.
| Result | Meaning |
|---|---|
S_OK |
Clean exit |
DN_E_SCRIPT_EXCEPTION |
Entry script threw an unhandled exception |
function RunScriptFile(
pathUtf8 : PAnsiChar // absolute path or path relative to scripts_path
): HRESULT; stdcall;Loads and executes a JS file. The format (CJS/ESM) is determined automatically by extension (.mjs → ESM, .cjs → CJS, .js → per configuration).
function RunScriptText(
sourceUtf8 : PAnsiChar; // JavaScript source text
virtualFilenameUtf8 : PAnsiChar // virtual filename (nil = auto); for ESM use a file: URL
): HRESULT; stdcall;Executes inline JavaScript. Can be called multiple times after Start to inject commands.
// Simple call
Runtime.RunScriptText('console.log("hello")', nil);
// For ESM, virtualFilename must be a file: URL
var
VFileUrl: array[0..511] of AnsiChar;
begin
DN_PathToFileUrl(PAnsiChar(AnsiString(TmpDir + '\main.mjs')),
@VFileUrl, SizeOf(VFileUrl), nil);
Runtime.RunScriptText(PAnsiChar(EsmSource), @VFileUrl);
end;function Shutdown(
timeoutMs : Cardinal // DN_TIMEOUT_INFINITE ($FFFFFFFF) = wait forever; 0 = poll once
): HRESULT; stdcall;Atomic Stop + wait for the worker thread. All V8 cleanup runs inside the worker thread under v8::Locker, preventing ACCESS_VIOLATION on runtime destruction. Once Shutdown returns, the object can be released immediately.
| Result | Meaning |
|---|---|
S_OK |
Completed cleanly |
S_FALSE |
Already stopped |
DN_E_INVALID_STATE |
Start has not been called |
HRESULT_FROM_WIN32(ERROR_TIMEOUT) |
timeoutMs exceeded |
// Recommended shutdown pattern
NodeCheck(Runtime.Shutdown(DN_TIMEOUT_INFINITE), Factory); // wait forever
Runtime := nil; // Releasefunction GetState(
out state : Cardinal // receives a DN_RS_* constant
): HRESULT; stdcall;Returns the current runtime state. Analogous to TComponent.ComponentState in the Delphi RTL.
var State: Cardinal;
Runtime.GetState(State);
WriteLn('State: ', RuntimeStateName(State)); // 'RUNNING', 'STOPPED', ...function EnableInspector(
hostUtf8 : PAnsiChar; // listening host, e.g. '127.0.0.1'; nil = '127.0.0.1'
port : Word; // DevTools port, typically 9229
pauseOnStart : LongBool // True = wait for debugger connection before executing code
): HRESULT; stdcall;Enables the V8 Inspector (Chrome DevTools Protocol). Called before Start. The debugger connects in Chrome/Edge at devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=<host>:<port>.
| Result | Meaning |
|---|---|
S_OK |
Inspector scheduled; activates on Start |
DN_E_INVALID_STATE |
Runtime is already running |
E_INVALIDARG |
Invalid parameters; or hostUtf8 is not a loopback address (127.0.0.1, ::1, localhost) — non-loopback is forbidden by default |
// Enable Inspector on port 9229, wait for debugger
NodeCheck(Runtime.EnableInspector('127.0.0.1', 9229, True), Factory);
NodeCheck(Runtime.Start, Factory);
// Open chrome://inspect in Chrome → Remote Target → inspectfunction WaitReady(
timeoutMs : Cardinal; // max wait in ms; DN_TIMEOUT_INFINITE ($FFFFFFFF) = wait forever
pStartupHr : PHRESULT // optional; receives startup result code
): HRESULT; stdcall;Blocks the calling thread until the runtime has finished starting up and is ready to
accept CallFunction/PostFunction calls.
Readiness semantics:
- CJS / synchronous scripts: signals immediately after
LoadEnvironmentreturns — the synchronous frame has completed. - ESM modules with top-level
await: signals only after the entry-module Promise settles (fulfills or rejects). This means WaitReady is safe to use as a "fully initialized" signal — all top-levelawaitchains have resolved before it returnsS_OK.
Call after Start() to reliably determine whether the runtime is ready for CallFunction.
| Result | Meaning |
|---|---|
S_OK |
Ready event received; check *pStartupHr for startup outcome |
HRESULT_FROM_WIN32(ERROR_TIMEOUT) |
Timeout elapsed before startup completed |
DN_E_INVALID_STATE |
Start() has not been called yet |
*pStartupHr receives one of:
S_OK— entry script / ESM module initialized successfullyDN_E_SCRIPT_EXCEPTION— entry script threw a top-level exception, or ESMawaitrejectedDN_E_RUNTIME_STOPPED— runtime was stopped before the ESM Promise settledE_FAIL— internal bootstrap error
NodeCheck(Runtime.Start, Factory);
var startupHr: HRESULT;
var hr := Runtime.WaitReady(5000, @startupHr);
if hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT) then
raise ENodeError.Create('Runtime failed to start within 5 seconds')
else if FAILED(startupHr) then
raise ENodeError.CreateFmt('Entry script error: %s', [Runtime.GetLastError]);GUID: {BE136A3D-0D3A-45F8-B928-B19A96B948A6}
Provides access to the log ring buffer. Obtained via QI on INodeFactory or INodeRuntime.
Log := Runtime as INodeLog; // or: Factory as INodeLogCaptures: console.log/warn/error, process.stdout/stderr.write, unhandled exceptions, process.on('warning').
function SetCallback(
pfn : TDNLogCallback; // nil → remove callback
pUserData : Pointer
): HRESULT; stdcall;Registers a callback invoked on the event loop thread. Must be non-blocking. Exceptions must not cross the ABI boundary — wrap in try..except.
procedure MyLogCallback(pRecord: PDNLogRecord; pUserData: Pointer); cdecl;
begin
try
Writeln(string(pRecord^.message_utf8));
except
// always catch — exception across ABI = UB
end;
end;
// Register before Start:
Log.SetCallback(@MyLogCallback, nil);function SetLevel(Level: Cardinal): HRESULT; stdcall;Sets the minimum log level. Records below the level are discarded. Default: DN_LOG_DEBUG.
function Poll(pOut: PDNLogRecord): HRESULT; stdcall;Extracts one record from the ring buffer.
| Result | Meaning |
|---|---|
S_OK |
Record retrieved |
S_FALSE |
Buffer empty |
Important:
pOut.source_utf8andmessage_utf8are valid only until the nextPollcall.
// Typical polling loop
var Rec: TDNLogRecord;
while Log.Poll(@Rec) = S_OK do
begin
case Rec.level of
DN_LOG_INFO : Writeln('[I] ' + string(Rec.message_utf8));
DN_LOG_WARN : Writeln('[W] ' + string(Rec.message_utf8));
DN_LOG_ERROR: Writeln('[E] ' + string(Rec.message_utf8));
end;
end;GUID: {A4AEE322-C1B0-4E4D-880E-B4FBC75BF0AD}
Runs npm operations from Delphi. Obtained via QI on INodeRuntime.
Npm := Runtime as INodeNpm;npm is launched via CreateProcess("cmd.exe /C npm …"). Requires npm.cmd on %PATH%.
function Exec(
pCfg : PDNNpmConfig; // nil → defaults
argvUtf8 : PAnsiChar // space-separated arguments, e.g. 'run build'
): HRESULT; stdcall;Executes an arbitrary npm command synchronously.
function Install(
pCfg : PDNNpmConfig;
packageUtf8 : PAnsiChar // package name, e.g. 'lodash@4'
): HRESULT; stdcall;Runs npm install <package> in pCfg.work_dir_utf8.
function InstallFromPackageJson(
pCfg : PDNNpmConfig // work_dir_utf8 = directory containing package.json
): HRESULT; stdcall;Runs npm install (reads package.json) in pCfg.work_dir_utf8.
var
NpmCfg: TDNNpmConfig;
WorkDir: AnsiString;
begin
WorkDir := AnsiString(CommonDir);
FillChar(NpmCfg, SizeOf(NpmCfg), 0);
NpmCfg.size := SizeOf(NpmCfg);
NpmCfg.work_dir_utf8 := PAnsiChar(WorkDir);
OleCheck(Npm.InstallFromPackageJson(@NpmCfg));
end;GUID: {848A16FC-57BE-4EF3-A5CB-3D96EBFAC23B}
Embedded npm without a system installation. Obtained via INodeFactory.GetNpmRunner() or QI(IID_INodeNpmRunner) on the factory.
var Runner: INodeNpmRunner;
Runner := Factory as INodeNpmRunner; // or Factory.GetNpmRunner(Runner)Operates in two modes (selected automatically):
- Mode C-1 — uses
npm-cli.jsfrom<dll-dir>\npm\bin\npm-cli.js(files on disk). - Mode C-2 — uses an esbuild bundle embedded directly in the DLL as
const char[](no files on disk required).
type
TDNNpmRunnerConfig = record
size : Cardinal; // SizeOf(TDNNpmRunnerConfig) — versioning guard
work_dir_utf8 : PAnsiChar; // working directory for npm (--prefix); required
node_exe_utf8 : PAnsiChar; // path to node.exe for lifecycle scripts; nil = auto-detect
flags : Cardinal; // combination of DN_NPM_RUNNER_* flags
join_timeout_ms: Cardinal; // wait timeout; 0 = 10 minutes
end;flags constants:
| Constant | Value | Description |
|---|---|---|
DN_NPM_RUNNER_ENABLE_SCRIPTS |
$0001 |
Allow lifecycle scripts (preinstall/postinstall) |
DN_NPM_RUNNER_FORCE_BUNDLE |
$0002 |
Force Mode C-2 (bundle) |
DN_NPM_RUNNER_FORCE_DISK |
$0004 |
Force Mode C-1 (files on disk) |
function RunNpm(
pConfig : PDNNpmRunnerConfig; // configuration; required; work_dir_utf8 is required
commandUtf8 : PAnsiChar; // npm command, e.g. 'install lodash@4 --save'
pExitCode : PInteger // receives npm exit code; 0 = success; nil = ignore
): HRESULT; stdcall;| Result | Meaning |
|---|---|
S_OK |
npm finished (check *pExitCode) |
E_INVALIDARG |
pConfig or commandUtf8 = nil / invalid size |
E_NOTIMPL |
Neither Mode C-1 files nor Mode C-2 bundle is available |
E_FAIL |
Internal runtime launch error |
var
Runner : INodeNpmRunner;
RunCfg : TDNNpmRunnerConfig;
ExitCode : Integer;
WorkDir : UTF8String;
begin
Runner := Factory as INodeNpmRunner;
WorkDir := UTF8String(GetCurrentDir);
FillChar(RunCfg, SizeOf(RunCfg), 0);
RunCfg.size := SizeOf(RunCfg);
RunCfg.work_dir_utf8 := PAnsiChar(WorkDir);
NodeCheck(Runner.RunNpm(@RunCfg, 'install lodash@4 --save', @ExitCode), Factory);
if ExitCode <> 0 then
raise Exception.CreateFmt('npm exited with code %d', [ExitCode]);
end;npm output is available via
QI(IID_INodeLog)on theINodeNpmRunnerobject. npmstdout/stderris captured and placed in the ring log.
GUID: {E5F6A7B8-C9D0-4E1F-2A3B-4C5D6E7F8A9B} (v7: AllocSharedBuffer + IDNSharedBufferHandle)
Bidirectional Delphi↔JS bridge. Obtained via QI on INodeRuntime after Start.
Bridge := Runtime as INodeBridge;Direction A (Delphi→JS): CallFunction — calls a globalThis function with JSON arguments; blocks the calling thread until a JSON result is received.
Direction B (JS→Delphi): RegisterExport — publishes a Delphi object (IDispatch) as globalThis.bridge.<ns> in JS.
Lifecycle: after
Runtime.Shutdown()/Runtime.Stop()+Join(), the bridge automatically detaches from V8 (viaDetachFromV8()). SubsequentCallFunctioncalls immediately returnDN_E_INVALID_STATE— no Access Violation. ReleasingBridge := nilis safe at any point afterShutdown.
function CallFunction(
fnNameUtf8 : PAnsiChar; // function name in globalThis
argsJsonUtf8 : PAnsiChar; // JSON argument array, e.g. '[1, "hello"]'
timeoutMs : Cardinal; // DN_TIMEOUT_INFINITE ($FFFFFFFF) = wait forever
pResult : PDNCallResult // receives result (nil = ignore)
): HRESULT; stdcall;Calls globalThis.<fnName>(args...) in the JS environment, blocking the calling thread.
| Result | Meaning |
|---|---|
S_OK |
Success; pResult->json_utf8 contains the JSON response |
DN_E_NOT_REGISTERED |
Function not found on globalThis |
DN_E_CALL_FAILED |
JS threw an exception or returned a rejection |
DN_E_CALL_TIMEOUT |
timeoutMs expired |
DN_E_INVALID_STATE |
Runtime is not running or has been stopped |
var Res: TDNCallResult;
NodeCheck(Bridge.CallFunction('calcTax', '[1234.56, 0.2]', 5000, @Res), Factory);
WriteLn(string(AnsiString(Res.json_utf8))); // "246.912"
Bridge.FreeResult(@Res);function FreeResult(pResult: PDNCallResult): HRESULT; stdcall;Frees the string allocated by the DLL. Passing nil or pResult->json_utf8 = nil is a no-op.
function RegisterExport(
nsUtf8 : PAnsiChar; // namespace name (JS identifier)
pDisp : IDispatch; // Delphi object (AddRef'd by the bridge)
flags : Cardinal; // DN_EXPORT_WORKER or DN_EXPORT_MAIN
hwnd : HWND // only for DN_EXPORT_MAIN; 0 for WORKER
): HRESULT; stdcall;Publishes a Delphi object as globalThis.bridge.<ns> in JS. Methods with the Async suffix in JS return a Promise.
| Flag | Meaning |
|---|---|
DN_EXPORT_WORKER (0) |
IDispatch::Invoke is called directly on the event loop thread |
DN_EXPORT_MAIN (1) |
Call is marshaled via WM_DN_DISPATCH to the Delphi thread (hwnd); JS receives a Promise |
// Publish object for Direction B
var Disp: IDispatch;
Disp := TDNExport.Wrap(TMyService.Create);
Bridge.RegisterExport('svc', Disp, DN_EXPORT_WORKER, 0);
// In JS: const v = bridge.svc.GetVersion();function ProcessMainThreadPending: HRESULT; stdcall;For DN_EXPORT_MAIN: call from WndProc when WM_DN_DISPATCH is received. Executes all pending IDispatch::Invoke on the current thread and signals JS completion.
Use
TDNMainDispatcherfromNodeBridgeHelpers.pas— it creates a hidden window and handlesWM_DN_DISPATCHautomatically.
| Class | Purpose |
|---|---|
TDNCallProxy |
Typed wrapper for CallFunction — accepts/returns AnsiString |
TDNExport.Wrap |
Publishes a TObject with published methods as IDispatch for RegisterExport |
TDNMainDispatcher |
Creates a hidden window, handles WM_DN_DISPATCH on the main thread |
BridgeCallFunction |
Procedural wrapper for a single call without manual Proxy management |
// Direction A — blocking call via TDNCallProxy
var Proxy: TDNCallProxy;
Proxy := TDNCallProxy.Create(Bridge, 30000 {ms});
try
ResultJson := Proxy.Call('loadAndRender', ArgsJson);
finally
Proxy.Free;
end;Always:
FillChar(Cfg, SizeOf(Cfg), 0)+Cfg.size := SizeOf(Cfg)before use.
Passed to DN_CreateFactory. All fields are optional (nil/0 = defaults).
| Field | Type | Description |
|---|---|---|
size |
Cardinal | Required: SizeOf(TDNFactoryConfig) |
argv0_utf8 |
PAnsiChar | process.argv[0]; nil → 'node' |
process_exec_path_utf8 |
PAnsiChar | process.execPath; nil → argv0_utf8 |
initial_node_options_utf8 |
PAnsiChar | Additional NODE_OPTIONS; nil = none |
platform_thread_pool_size |
Cardinal | V8 platform threads; 0 = default |
libuv_threadpool_size |
Cardinal | UV_THREADPOOL_SIZE; 0 = 4 |
max_js_worker_threads |
Cardinal | worker_threads limit; 0 = default |
log_capacity |
Cardinal | Ring buffer slots; 0 = 4096 |
flags |
Cardinal | Reserved; 0 |
Passed to INodeFactory.CreateRuntime.
| Field | Type | Description |
|---|---|---|
size |
Cardinal | Required: SizeOf(TDNRuntimeConfig) |
scripts_path_utf8 |
PAnsiChar | Base directory / cwd; nil → exe directory |
script_path_utf8 |
PAnsiChar | Path to the entry file .js/.mjs/.cjs; nil if script_text_utf8 is used |
script_text_utf8 |
PAnsiChar | Inline JS source; nil if script_path_utf8 is used |
virtual_filename_utf8 |
PAnsiChar | Virtual filename for script_text; for ESM — a file: URL |
node_options_utf8 |
PAnsiChar | Per-runtime NODE_OPTIONS |
env_block_utf8 |
PAnsiChar | Double-NUL-terminated block of KEY=VALUE\0KEY=VALUE\0\0; nil = inherit parent env |
extra_module_paths_utf8 |
PAnsiChar | Additional paths in NODE_PATH |
module_format |
Cardinal | DN_MODULE_FORMAT_* |
flags |
Cardinal | DN_RUNTIME_ALLOW_* |
extra_module_paths_utf8 — path to the node_modules directory (not its parent):
CommonModules := AnsiString(CommonDir + '\node_modules');
Cfg.extra_module_paths_utf8 := PAnsiChar(CommonModules);
// CommonModules must remain alive until CreateRuntime is called!⚠ Known Limitation — process-wide NODE_PATH
extra_module_paths_utf8is implemented by setting the process-wideNODE_PATHenvironment variable for the duration of the runtime. If twoTNodeRuntimeinstances with differentextra_module_pathsare started concurrently, the secondStart()will capture the already-modifiedNODE_PATHas its "original" value, and restoring it onStop()will produce incorrect results for the first runtime.Safe usage: use
extra_module_paths_utf8only when at most one runtime is active at a time. A per-environment module resolution API is planned for a future version.
env_block_utf8 — double-NUL-terminated block, same as Windows SetEnvironmentStrings format:
var Block: AnsiString;
Block := 'KEY1=val1' + #0 + 'KEY2=val2' + #0 + #0; // final double-NUL
Cfg.env_block_utf8 := PAnsiChar(Block);Each KEY=VALUE entry is separated by a single NUL byte; the block ends with an additional NUL
(i.e., two consecutive NUL bytes at the end). nil inherits the parent-process environment.
Passed to INodeNpm methods.
| Field | Type | Description |
|---|---|---|
size |
Cardinal | Required: SizeOf(TDNNpmConfig) |
work_dir_utf8 |
PAnsiChar | Working directory; nil → runtime scripts_path |
env_block_utf8 |
PAnsiChar | Env block (same format as TDNRuntimeConfig); nil = inherit |
timeout_ms |
Cardinal | npm process timeout in ms; 0 = built-in default 300 000 ms (5 min) |
flags |
Cardinal | Reserved; 0 |
Returned by INodeLog.Poll and passed to the callback.
| Field | Type | Description |
|---|---|---|
timestamp_ms |
UInt64 | Milliseconds since epoch |
sequence |
UInt64 | Monotonic sequence number |
level |
Cardinal | DN_LOG_* constant |
thread_id |
Cardinal | OS thread ID |
source_utf8 |
PAnsiChar | Component, e.g. 'runtime', 'npm.stdout' |
message_utf8 |
PAnsiChar | UTF-8 message |
| Constant | Value | Description |
|---|---|---|
DN_LOG_TRACE |
0 | Very detailed trace |
DN_LOG_DEBUG |
1 | Debug messages |
DN_LOG_INFO |
2 | Informational |
DN_LOG_WARN |
3 | Warnings |
DN_LOG_ERROR |
4 | Errors |
DN_LOG_FATAL |
5 | Fatal errors |
| Constant | Value | Description |
|---|---|---|
DN_MODULE_FORMAT_AUTO |
0 | Auto by extension (.mjs→ESM, .cjs→CJS) |
DN_MODULE_FORMAT_COMMONJS |
1 | Force CJS |
DN_MODULE_FORMAT_ESM |
2 | Force ESM |
| Constant | Value | Description |
|---|---|---|
DN_RUNTIME_ALLOW_VIRTUAL_ESM_RESOURCE |
$00000001 |
Allow ESM from RunScriptText |
| Constant | Value | Description |
|---|---|---|
DN_E_SCRIPT_EXCEPTION |
$80040001 |
JS threw an unhandled exception |
DN_E_INVALID_STATE |
$80040002 |
Invalid operation for current state |
DN_E_RUNTIME_STOPPED |
$80040003 |
Runtime stopped, cannot restart |
DN_E_INVALID_ARG |
$80040004 |
Invalid argument |
DN_E_CALL_TIMEOUT |
$80040005 |
INodeBridge.CallFunction — timeout expired |
DN_E_CALL_FAILED |
$80040006 |
INodeBridge.CallFunction — JS threw exception/rejection |
DN_E_NOT_REGISTERED |
$80040007 |
INodeBridge.CallFunction — function not found on globalThis |
| Constant | Value | Description |
|---|---|---|
DN_EXPORT_WORKER |
0 |
IDispatch::Invoke is called on the event loop thread |
DN_EXPORT_MAIN |
1 |
Call is marshaled via WM_DN_DISPATCH to the Delphi thread (hwnd) |
WM_DN_DISPATCH |
WM_USER + $D11 |
Windows message for dispatch via TDNMainDispatcher |
INodeLog, INodeNpm, INodeBridge are obtained via QI on the same COM object as INodeRuntime. They all implement COM refcounting (AddRef/Release): the object self-destructs when the last reference drops to zero.
Recommended pattern — Shutdown first:
// After Shutdown, V8 is cleanly stopped; Bridge, Npm, Log may be released in any order.
NodeCheck(Runtime.Shutdown(10000), Factory); // Stop + WaitForWorkerThread
Bridge := nil; // Release; if refcount > 0, object stays alive but detached (CallFunction → DN_E_INVALID_STATE)
Log := nil;
Npm := nil;
Runtime := nil; // Release runtime
Factory := nil; // Release factory (last)Classic pattern — Stop + Join:
// Explicit release order for derived interfaces is not required if Shutdown was called first.
// Without Shutdown — release derived interfaces BEFORE Runtime:
Log := nil;
Npm := nil;
Bridge := nil;
Runtime.Stop;
Runtime.Join(5000, nil);
Runtime := nil;
Factory := nil;Why Shutdown matters: during Shutdown, the worker thread resets all V8 Globals (v8::Global<Context>, v8::Global<Promise::Resolver>) inside v8::Locker before releasing the Locker. This guarantees that ~TNodeBridge (called when the last COM reference drops — possibly after iso->Dispose()) does not touch dead V8 objects. The release order of Delphi variables does not matter in this case.
- Getting Started — installation and first run
- Examples — 20 working examples with real usage patterns
- Build from Source — how to build Node.js and the DLL
All vtable methods are callable from any thread with respect to internal native state. However, the following distinctions apply:
| Method | Caller thread | Notes |
|---|---|---|
INodeFactory::CreateRuntime |
Any | Thread-safe singleton |
INodeRuntime::Start |
Any, once | Sets up OS worker thread |
INodeRuntime::Stop |
Any | Signals stop; non-blocking |
INodeRuntime::Join |
Any (not worker) | Blocks until worker exits |
INodeRuntime::Shutdown |
Any (not worker) | Stop + Join in one call |
INodeLog::Poll |
Any | Lock-free ring buffer |
INodeLog::SetCallback |
Any | Callback fired on worker thread |
INodeBridge::CallFunction |
Any (not worker) | Blocks caller thread; worker-thread re-entrant call causes deadlock |
INodeBridge::PostFunction |
Any | Non-blocking; callback on main thread (hwnd) or worker thread (fallback) |
INodeBridge::Evaluate |
Any (not worker) | Same threading rules as CallFunction |
INodeBridge::RegisterExport |
Any | Safe to call before Start |
INodeBridge::ProcessMainThreadPending |
Main thread only | Pumps Direction-B callbacks; call from Delphi message loop |
INodeBridge::AllocSharedBuffer |
Any (not worker) | Allocates V8 SAB; blocks until V8 allocation completes |
COM apartment requirements:
DN_EXPORT_WORKERhandlers are invoked on the Node.js worker thread (MTA). RegisteredIDispatchobjects must be free-threaded or GIT-marshaled. STA-only objects (standard DelphiTAutoObject) will fail with COM apartment errors.DN_EXPORT_MAINhandlers are dispatched on the Delphi main thread viaWM_DN_DISPATCH. VCL-bound objects are safe to use here.
Process-wide side effects:
INodeFactory::Release(last reference) — terminal V8 platform shutdown; cannot re-initialize in same process.INodeRuntime::Start— setsNODE_PATH,NODE_EXEC_PATH,UV_THREADPOOL_SIZEenv vars process-wide for the duration of the runtime's lifetime.