java-native-access/jna · error · Win32Exception
Win32Exception from GetLastError after Module32NextW failed…
Error message
Win32Exception from GetLastError after Module32NextW failed with unexpected error
What it means
Kernel32Util.getModules throws this Win32Exception when Module32NextW returns false with a last-error other than ERROR_SUCCESS (0) or ERROR_NO_MORE_FILES (18). Those two codes mean the iteration finished normally; any other code is treated as a genuine failure during module-list iteration and is wrapped in a Win32Exception.
Solutions
- Catch Win32Exception and treat the partially collected module list as best-effort output
- Filter out unexpected transient codes (e.g. ERROR_PARTIAL_COPY 299) and retry the snapshot
- Retry getModules once or twice for PIDs known to be short-lived
- Check errorCode to distinguish real failures (6=invalid handle) from iteration completion
Example fix
// before
List<Tlhelp32.MODULEENTRY32W> mods = Kernel32Util.getModules(pid);
// after
List<Tlhelp32.MODULEENTRY32W> mods;
try {
mods = Kernel32Util.getModules(pid);
} catch (Win32Exception e) {
if (e.getErrorCode().intValue() == 299) { // ERROR_PARTIAL_COPY: process changed mid-scan
mods = Kernel32Util.getModules(pid); // single retry
} else {
throw e;
}
} Defensive patterns
Strategy: retry
Validate before calling
// ensure PID is still alive to reduce mid-iteration teardown races if (!pidAlive(pid)) return Collections.emptyList();
Type guard
null
Try / catch
List<Tlhelp32.MODULEENTRY32W> mods;
try {
mods = Kernel32Util.getModules(pid);
} catch (Win32Exception e) {
if (e.getErrorCode().intValue() == 299) { // ERROR_PARTIAL_COPY — transient
mods = Kernel32Util.getModules(pid);
} else {
throw e;
}
} Prevention
- Retry snapshots for PIDs whose module list may change mid-iteration
- Filter expected transient codes (ERROR_PARTIAL_COPY 299) from hard failures
- Use the partially returned data when best-effort output is acceptable
- Rate-limit scans of many PIDs to reduce contention with process churn
When it happens
Trigger: Calling Kernel32Util.getModules(processID) where the module iteration fails partway with an unexpected native error — the target process was modified/unloaded modules mid-iteration, the snapshot handle became invalid, or an access violation in the toolhelp walk surfaces as a real error code.
Common situations: Inspecting a rapidly starting/exiting process whose module list changes during enumeration, hooking/injection tools mutating modules concurrently, scanning many PIDs in a loop and hitting transient states.
Related errors
- Win32Exception from native GetLastError after…
- Win32Exception from native GetLastError after…
- LockResource returned null.
- Win32Exception from native GetLastError after…
- Win32Exception from native GetLastError after…
AI-assisted analysis of java-native-access/jna@d036ad9781 (2026-09-12).
Data as JSON: /api/errors/edc78a7e75bf8e19.
Report an issue: GitHub.
Appendix: source
Thrown at contrib/platform/src/com/sun/jna/platform/win32/Kernel32Util.java:1197
if (!Kernel32.INSTANCE.Module32FirstW(snapshot, first)) {
throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
}
List<Tlhelp32.MODULEENTRY32W> modules = new ArrayList<>();
modules.add(first);
Tlhelp32.MODULEENTRY32W next = new Tlhelp32.MODULEENTRY32W();
while (Kernel32.INSTANCE.Module32NextW(snapshot, next)) {
modules.add(next);
next = new Tlhelp32.MODULEENTRY32W();
}
int lastError = Kernel32.INSTANCE.GetLastError();
// if we got a false from Module32Next,
// check to see if it returned false because we're genuinely done
// or if something went wrong.
if (lastError != W32Errors.ERROR_SUCCESS && lastError != W32Errors.ERROR_NO_MORE_FILES) {
throw new Win32Exception(lastError);
}
return modules;
} catch (final Win32Exception e) {
throw we = e; // re-throw to avoid return value!
} finally {
cleanUp(snapshot, we);
}
}
/**
* Expands environment-variable strings and replaces them with the values
* defined for the current user.
*
* @param input A string that contains one or more environment-variable
* strings in the form: %variableName%. For each such
* reference, the %variableName% portion is replaced with the
* current value of that environment variable.View on GitHub (pinned to d036ad9781)