我的 DLL 代码可以从 exe 文件正常运行,但是无法从 Java loadLibrary 加载。

16

我创建了一个C++模块,将其构建为共享库文件,然后使用JNI从Java中调用它。

我有两个环境,Windows和Unix,我有一个C++可执行程序和一个Java程序,我只需针对每个环境重新编译即可。

  • 当我在Unix中编译我的tester.exe程序并使用来自我的库(.so)的方法运行它时,它可以正常工作。
  • 当我在Unix中编译我的Java程序并使用Java的loadLibrary加载我的库(.so)时,它可以正常工作。
  • 当我在Windows中编译我的tester.exe程序并使用来自我的库(.dll)的方法运行它时,它可以正常工作。就像unix版本一样。

  • 但是,当我在Windows中编译我的Java程序并使用Java的loadLibrary加载我的库(.dll)时,它会失败。它显示尝试访问无效地址。

我无法弄清楚为什么它无法在Windows中使用Java loadLibrary工作,但在其他地方使用相同代码时它可以正常工作。如果我延迟加载我的库使用的依赖DLL,那么我的库会在Java中加载但无法正常工作。我知道有特定的代码导致Java无法加载我的库,但我无法弄清楚为什么我的C++可执行程序在使用相同的方法和库时没有问题。


我的dll有1个公开的方法,它调用4个现有库中的方法。如果我将这4个方法注释掉,则我的dll可以在Java中正常加载。我知道这与我的dll链接的库中的这些方法有关。 Java是否以不同的方式查看依赖库?我尝试首先加载依赖库,但我加载的其中一个dll文件会导致递归错误并导致堆栈溢出。

有人知道解决由递归错误导致堆栈溢出的DLL的方法吗?我需要其中的方法,但无法使用java loadLibrary加载它。


以下是涉及的文件和实际错误消息的更多详细信息。

我给我的初始 dll 文件添加了一个 DllMain 函数,只是为了看看都加载了什么。如果我将同样的程序(my_plain_dll_to_call_JNI_DLL)编译为 exe 文件,一切正常。但是,当我从我的 Java 程序中加载它时发生了以下情况:

  • myJavaProgram 简单地调用 System.loadLibrary() 来加载一个基本的 .dll 文件,该文件调用了我的另一个包含 JNI 代码的 dll 中的一个方法。
  • my_plain_dll_to_call_JNI_DLL 是我创建的一个 dll 文件,通过将其链接到我的 dll 库文件来测试依赖性。它只是调用另一个 dll 中的方法,该 dll 调用了我需要的本地代码。
  • my_JNI_DLL.ll 是一个与现有 C++ 编程库链接的 dll 文件,我需要从 JNI 中访问它。它包含直接调用现有源代码库中方法的内容。

我在每行文本左侧写上了显示文件名,以显示执行所处的层次结构。


c:\java myJavaProgram
myJavaProgram: Java静态方法进入。
myJavaProgram: Java调用System.loadLibrary(my_plain_dll_to_call_JNI_DLL)
my_JNI_DLL.dll: 进入DllMain
my_JNI_DLL.dll: DLL_PROCESS_ATTACH
my_plain_dll_to_call_JNI_DLL: DLL_PROCESS_ATTACH my_plain_dll_to_call_JNI_DLL: DLL_THREAD_ATTACH my_plain_dll_to_call_JNI_DLL: DLL_THREAD_DETACH my_plain_dll_to_call_JNI_DLL: DLL_PROCESS_DETACH
myJavaProgram: my_plain_dll_to_call_JNI_DLL加载成功!
myJavaProgram: Java静态方法退出。
myJavaProgram: 进入Main()。
my_plain_dll_to_call_JNI_DLL: 在call_my_JNI_DLL_method中
my_JNI_DLL.dll: 在my_JNI_DLL_method中
my_JNI_DLL.dll: 进入my_JNI_DLL_CheckEnvironmentVariables()
my_JNI_DLL.dll: 退出my_JNI_DLL_CheckEnvironmentVariables
my_JNI_DLL.dll: 调用StartExistingNativeCode。
# # Java运行时环境检测到致命错误: # # 内部错误(0xc0fb007e),pid=7500,tid=7552 # # JRE版本:6.0_21-b06 # Java VM:Java HotSpot(TM) Client VM (17.0-b16 mixed mode, sharing windows-x86) # 问题所在帧: # C [KERNELBASE.dll+0x9673] # # 保存有关更多信息的错误报告文件如下: # C:\hs_err_pid7500.log # # 如果您想提交错误报告,请访问: # http://java.sun.com/webapps/bugreport/crash.jsp # 此崩溃发生在Java虚拟机之外的本地代码中。 # 请参见有问题的帧以报告错误。 #
my_plain_dll_to_call_JNI_DLL: DLL_PROCESS_DETACH
my_JNI_DLL.dll: 进入DllMain
my_JNI_DLL.dll DLL_PROCESS_DETACH

