【问题标题】:How to prevent JNI from overwriting GetLastError()如何防止 JNI 覆盖 GetLastError()
【发布时间】:2013-01-18 10:26:41
【问题描述】:

我们正在为xidobi 串行端口项目实现一些winapi 方法的一对一映射。 C 方法到 java 的映射按预期工作,但由于未知原因,GetLastError() 被清除。

这是C代码

// 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();
}

Java 中,我们这样调用映射的原生方法:

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)

我们发现,如果我们在 C 中在 CreateFile(..) 之后立即调用 GetLastError(),则会返回正确的错误代码。由于一对一的映射非常简单,我们假设 JNI 或 VM 自己调用 SetLastError() 并清除我们的最后一个错误。

我们不想放弃一对一的映射设计,那么我们可以做些什么来解决这个难题呢?

这是一个类似的问题,在这种情况下没有帮助:CreateFile() returns INVALID_HANDLE_VALUE but GetLastError() is ERROR_SUCCESS

【问题讨论】:

  • 在 Java_org_xidobi_OS_CreateFile() 中调用 GetLastError() 会有帮助吗? Java_org_xidobi_OS_GetLastError() 将返回保存错误代码的变量。
  • 在调用ReleaseStringUTFChars()之前存储GetLastError()的值,并在调用ReleaseStringUTCChars()之后将其传递给SetLastError()
  • CreateFile之后立即调用GetLastError()会破坏我们的一对一映射原则。但是,存储最后一个错误代码在单线程世界中有效,但在多线程环境中无效。每个线程都有自己的错误代码,GetLastError() 保证这一点,如果我们将值存储在 C 中,我们必须实现这种行为。
  • @hmjd 我们试了你的方法,ReleaseStringUTCChars()没有清除错误,清除发生在Java_org_xidobi_OS_CreateFile返回之后,Java_org_xidobi_OS_GetLastError输入之前。

标签: java c winapi java-native-interface


【解决方案1】:

无法保证GetLastError() 在您对CreateFile 的本机调用和您对GetLastError 的本机调用之间的任何JVM 操作都将继续存在。因此,您应该在CreateFile 之后立即调用GetLastError 并将值保存在您自己的线程本地槽中。

然后您的GetLastError 实现将从您存储它的任何位置检索它。

您可能希望将其重命名为 LastXidobiError 或其他名称,这样它只会检索由您的库中的调用设置的错误。

// 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;
}

【讨论】:

  • 您的方法会为每个新线程创建一个新的 TLS 索引。当线程消失并收集垃圾时,如何释放未使用的 TLS 索引?换句话说:如何确定何时必须调用TlsFree
  • @chriss,该方法在加载 DLL 时创建一个 TLS 索引,而不是针对每个新线程。如果您想释放 TLS(好主意!!!),理想情况下,您应该在 DllMain 中为 DLL_PROCESS_ATTACH 分配 TLS,并在 DLL_PROCESS_DETACH 时释放它。否则,如果 DLL 被卸载并重新加载,将分配一个新的 TLS。
  • @chriss,根据您的评论编辑了答案以合并释放 TLS。
猜你喜欢
  • 1970-01-01
  • 2017-12-19
  • 2021-02-08
  • 2014-05-19
  • 2020-07-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-31
相关资源
最近更新 更多