Skip to content

Commit 9a48e53

Browse files
committed
refactor: unify GetLastError/errno API with Python ctypes semantics
- Native: CTypesAddon carries captured_last_error / captured_errno as instance data (per-environment, thread-isolated by N-API) - FFIFunction::Call with use_last_error / use_errno updates both the per-FFIFunction slot and the addon-level slot atomically after ffi_call - Top-level GetLastError() / SetLastError() / get_errno() / set_errno() now read/write the private slot only — parity with Python ctypes - platform/errors.js reduced to FormatError / WinError plumbing - README + one test updated for the new semantics
1 parent d929e33 commit 9a48e53

8 files changed

Lines changed: 171 additions & 360 deletions

File tree

‎README.md‎

Lines changed: 51 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -204,40 +204,41 @@ console.log(`${st.wYear}-${st.wMonth}-${st.wDay}`);
204204

205205
## API Compatibility Reference
206206

207-
| Feature | Python ctypes | node-ctypes |
208-
| -------------------- | --------------------------------- | ----------------------------------------------- |
209-
| Load library | `CDLL("lib.so")` | `new CDLL("lib.so")` |
210-
| Function setup | `f.argtypes = [c_int]` | `f.argtypes = [c_int]` |
211-
| Structs | `class P(Structure):` | `class P extends Structure` |
212-
| Unions | `class U(Union):` | `class U extends Union` |
213-
| Arrays | `c_int * 5` | `array(c_int, 5)` |
214-
| Bit fields | `("f", c_uint, 3)` | `["f", c_uint32, 3]` |
215-
| Callbacks | `CFUNCTYPE(c_int, c_int)` | `CFUNCTYPE(c_int, c_int)` |
216-
| Pointers | `POINTER(c_int)` / `pointer(obj)` | `POINTER(c_int)` / `pointer(obj)` |
217-
| Pointer arithmetic | `p[i]`, C-style `ptr + n` | `p[i]`, `p.add(n)`, `p.slice(a, b)` |
218-
| Sizeof | `sizeof(c_int)` | `sizeof(c_int)` |
219-
| Alignment | `alignment(c_int)` | `alignment(c_int)` |
220-
| Strings | `c_char_p(b"hello")` | `create_string_buffer("hello")` |
221-
| Variadic | `sprintf(buf, b"%d", 42)` | `sprintf(buf, "%d", 42)` |
222-
| Errno | `get_errno()` | `get_errno()` |
223-
| byref | `byref(obj)` | `byref(obj)` |
224-
| cast | `cast(ptr, type)` | `cast(ptr, type)` (supports `POINTER()` target) |
225-
| Find library | `ctypes.util.find_library("c")` | `find_library("c")` |
226-
| Lazy load | `ctypes.cdll.msvcrt.printf(...)` | `cdll.msvcrt.printf(...)` |
227-
| `from_buffer` | `S.from_buffer(buf)` | `S.from_buffer(buf)` |
228-
| `from_buffer_copy` | `S.from_buffer_copy(buf)` | `S.from_buffer_copy(buf)` |
229-
| `from_address` | `S.from_address(addr)` | `S.from_address(addr)` |
230-
| `in_dll` | `c_int.in_dll(lib, "errno")` | `c_int.in_dll(lib, "errno")` |
231-
| `_as_parameter_` | Unwrap on call | Unwrap on call |
232-
| `from_param` | Custom conversion | Custom conversion |
233-
| `use_last_error` | `CDLL(..., use_last_error=True)` | `new CDLL(..., { use_last_error: true })` |
234-
| `use_errno` | `CDLL(..., use_errno=True)` | `new CDLL(..., { use_errno: true })` |
235-
| `OleDLL` / `HRESULT` | `OleDLL("ole32")` auto-raises | `new OleDLL("ole32.dll")` auto-throws |
236-
| `paramflags` | `proto((name, lib), paramflags)` | `proto.bind(lib, name, paramflags)` |
237-
| Big-endian struct | `class X(BigEndianStructure)` | `class X extends BigEndianStructure` |
238-
| Little-endian struct | `class X(LittleEndianStructure)` | `class X extends LittleEndianStructure` |
239-
| Big-endian union | `class X(BigEndianUnion)` | `class X extends BigEndianUnion` |
240-
| Little-endian union | `class X(LittleEndianUnion)` | `class X extends LittleEndianUnion` |
207+
| Feature | Python ctypes | node-ctypes |
208+
| --------------------- | --------------------------------------- | ----------------------------------------------- |
209+
| Load library | `CDLL("lib.so")` | `new CDLL("lib.so")` |
210+
| Function setup | `f.argtypes = [c_int]` | `f.argtypes = [c_int]` |
211+
| Structs | `class P(Structure):` | `class P extends Structure` |
212+
| Unions | `class U(Union):` | `class U extends Union` |
213+
| Arrays | `c_int * 5` | `array(c_int, 5)` |
214+
| Bit fields | `("f", c_uint, 3)` | `["f", c_uint32, 3]` |
215+
| Callbacks | `CFUNCTYPE(c_int, c_int)` | `CFUNCTYPE(c_int, c_int)` |
216+
| Pointers | `POINTER(c_int)` / `pointer(obj)` | `POINTER(c_int)` / `pointer(obj)` |
217+
| Pointer arithmetic | `p[i]`, C-style `ptr + n` | `p[i]`, `p.add(n)`, `p.slice(a, b)` |
218+
| Sizeof | `sizeof(c_int)` | `sizeof(c_int)` |
219+
| Alignment | `alignment(c_int)` | `alignment(c_int)` |
220+
| Strings | `c_char_p(b"hello")` | `create_string_buffer("hello")` |
221+
| Variadic | `sprintf(buf, b"%d", 42)` | `sprintf(buf, "%d", 42)` |
222+
| Errno slot | `get_errno()` / `set_errno()` | `get_errno()` / `set_errno()` |
223+
| Last-error slot (Win) | `get_last_error()` / `set_last_error()` | `GetLastError()` / `SetLastError()` |
224+
| byref | `byref(obj)` | `byref(obj)` |
225+
| cast | `cast(ptr, type)` | `cast(ptr, type)` (supports `POINTER()` target) |
226+
| Find library | `ctypes.util.find_library("c")` | `find_library("c")` |
227+
| Lazy load | `ctypes.cdll.msvcrt.printf(...)` | `cdll.msvcrt.printf(...)` |
228+
| `from_buffer` | `S.from_buffer(buf)` | `S.from_buffer(buf)` |
229+
| `from_buffer_copy` | `S.from_buffer_copy(buf)` | `S.from_buffer_copy(buf)` |
230+
| `from_address` | `S.from_address(addr)` | `S.from_address(addr)` |
231+
| `in_dll` | `c_int.in_dll(lib, "errno")` | `c_int.in_dll(lib, "errno")` |
232+
| `_as_parameter_` | Unwrap on call | Unwrap on call |
233+
| `from_param` | Custom conversion | Custom conversion |
234+
| `use_last_error` | `CDLL(..., use_last_error=True)` | `new CDLL(..., { use_last_error: true })` |
235+
| `use_errno` | `CDLL(..., use_errno=True)` | `new CDLL(..., { use_errno: true })` |
236+
| `OleDLL` / `HRESULT` | `OleDLL("ole32")` auto-raises | `new OleDLL("ole32.dll")` auto-throws |
237+
| `paramflags` | `proto((name, lib), paramflags)` | `proto.bind(lib, name, paramflags)` |
238+
| Big-endian struct | `class X(BigEndianStructure)` | `class X extends BigEndianStructure` |
239+
| Little-endian struct | `class X(LittleEndianStructure)` | `class X extends LittleEndianStructure` |
240+
| Big-endian union | `class X(BigEndianUnion)` | `class X extends BigEndianUnion` |
241+
| Little-endian union | `class X(LittleEndianUnion)` | `class X extends LittleEndianUnion` |
241242

