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

  1. Catch Win32Exception and treat the partially collected module list as best-effort output
  2. Filter out unexpected transient codes (e.g. ERROR_PARTIAL_COPY 299) and retry the snapshot
  3. Retry getModules once or twice for PIDs known to be short-lived
  4. 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

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


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)