更新 我已经将问题缩小到一个内存管理库,它是从另一个dll链接进来的,而我的程序使用了这个dll。它使用的dll是sh33w32.dll,它被称为SmartHeap,由一家名为Microquil的公司开发。我有3.3版本,当Java LoadLibrary试图加载该dll时,它失败了。我不确定我能做什么来让java处理加载该库。它肯定与Java可以访问的内存区域有关,与Windows允许exe访问的不同。exe没有SmartHeap库的问题,但是Java不允许我使用它。有什么想法或经验来处理这个问题吗?我尝试通过重新编译其他库来删除链接库,但是代码中的常规调用失败了。


额外找到的信息 在Java中无法加载的dll中的函数名为MemRegisterTask。它是一个名为SmartHeap的产品的一部分,由Microquill开发。以下是我发现的关于此函数的文档。我认为这种内存分配导致了Java无法加载它。

MemRegisterTask初始化SmartHeap Library。在大多数平台上,您不需要调用MemRegisterTask,因为SmartHeap会在您进行第一次调用时自行初始化。

SmartHeap为每个任务或进程维护注册引用计数。每次调用MemRegisterTask时,此引用计数都会递增。如果您最后一次调用SmartHeap发生在应用程序准备终止之前,您可以调用MemUnregisterTask来终止SmartHeap。MemUnregisterTask将引用计数减少1个——当计数为零时,SmartHeap将释放与当前任务或进程相关联的任何SmartHeap分配的内存和调试状态。


我不确定能否发布代码,因为工作规定的原因。我已经解决了遇到的所有其他JNI错误,所以我正在尝试理解这些概念,看看是否可以找出失败的原因。我正确地公开了我的Java程序使用的1个方法。问题是dll甚至无法在Windows Java中加载,而Unix Java则正常工作。 - Logan
你是否将Windows二进制文件编译为32位并尝试在64位虚拟机上使用?或者反之亦然?此外,请确保您正在使用正确的内存管理二进制文件。 - Andrew T Finnell
2
看起来我的JVM是32位的。我用于C代码的Borland 5.5编译器也是32位的。Borland编译器正在给我发出警告,它也是32位的。我们有一个管理内存的库,它被链接到我的本地代码中,这是失败的原因。我可以加载除1个之外的所有依赖dll,它会给我一个Java递归错误和一些关于堆栈溢出的内容。不过我不知道该如何解决这个问题,因为只要我链接到我们现有的库文件,它就会被链接进去。 - Logan
关于更新,您是说您正在尝试使用loadLibrary()直接加载sh33w32.dll,还是正在加载其他需要sh33w32.dll的DLL? - Dmitry Leskov
我尝试直接加载sh33w32.dll,以为它可能是需要先加载的依赖项,但这也不起作用。我的dll引用了另一个dll中的函数,而那个dll是链接到sh33w32.dll文件的。看起来当为C代码分配内存时它会失败。现在我已经将我的dll更改为动态加载方法,所以我的dll可以加载,但对C函数的调用失败了。 - Logan
显示剩余4条评论
2个回答