242243
## Supported Types
243244

@@ -290,17 +291,29 @@ const CoInit = ole32.func("CoInitializeEx", HRESULT, [c_void_p, c_uint32]);
290291
CoInit(null, 0); // throws on failure, no manual check needed
291292
```
292293

293-
### Snapshotted errno / GetLastError
294+
### Snapshotted errno / GetLastError (Python ctypes parity)
295+
296+
`GetLastError()` / `get_errno()` read a **private thread-local slot** — exactly
297+
like `ctypes.get_last_error()` / `ctypes.get_errno()` in Python. The slot is
298+
updated **only** by FFI calls through libraries opened with `use_last_error`
299+
or `use_errno`. If you never set that flag, the slot stays at 0.
294300

295301
```javascript
302+
import { WinDLL, GetLastError, c_void_p, c_wchar_p } from "node-ctypes";
303+
296304
const user32 = new WinDLL("user32.dll", { use_last_error: true });
297305
const FindWindow = user32.func("FindWindowW", c_void_p, [c_wchar_p, c_wchar_p]);
306+
298307
FindWindow("NoSuchClass", null);
299-
console.log(user32.get_last_error()); // ERROR_CANNOT_FIND_WND_CLASS
308+
309+
// Both read the same snapshot — the one taken atomically in native code
310+
// immediately after ffi_call, before any other JS can clobber it.
311+
console.log(GetLastError()); // top-level API (Python: ctypes.get_last_error)
312+
console.log(user32.get_last_error()); // per-library alias
300313
```
301314

302-
The snapshot is taken inside the native bridge immediately after `ffi_call`,
303-
so other Node.js code can't clobber it before you read it back.
315+
To read the **system** `GetLastError()` directly (ignoring the private slot),
316+
call the Win32 API manually as any other FFI function — same as Python.
304317

305318
### `paramflags` — out parameters become return values
306319

‎lib/index.js‎

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import {
3838
} from "./memory/buffer.js";
3939
import { readValue as _readValue, writeValue as _writeValue, sizeof as _sizeof } from "./memory/operations.js";
4040
import { addressOf as _addressOf, byref as _byref, cast as _cast, ptrToBuffer as _ptrToBuffer, POINTER as _POINTER, pointer as _pointer } from "./memory/pointer.js";
41-
import { get_errno as _get_errno, set_errno as _set_errno, GetLastError as _GetLastError, SetLastError as _SetLastError, FormatError as _FormatError, WinError as _WinError } from "./platform/errors.js";
41+
import { FormatError as _FormatError, WinError as _WinError } from "./platform/errors.js";
4242
import { bitfield as _bitfield, _isStruct, _isArrayType, _isBitField } from "./structures/helpers/common.js";
4343
import { array as _array } from "./structures/helpers/array.js";
4444
import { _readBitField as _readBitFieldHelper, _writeBitField as _writeBitFieldHelper, _readUintFromBuffer, _writeUintToBuffer } from "./structures/helpers/bitfield.js";
@@ -358,27 +358,42 @@ function array(elementType, count) {
358358
// Now imported from ./platform/errors.js - see that file for implementation details
359359
// ============================================================================
360360

361+
// get_errno / set_errno / GetLastError / SetLastError — parity Python ctypes.
362+
// Tutte e 4 leggono/scrivono SOLO lo slot thread-local privato mantenuto dal
363+
// native addon. Lo slot viene aggiornato da FFIFunction::Call solo quando la
364+
// library è stata aperta con use_errno / use_last_error: true. Se non hai mai
365+
// usato quella flag, lo slot è 0 — stesso comportamento di ctypes.get_errno()
366+
// in Python.
367+
//
368+
// Per leggere il valore di sistema "grezzo" basta aprire una library qualsiasi
369+
// con use_last_error: true e fare la call — oppure, come in Python, chiamare
370+
// kernel32.GetLastError / msvcrt._get_errno manualmente come FFI call.
371+
361372
function get_errno() {
362-
return _get_errno(CDLL, alloc, readValue, c_int32, c_void_p);
373+
return native.getCapturedErrno();
363374
}
364375

365376
function set_errno(value) {
366-
return _set_errno(value, CDLL, writeValue, c_int32, c_void_p);
377+
return native.setCapturedErrno(value);
367378
}
368379

369380
function GetLastError() {
370-
return _GetLastError(WinDLL, c_uint32, c_void, c_void_p);
381+
return native.getCapturedLastError();
371382
}
372383

373384
function SetLastError(code) {
374-
return _SetLastError(code, WinDLL, c_uint32, c_void, c_void_p);
385+
return native.setCapturedLastError(code);
375386
}
376387

377388
function FormatError(code) {
389+
// Parity Python: se code non specificato, usa lo slot privato
390+
// (ctypes.get_last_error()), non il GetLastError() di sistema.
391+
if (code === undefined) code = native.getCapturedLastError();
378392
return _FormatError(code, WinDLL, alloc, c_uint32, c_void, c_void_p);
379393
}
380394

381395
function WinError(code) {
396+
if (code === undefined) code = native.getCapturedLastError();
382397
return _WinError(code, WinDLL, alloc, c_uint32, c_void, c_void_p);
383398
}
384399

0 commit comments

Comments
 (0)