JNA的正确打开方式

JAVA通过JNA调用C/C++生成的DLL库网上介绍的方法已经很多了。
但是轮到自己调用的时候,就各种问题困扰了整整一天。
我把遇到的问题分享一下,万一朋友们遇到和我一样的问题,可以参考解决。

基本使用方式

1. 使用CDecl方式导出的DLL
package com.sun.jna.examples;

import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Platform;

public class HelloWorld {
    public interface CLibrary extends Library {
        CLibrary INSTANCE = (CLibrary)
            Native.load((Platform.isWindows() ? "msvcrt" : "c"), CLibrary.class);
        void printf(String format, Object... args);
    }

    public static void main(String[] args) {
        CLibrary.INSTANCE.printf("Hello, %s", "world");
    }
}
2. 使用StdCall方式导出的DLL

将父类改成StdCallLibrary (此处有坑,稍后解释)

public interface CLibrary extends StdCallLibrary 
3. 单例调用模式
CLibrary SYNC_INSTANCE = (CLibrary) Native.synchronizedLibrary(INSTANCE);

常见错误

1. dll库无法加载

java.lang.UnsatisfiedLinkError: Unable to load library …

解决方法:

  1. 改成绝对路径 ,比如 “D:\\dlls\\xxx.dll”
  2. java启动参数加入-Djna.library.path=“D:\\dlls”
  3. 将dll放入java.exe相同的目录
2. 不是有效的32位程序

java.lang.UnsatisfiedLinkError: %1 不是有效的 Win32 应用程序。

解决方法:

  1. JDK的版本要和调用dll的版本一致
3. 找不到指定的程序

java.lang.UnsatisfiedLinkError: Error looking up function

这个就是上面说到的那个坑,使用StdCall方式导出的DLL,暴露出的方法名是自动生成的(c++不太懂,大概是这么回事),比如定了一个方法名:TestPrint(), 这时候实际生成的方法名就变成了 _TestPrint@12 (下划线+方法名+@+参数栈数),用 depends.exe 这个软件可以也可以看出来。
网上搜了许久也没有一个确切的答案,debug的时候,NativeLibrary.java 586行getFunction方法中,传入的functionName 是TestPrint,我想是不是这时候要是传入_TestPrint@12就可以了。 然后再看继承的StdCallLibrary.java,里面有一个StdCallFunctionMapper类型的常量:FUNCTION_MAPPER, 而这个StdCallFunctionMapper的69行getFunctionName方法中,正是返回了_TestPrint@12这种形式的名称,按说应该要走到这边就没问题了,但是一直debug不进来。翻了翻源代码,终于找到在 Native.load() 方法中,可以传入一个自定义的Map,最后终于找到了这个方法。

解决方法:

  1. Native.load() 方法传入第三个参数:
MyLib INSTANCE = (MyLib) Native.load("D:\\dlls\\MyLib"), MyLib.class, 
   Collections.singletonMap(OPTION_FUNCTION_MAPPER, FUNCTION_MAPPER));

其中:OPTION_FUNCTION_MAPPERcom.sun.jna.Library里的常量,FUNCTION_MAPPERcom.sun.jna.win32.StdCallLibrary 里的常量。

其实这个Map参数在官网的说明中也提到了。但是没有具体例子的话,很难知道在哪里使用,以及如何使用吧。

4. 不支持二维数组

java.lang.IllegalArgumentException: Unsupported array argument type:

由于C那边二维数组的内部存储类型是用一维数组来模拟的,所以Java调用的时候先要把二维数据转换成一维数组才行。具体就是两个for循环遍历了。例子可以参考这个:http://hant.ask.helplib.com/java/post_11075319

5. 结构体等还没用到,待补充

参考

官网:https://github.com/java-native-access/jna

吐槽

调查中,使用depend查看dll依赖,一度以为是缺少API-MS-WIN-*** 文件导致的,一个一个下载实在太费力了,放弃。
后来下载了visual studio 用c#调用这个dll,出乎意料的好用:

   [DllImport("D:\\dlls\\MyLib.dll")]
   public static extern void TestPrint(double[,] input, int rows, int cols, double TM, double[,] output);

说明不是dll缺少的原因,才继续回到Java,看看到底问题出在了哪里。
记录一下, 小司机不才,如果大家有更好的解决方式,请评论。

发布了27 篇原创文章 · 获赞 3 · 访问量 1144

猜你喜欢

转载自blog.csdn.net/qq_39609993/article/details/102466977