【问题标题】:Windows JNI Loading and executing c++ code in dll error java.lang.UnsatisfiedLinkError:Windows JNI 在 dll 错误 java.lang.UnsatisfiedLinkError 中加载和执行 c++ 代码:
【发布时间】:2021-05-28 02:04:35
【问题描述】:

答案/解决方案:要明确 Jorn Vernee 的答案是正确的,因为当我创建我的 .h 文件时,我最初在 .java 类中没有包信息,但是当我的代码被执行时,它确实有包信息。 .h 文件中的方法名称最终会在名称中包含包信息。

我有一些用于截屏的 C++ 代码。我一直在尝试使用 JNA 和 JNI 从 Java 调用它,但都没有运气。我的最终目标是让 captureScreen 方法工作。我创建了一个 getNumber 方法,作为一个简单的测试,没有传入任何参数,但即使它也不起作用。

更新 我的应用程序作为 64 位应用程序运行,而我的 dll 是 64 位的。不确定这是否会影响传入或传出的参数。

我目前的实现

Java

public class JNIScreenShotTest { 
   static {
  String oldLibraryPath = System.getProperty("java.library.path");
  try{
  //This doesnt work for me it fails on the loadLibrary call and I get an UnsatisfiedLinkError
  //System.setProperty("java.library.path", "C:\\workspace\\com.tdkc.udop\dlls");
  //System.loadLibrary("JNIScreenShot"); 
                               
  //This Manages to load the library without error                         
  System.load("C:\\workspace\\com.tdkc.udop\\dlls\\JNIScreenShot.dll")
  }finally}
      System.setProperty("java.library.path", oldLibraryPath );
  }
 }

  private native void captureScreen(String filePath, int x, int y, int width, int height);

  private native int getNumber();

}

我使用 javac -h 从上面的类创建我的头文件

.h 文件

    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class JNIScreenShot */

    #include "DXGICapture.h"

    #include <stdio.h>
    #include <tchar.h>
    #include <shlobj.h>
    #include <atlconv.h>
    #include <string>

    using namespace std;

    #ifndef _Included_JNIScreenShot
    #define _Included_JNIScreenShot
    #ifdef __cplusplus
    extern "C" {
    #endif
        /*
         * Class:     JNIScreenShot
         * Method:    captureScreen
         * Signature: (Ljava/lang/String;IIII)V
         */
        JNIEXPORT void JNICALL Java_JNIScreenShot_captureScreen
        (JNIEnv*, jobject, jstring, jint, jint, jint, jint);

        /*
     * Class:     JNIScreenShotTest
     * Method:    getNumber
     * Signature: ()I
     */
        JNIEXPORT jint JNICALL Java_JNIScreenShotTest_getNumber
        (JNIEnv*, jobject);

    #ifdef __cplusplus
    }
    #endif
    #endif

C++ 文件

    // ScreenCapture.cpp : Defines the exported functions for the DLL.
//


#include "JNIScreenShot.h"
#include <iostream>


JNIEXPORT void JNICALL Java_JNIScreenShot_captureScreen
(JNIEnv *env, jobject clz, jstring filePath, jint x, jint y, jint width, jint height) {
    HRESULT hr = S_OK;
    CDXGICapture dxgiCapture;
    jboolean isCopy;
    const char* pszOutputFileName = (env)->GetStringUTFChars(filePath, &isCopy);

    hr = dxgiCapture.Initialize();
    tagScreenCaptureFilterConfig config;

    // set default config
    RtlZeroMemory(&config, sizeof(config));
    config.ShowCursor = 1;
    config.SizeMode = tagFrameSizeMode_AutoSize;

    hr = dxgiCapture.SetConfig(config);
    hr = dxgiCapture.CaptureToFile((LPCTSTR)CA2WEX<>(pszOutputFileName), x, y, width, height);
    if (FAILED(hr))
    {
        printf("Error[0x%08X]: CDXGICapture::CaptureToFile failed.\n", hr);
    }
}

/*
 * Class:     JNIScreenShotTest
 * Method:    getNumber
 * Signature: ()I
 */
JNIEXPORT jint JNICALL Java_JNIScreenShotTest_getNumber
(JNIEnv *env, jobject clz) {
    return 2;
}

我的java调用如下

File f = new File("Some file");
int x = some value;
int y = some value;
int width = some value;
int height = some value;

JNIScreenShot jniScreenShot = new JNIScreenShot();
jniScreenShot.(f.getAbsolutePath(), x,y,width,height)