1

在 hs_err ... 日志文件中是否有任何有用的信息?通常会有堆栈回溯等指向某些问题的信息。

您也尝试在调试器中运行带参数的 java.exe(运行加载内容的测试)了吗?

从上面的跟踪中可以看出,加载似乎正常工作(跟踪输出表明您已经增加了 dllentrypoint/dllmain 的跟踪输出)。

加载的顺序如下:

  1. 加载依赖的 DLL
  2. 加载 DLL 本身
  3. 使用进程附加调用 dllentrypoint/dllmain

所以这已经超出了加载 DLL 的范畴。

您是否检查过您是否正在使用 Windows 的调试/发布运行时?调试版本可能与发布版本冲突 - Java 是发布版本,您的示例 exe 可能与您的 DLL 构建相同。


我几乎拥有可能拥有的每种发布和调试组合。要制作一个发布版本的DLL,我只需要关闭调试吗?我已经阅读了相关资料,但不确定如何操作。我现在已经能够通过动态链接到我需要的代码来加载我的dll,但是我需要执行的代码仍然失败。因此,动态链接与静态链接没有任何区别。我还阅读了关于使用字节数组进行内存管理的内容,但不确定如何在刚开始加载dll时实现。 - Logan
我已经查看了hs_err日志文件,阅读了在线文章以了解如何阅读这些文件,但从未显示出问题。有趣的是,我认为kernelbase.dll失败了,但昨天我运行了我的代码并从C代码中抛出了空指针异常。它创建了与运行我的代码时获取的相同的hs_err日志。当我打字时,让我想知道是否真的存在空值。如果有空值,为什么在Windows上作为exe文件可以工作呢?此外,为什么会在loadlibrary上失败... - Logan

0

看起来像是调用约定或类型大小不匹配。每个Windows C编译器都有自己的特点,而Windows JNI头文件假定(最近版本的)Microsoft Visual C++。仔细查看警告-精度损失是一个坏迹象。

例如,__int64 是MSVC特有的。您需要找出Borland C中64位整数类型的名称,并将其映射到__int64,然后再包括jni.h


Borland中的__int64类型是LongLong,看起来它已经被定义了。你认为这会阻止我的DLL加载吗?我现在将我的dll更改为动态链接到其他dll,但现在代码在Java中失败,尽管dll在Java中可以加载。当从c++ exe文件引用时,相同的dll代码仍然正常工作。这一定与Java允许访问内存的方式有关。我只是找不到任何好的信息。 - Logan
我仍然建议追踪警告到根本原因。另外,你是否尝试在除HotSpot以外的JVM上使用-verbose:jni运行应用程序? - Dmitry Leskov
我已经尝试使用-verbose:jni运行,它只是到达我的代码,然后无法执行我需要使用的dll中的命令。我相信这现在是一个内存问题。听起来像是链接的这个DLL正在尝试分配内存。当Java尝试加载它时,会出现递归错误和堆栈溢出。这个dll中只有一个方法被实际使用,但加载它会导致堆栈溢出。 - Logan
堆栈溢出通常意味着调用约定不匹配。也就是说,当函数返回时,它从堆栈中获取一个值,该值不是返回地址,而是某个其他函数的地址,然后事情变得非常混乱。您能在调试器下跟踪所谓的有问题的函数吗? - Dmitry Leskov
我已经试图调试dll,但我无法让Eclipse中的调试器工作,而我的Borland TurboDebugger出于某种原因无法附加到进程。失败的方法是在C中分配内存,所以我听说Java在这方面表现不佳。除非有静态方法运行或类似情况,否则我不确定为什么它在加载dll时尝试分配内存。 - Logan
你需要一个低级别的调试器。Windows SDK中有WinDbg,但是你也可以单独下载Windows调试工具。我们遗留产品(Native XDS-x86)附带的调试器(免费软件)也可能适用,因为你的应用程序是32位的。 - Dmitry Leskov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接