2

We are implementing a one-to-one mapping of some winapi methods, for the xidobi serial port project. The mapping of the C methods to java works as expected, but for an unknown reason GetLastError() get cleared.

Here is the C-Code:

// CreateFile ////////////////////////////////////////////////////////////
JNIEXPORT jint JNICALL
Java_org_xidobi_OS_CreateFile(JNIEnv *env, jobject this,
    jstring lpFileName,
    jint dwDesiredAccess,
    jint dwShareMode,
    jint lpSecurityAttributes,
    jint dwCreationDisposition,
    jint dwFlagsAndAttributes,
    jint hTemplateFile) {

const char* fileName = (*env)->GetStringUTFChars(env, lpFileName, NULL);

HANDLE handle = CreateFile(fileName,
                            dwDesiredAccess,
                            dwShareMode,
                            (LPSECURITY_ATTRIBUTES) lpSecurityAttributes,
                            dwCreationDisposition,
                            dwFlagsAndAttributes,
                            (HANDLE) hTemplateFile);

(*env)->ReleaseStringUTFChars(env, lpFileName, fileName);

return (jint) handle;
}

// GetLastError ////////////////////////////////////////////////////////////
JNIEXPORT jint JNICALL
Java_org_xidobi_OS_GetLastError(JNIEnv *env, jobject this) {
    return (jint)  GetLastError();
}

In Java we call the mapped native methods in like this:

int handle = os.CreateFile("\\\\.\\" + portName, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);

if (handle != INVALID_HANDLE_VALUE)
    return handle;
int lastError= os.GetLastError(); //-> sometimes 0 (ERROR_SUCCESS)

We figured out that if we call GetLastError() in C right after CreateFile(..) the correct error code is returned. Since the one-to-one mapping is dead simple we assume that JNI or the VM calls SetLastError() itself and clears our last error.

We don't want to give up the one-to-one mapping design, so what can we do to solve this puzzle?

Here is a similar question that doesn't help in this case: CreateFile() returns INVALID_HANDLE_VALUE but GetLastError() is ERROR_SUCCESS

Community
  • 1
  • 1
Chriss
  • 5,157
  • 7
  • 41
  • 75
  • Would calling GetLastError() in Java_org_xidobi_OS_CreateFile() be of any help? Java_org_xidobi_OS_GetLastError() would return the variable where the error code was saved. – ignis Jan 18 '13 at 10:33
  • Store the value of `GetLastError()` before the call to `ReleaseStringUTFChars()` and pass it to `SetLastError()` after the caller to `ReleaseStringUTCChars()` ? – hmjd Jan 18 '13 at 10:34
  • 1
    Calling `GetLastError()` right after `CreateFile`would break our one-to-one mapping principle. However storing the last error code works in a single thread world, but not in a multi threading environment. Every thread have its own error code, `GetLastError()` guarantees this, if we store the value we have to implement this behaviour our self in C. – Chriss Jan 18 '13 at 11:06
  • @hmjd We tried your approach, `ReleaseStringUTCChars()` doesn't clear the error, the clearing happens after `Java_org_xidobi_OS_CreateFile` returns and before `Java_org_xidobi_OS_GetLastError` is entered. – Chriss Jan 18 '13 at 11:26

1 Answers1

4

There is no guarantee that GetLastError() will survive whatever the JVM is doing between your native call to CreateFile and your native call to GetLastError. Therefore you should immediately call GetLastError after CreateFile and save the value in a thread local slot of your own.

Then your implementation of GetLastError will retrieve it from wherever you stored it.

You may wish to rename it LastXidobiError or some such as it will only retrieve an error which was set by a call in your library.

// Space to store last error ////////////////////////////////////////////
static DWORD dwTlsIndexLastError = 0;

BOOL WINAPI DllMain(
  _In_  HINSTANCE hinstDLL,
  _In_  DWORD fdwReason,
  _In_  LPVOID lpvReserved
){


    switch(fdwReason){
    case DLL_PROCESS_ATTACH:
        dwTlsIndexLastError = TlsAlloc();
        break;
    case DLL_PROCESS_DETACH:
        TlsFree(dwTlsIndexLastError);
        dwTlsIndexLastError = 0;
        break;
     }
     return TRUE;
}

///// Save the last error. 
///// Call this function after the "real" function whose error you want to report.
void SaveLastError()
{
    TlsSetValue(dsTlsIndexLastError, (LPVOID)(DWORD_PTR)GetLastError());
} 
// GetLastError ////////////////////////////////////////////////////////////
JNIEXPORT jint JNICALL
Java_org_xidobi_OS_GetLastError(JNIEnv *env, jobject this) {
    return (jint)  TlsGetValue(dsTlsIndexLastError);
}

// CreateFile ////////////////////////////////////////////////////////////
JNIEXPORT jint JNICALL
Java_org_xidobi_OS_CreateFile(JNIEnv *env, jobject this,
    jstring lpFileName,
    jint dwDesiredAccess,
    jint dwShareMode,
    jint lpSecurityAttributes,
    jint dwCreationDisposition,
    jint dwFlagsAndAttributes,
    jint hTemplateFile) {

const char* fileName = (*env)->GetStringUTFChars(env, lpFileName, NULL);

HANDLE handle = CreateFile(fileName,
                            dwDesiredAccess,
                            dwShareMode,
                            (LPSECURITY_ATTRIBUTES) lpSecurityAttributes,
                            dwCreationDisposition,
                            dwFlagsAndAttributes,
                            (HANDLE) hTemplateFile);

// Save the value of GetLastError for the relevant function
SaveLastError();

(*env)->ReleaseStringUTFChars(env, lpFileName, fileName);

return (jint) handle;
}
Ben
  • 34,935
  • 6
  • 74
  • 113
  • 1
    Your approch creates a new TLS-index, for every new Thread. How to free a unused TLS-index when a Thread is gone and get garbage collected? In other words: How to determine when `TlsFree` have to be called? – Chriss Jan 18 '13 at 14:00
  • 3
    @chriss, The approach creates a TLS index when the DLL is loaded, not for every new thread. If you want to free the TLS (good idea!!!) you should ideally allocate the TLS in the `DllMain` for `DLL_PROCESS_ATTACH` and deallocate it when `DLL_PROCESS_DETACH`. Otherwise a new TLS will be allocated if the DLL is unloaded and reloaded. – Ben Jan 18 '13 at 14:30
  • @chriss, edited the answer to incorporate freeing the TLS as per your comment. – Ben Jan 18 '13 at 20:17