这会产生错误 java.lang.UnsatisfiedLinkError: com.tdkc.udop.screencapture.jniScreenShot.captureScreen(Ljava/lang/string;IIII)V

起初我认为我的 captureScreen 中的某些东西失败了,或者可能是我传入的数据类型不同。所以我创建了 getNumber 方法作为测试

调用 getNumber() 时出现类似错误 java.lang.UnsatisfiedLinkError: com.tdkc.udop.screencapture.jniScreenShot.getNumber()I

两个错误消息都引用了 .h 文件中的签名,所以我知道它找到了 dll 并尝试调用正确的方法。

花了一些时间查看 UnsatisfiedLinkError,但大多数谷歌搜索结果显示,当库加载时,我已经看到很多包括尝试使用 System.loadLibrary() 而不是 System.load() 时

它似乎可以找到方法,因为错误包含在我的 .h 文件中为每个方法定义的签名。

在这一点上,我已经尝试遵循似乎一百万个关于 JNI 的指南和教程,并且已经 2 天了,我失去了任何帮助,非常感谢。

【问题讨论】:

  • 如果您使用-Xlog:library*=trace 运行程序(需要JDK 15+),它会打印什么?您的库是否已成功加载?本机函数的符号查找是否搜索正确的库?
  • 我不确定我是否理解您的要求。我的应用程序从 Eclipse 运行。该行的快速谷歌没有告诉我它做什么或它保存文件等你能详细说明吗?这是虚拟机参数吗?
  • 是的,这是一个 VM 参数。输出应打印在控制台中。这是一个启用有关本机库加载和符号查找的额外日志记录的选项。您可以在控制台中运行java -Xlog:help(cmd/powershell/sh/bash,具体取决于您使用的平台/shell)或查看在线文档:docs.oracle.com/en/java/javase/15/docs/specs/man/… 以获取更多信息
  • 使用该 VM 参数,我无法启动我的应用程序,我得到一个 Java 虚拟机启动器,然后是一个 Eclipse 错误对话框,提示无法连接到 VM。话虽这么说,它正在加载 dll,因为它尝试调用时的错误消息与我的 .h 文件中方法上的签名匹配
  • 输出应该告诉您是否正在加载正确的库(它会打印路径),以及符号查找是否使用正确的名称在正确的库中查找。 FWIW,对于查找来说唯一重要的是函数的名称(DLL 中没有类型信息,至少通常不是)。您可以使用 dumpbin(Visual Studio 附带)或 Dependencies 之类的东西来验证正在加载的 DLL 是否实际包含该符号。

标签: java c++ java-native-interface


【解决方案1】:

从你的错误信息来看:

java.lang.UnsatisfiedLinkError: com.tdkc.udop.screencapture.jniScreenShot.getNumber()I

您的班级似乎在一个包中。但是,您的本机函数的名称与此不匹配:

Java_JNIScreenShotTest_getNumber

包名也应该用本地函数的名称编码。从该错误消息来看,您的本机函数的名称应该是:

Java_com_tdkc_udop_screencapture_jniScreenShot_getNumber

使用-Xlog:library*=trace (since JDK 15),您可以看到虚拟机用于查找的确切名称。例如:

[0.211s][info][library] Loaded library C:\Program Files\Java\jdk-15\bin\nio.dll, handle 0x00007ffacf0f0000
...
[0.212s][info][library] Found Java_sun_nio_fs_WindowsNativeDispatcher_initIDs in library with handle 0x00007ffacf0f0000

【讨论】:

  • 补充一点:如果您更改 JNI 包/类/方法名称,请始终重新创建您的标头并更改您的实现以匹配
  • 这是问题所在,因为 .h 文件是从没有包的 java 文件构建的。一旦我添加了包信息并让它重新生成 .h 文件,它就会更改我的方法名称。虽然这解决了我的问题,但它给我留下了另一个问题,我假设如果 java 文件被移动过,它将中断并且无法找到正确的方法。有没有办法让 .h 文件方法名称不包含包信息,这样如果 java 文件移动它就不会影响它?
  • @Jeremy 不是,不是。您可能想看看registerNatives API,它允许您手动链接本机函数,但它需要先调用本机代码,并且第一次调用会遇到相同的包命名问题。 IMO 这比它的价值更麻烦。至少在即将推出的 Panama 外部函数接口项目中,可以更轻松地链接任意本地函数,而无需精确的名称映射。
猜你喜欢
  • 1970-01-01
  • 2010-12-06
  • 1970-01-01
  • 2012-12-30
  • 1970-01-01
  • 1970-01-01
  • 2011-02-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多