How to make GetLastError reliably work with JNA?

2019-03-01 03:23发布

问题:

I'm using various Win32 API functions in my Java application and I use GetLastError to get information on failed API calls.

Most of the time it works, but now I found a case where something seems to reset the last error.

This is what I do both in my Java application and my VB6 application:

  1. I open the handle to the process with PID 4 (System)
  2. I call GetModuleFileNameEx with that handle.
  3. I call GetProcessImageFileName with that handle.

Now both API functions fail as expected (they return zero) and in my VB6 application GetLastError returns 87 ("Invalid parameter") after both API calls.

But in my Java application, only after GetModuleFileNameEx the last error is set to 87. After GetProcessImageFileName it is always zero.

What can I do about this?

EDIT:

Here are the JNA declarations:

public interface PsApi extends StdCallLibrary {
    PsApi INSTANCE = (PsApi) Native.loadLibrary("psapi", PsApi.class, 
            W32APIOptions.UNICODE_OPTIONS);
    int GetModuleFileNameEx(WinNT.HANDLE hProcess, WinDef.HMODULE hModule, 
            char[] lpFilename, int nSize);
    int GetProcessImageFileName(WinNT.HANDLE hProcess, char[] lpImageFileName, 
            int nSize);
}

And the calling code for GetProcessImageFileName

char[] buffer = new char[2048];
int result = PsApi.INSTANCE.GetProcessImageFileName(hProcess, buffer, 
   buffer.length);
if (result == 0) {
    throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
}
return new String(Arrays.copyOf(buffer, result));

And for GetModuleFileNameEx

char[] buffer = new char[2048];
int result = PsApi.INSTANCE.GetModuleFileNameEx(hProcess, hModule, buffer,
    buffer.length);
if (result == 0) {
     throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
}
return new String(Arrays.copyOf(buffer, result));

回答1:

The documentation describes two options:

If a function sets the system error property (errno or GetLastError()), the error code will be thrown as a LastErrorException if you declare the exception in your JNA mapping. Alternatively, you can use Native.getLastError() to retrieve it, providing that Native.setPreserveLastError(boolean) has been called with a true value. Throwing an exception is preferred since it has better performance.

The problem with calling GetLastError is that the JNA framework and indeed the Java runtime may call Windows function which reset the error. So you should not attempt to call GetLastError directly.



回答2:

You could use "throws LastErrorException" and normally that works, but this is not always the best solution. It's safer to test the return value of the API function and call Native.getLastError() when the return value of the API function indicates that an error occurred.

Native.getLastError() returns an internal copy of the system error value that was stored immediately after the last API method call dispatched by JNA returned (this is done in native/dispatch.c after calling ffi_call()). The error value is stored by JNA in thread local storage and cannot be altered by the Java runtime. It's no longer necessary to call Native.setPreserveLastError(true), because the last error value is always preserved.

If "throws LastErrorException" is used, JNA sets the system error value to 0 before dispatching the function call. Normally, the value is zero after a system API function succeeds, but there is no explicit guarantee.

The Linux Programmer's Manual (man 3 errno) states: "Its value is significant only when the return value of the call indicated an error (i.e., -1 from most system calls; -1 or NULL from most library functions); a function that succeeds is allowed to change errno."
Wikipedia: "Any library function can alter the value stored before return, whether or not they detect errors."

Under Windows, the documentation of the GetLastError API function does not guarantee that an API function, that is documented to set the last-error code, sets it to 0 or leaves it 0 on success.



标签: java winapi jna