You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 03:42:05