JNI调用第三方DLL遇EXCEPTION_ACCESS_VIOLATION及加载失败问题求助
问题:JNI调用第三方DLL时加载失败导致内存访问错误
我开发了一个Java应用,通过JNI导入自制C++ DLL,再调用厂商提供的第三方DLL(CardEncoder.dll)函数,但运行时触发内存访问错误。排查后确认是第三方DLL加载失败导致的,且该DLL无法修改,想解决DLL路径设置问题。
相关代码
Java类代码
public class CardEncoder { static { System.loadLibrary("CardEncoderJNIWrapper"); } public static void main(String[] args) { CardEncoder cardEncoder = new CardEncoder(); int status = cardEncoder.CE_ConnectComm("COM10"); System.out.println("Status: " + status); } public native int CE_ConnectComm(String portName); }
C++ JNI包装器代码(调用第三方DLL)
// Save as CardEncoder.cpp #include <jni.h> #include "pch.h" #include "CardEncoderWrapper.h" // Load module x86 or x64 #ifdef _M_IX86 HMODULE CardEncoderDll = LoadLibrary(L"32/CardEncoder.dll"); #else HMODULE CardEncoderDll = LoadLibrary(L"64/CardEncoder.dll"); #endif // _M_IX86 typedef int (*CECONNECTCOMM)(const char* portName); JNIEXPORT jint JNICALL Java_CardEncoder_CE_1ConnectComm (JNIEnv* env, jobject obj, jstring portName) { const char* comm = env->GetStringUTFChars(portName, 0); CECONNECTCOMM CE_ConnectComm = (CECONNECTCOMM)GetProcAddress(CardEncoderDll, "CE_ConnectComm"); return CE_ConnectComm(comm); }
运行时错误日志
# # A fatal error has been detected by the Java Runtime Environment: # # EXCEPTION_ACCESS_VIOLATION (0xc0000005) at pc=0x0000000000000000, pid=26220, tid=1056 # # JRE version: Java(TM) SE Runtime Environment 18.9 (11.0.17+10) (build 11.0.17+10-LTS-269) # Java VM: Java HotSpot(TM) 64-Bit Server VM 18.9 (11.0.17+10-LTS-269, mixed mode, tiered, compressed oops, g1 gc, windows-amd64) # Problematic frame: # C 0x0000000000000000 # # No core dump will be written. Minidumps are not enabled by default on client versions of Windows
排查更新
添加错误检查后返回-101,确认第三方DLL加载失败。尝试路径dll/64/CardEncoder.dll仍无效,项目文件夹结构如下:
_JavaProject |_libs | |_dll\64\CardEncoder.dll (第三方库) | |_CardEncoderJNIWrapper.dll (自制JNI包装DLL) |_src |_CardEncoder.java (加载JNI包装DLL的Java类)
需要解决:如何在C++的LoadLibrary()中正确设置第三方DLL的路径?
解决方案
方法1:从Java层传递绝对路径
由Java获取第三方DLL的绝对路径,传递给JNI层加载,避免路径歧义:
修改Java代码
public class CardEncoder { static { String libDir = System.getProperty("user.dir") + "/libs/"; System.setProperty("java.library.path", libDir); // 刷新库路径缓存,避免旧路径影响 try { Field sysPathsField = ClassLoader.class.getDeclaredField("sys_paths"); sysPathsField.setAccessible(true); sysPathsField.set(null, null); } catch (Exception e) { e.printStackTrace(); } System.loadLibrary("CardEncoderJNIWrapper"); } public static void main(String[] args) { CardEncoder cardEncoder = new CardEncoder(); String thirdPartyDllPath = System.getProperty("user.dir") + "/libs/dll/64/CardEncoder.dll"; int status = cardEncoder.CE_ConnectComm("COM10", thirdPartyDllPath); System.out.println("Status: " + status); } public native int CE_ConnectComm(String portName, String dllPath); }
修改C++代码
#include <jni.h> #include "pch.h" #include "CardEncoderWrapper.h" #include <windows.h> typedef int (*CECONNECTCOMM)(const char* portName); JNIEXPORT jint JNICALL Java_CardEncoder_CE_1ConnectComm (JNIEnv* env, jobject obj, jstring portName, jstring dllPath) { const char* comm = env->GetStringUTFChars(portName, nullptr); const char* path = env->GetStringUTFChars(dllPath, nullptr); // 转换为宽字符适配LoadLibraryW int wideLen = MultiByteToWideChar(CP_UTF8, 0, path, -1, nullptr, 0); wchar_t* widePath = new wchar_t[wideLen]; MultiByteToWideChar(CP_UTF8, 0, path, -1, widePath, wideLen); HMODULE dllHandle = LoadLibraryW(widePath); jint result = -1; if (dllHandle != nullptr) { CECONNECTCOMM connectFunc = (CECONNECTCOMM)GetProcAddress(dllHandle, "CE_ConnectComm"); if (connectFunc != nullptr) { result = connectFunc(comm); } // 不要在此释放DLL,否则后续调用会失效,建议在JNI销毁方法中释放 // FreeLibrary(dllHandle); } // 释放资源 delete[] widePath; env->ReleaseStringUTFChars(portName, comm); env->ReleaseStringUTFChars(dllPath, path); return result; }
方法2:通过JNI包装DLL路径推导
在C++中获取当前JNI包装DLL的所在路径,自动拼接第三方DLL的相对路径:
#include <jni.h> #include "pch.h" #include "CardEncoderWrapper.h" #include <windows.h> typedef int (*CECONNECTCOMM)(const char* portName); JNIEXPORT jint JNICALL Java_CardEncoder_CE_1ConnectComm (JNIEnv* env, jobject obj, jstring portName) { const char* comm = env->GetStringUTFChars(portName, nullptr); // 获取JNI包装DLL的绝对路径 wchar_t wrapperFullPath[MAX_PATH]; GetModuleFileNameW(nullptr, wrapperFullPath, MAX_PATH); // 移除文件名,得到libs目录路径 wchar_t* lastSlash = wcsrchr(wrapperFullPath, L'\\'); if (lastSlash != nullptr) { *lastSlash = L'\0'; } // 拼接第三方DLL路径 wchar_t thirdPartyPath[MAX_PATH]; swprintf_s(thirdPartyPath, L"%s\\dll\\64\\CardEncoder.dll", wrapperFullPath); HMODULE dllHandle = LoadLibraryW(thirdPartyPath); jint result = -1; if (dllHandle != nullptr) { CECONNECTCOMM connectFunc = (CECONNECTCOMM)GetProcAddress(dllHandle, "CE_ConnectComm"); if (connectFunc != nullptr) { result = connectFunc(comm); } } env->ReleaseStringUTFChars(portName, comm); return result; }
方法3:直接调整文件位置
把dll/64/CardEncoder.dll复制到libs/目录下(和CardEncoderJNIWrapper.dll同目录),然后C++代码中直接用:
HMODULE CardEncoderDll = LoadLibrary(L"CardEncoder.dll");
Windows会自动在当前DLL所在目录搜索依赖库,无需额外路径设置。
关键注意事项
- 位数匹配:Java运行时、JNI包装DLL、第三方DLL的位数必须完全一致(全32位或全64位),否则必然加载失败。
- 空指针检查:调用
GetProcAddress前必须确认LoadLibrary返回的句柄不为NULL,这是避免最初内存访问错误的核心。 - 依赖库完整性:如果第三方DLL还有其他依赖库,必须确保这些依赖也能被找到(可放到同目录或添加到系统PATH)。
内容的提问来源于stack exchange,提问作者simpsons3
相关产品推荐
相关产品推荐

