java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > java调用动态库dll

java调用动态库dll详细示例代码

作者:市场部需要一个软件开发岗位

最近应项目的需求,需要通过java调用dll动态库,于是找了一些资料,在此记录一下实现过程,这篇文章主要介绍了java调用动态库dll的相关资料,需要的朋友可以参考下

数据类型对应关系

Java TypeC Type
booleanint
bytechar
charwchar_t
shortshort
doubledouble
floatfloat
Stringchar*

JNI 调用 dll

示例

  1. 包jni中新建java文件,JniDemo.java
package jni;

public class JniDemo {
    public native static void set(int i);
    public native static int get();
}
  1. 使用javah命令生成头文件(jni_JniDemo.h)
javah jni.JniDemo
  1. vs2017创建项目 - Visual C++ - Windows 桌面 - 动态链接库(DLL)
    新建项目JniDemo
    注:选错了工程类型报错(UnsatisfiedLinkError: dll: 此操作仅在应用容器上下文中有效。)

  2. 拷贝jni_JniDemo.h,jni.h,jni_md.h至vs工程cpp文件目录下
    jni.h在D:\Java\jdk1.8.0_111\include\jni.h
    jni_md.h在D:\Java\jdk1.8.0_111\include\win32\jni_md.h

  3. 编辑文件
    修改文件jni_JniDemo.h:

#include <jni.h> 改为 #include "jni.h"

修改文件JniDemo.cpp:

#include "stdafx.h"
#include "jni_JniDemo.h"

int number = 0;

JNIEXPORT void JNICALL Java_jni_JniDemo_set
(JNIEnv *, jclass, jint i)
{
	number = i;
}

JNIEXPORT jint JNICALL Java_jni_JniDemo_get
(JNIEnv *, jclass)
{
	return number;
}
  1. 生成64位dll库
    文件位置:D:\vsrepos\JniDemo\Release\JniDemo.dll

  2. 把64位dll库拷贝至Java JniDemo项目根目录下,修改JniDemo.java

public class JniDemo {
    static{
        System.loadLibrary("JniDemo");
    }
    public native static void set(int i);
    public native static int get();

    public static void main(String[] args) {
//        System.out.println(System.getProperty("java.library.path"));
        set(100);
        System.out.println(get());
    }
}

缺点

如果有一个现有的.dll/.so文件,如果使用JNI技术调用,我们首先需要另外使用C语言写一个.dll/.so共享库,使用SUN规定的数据结构替代C语言的数据结构,调用已有的 dll/so中公布的函数。

然后再在Java中载入这个适配器dll/so,再编写Java native函数作为dll中函数的代理。

Jnative 调用 dll

Jnative是对JNI技术进行了封装,更加方便的让java去调用DLL

JNA调用dll

JNA全称Java Native Access
https://blog.csdn.net/gwd1154978352/article/details/55097376/
JNA中,它提供了一个动态的C语言编写的转发器,可以自动实现Java和C的数据类型映射。不再需要编写C动态链接库。

地址:https://github.com/java-native-access/jna

<!-- https://mvnrepository.com/artifact/net.java.dev.jna/jna -->
<dependency>
    <groupId>net.java.dev.jna</groupId>
    <artifactId>jna</artifactId>
    <version>5.3.1</version>
</dependency

JNA没办法直接调用类方法,需要将类方法“取出来”重新封装一遍。同时为了保持类的特性,每个方法增加一个参数,用于传递类对象的引用。

示例

需要定义一个接口,继承自Library 或StdCallLibrary

public interface CLibrary extends Library {
    CLibrary INSTANCE = (CLibrary)
        Native.loadLibrary((Platform.isWindows() ? "msvcrt" : "c"),
                           CLibrary.class);

    void printf(String format, Object... args);
}

搜索动态链接库路径的顺序是:先从当前类的当前文件夹找,如果没有找到,再在工程当前文件夹下面找win32/win64文件夹,找到后搜索对应的dll文件,如果找不到再到WINDOWS下面去搜索,再找不到就会抛异常了。

JNA模拟结构体

Structure 子类中的公共字段的顺序,必须与C 语言中的结构的顺序一致。否则会报错!

c:

struct UserStruct{
long id;
wchar_t* name;
int age;
};

jna:

public static class UserStruct extends Structure{
    public NativeLong id;
    public WString name;
    public int age;
    public static class ByReference extends UserStruct
implements Structure.ByReference { }
    public static class ByValue extends UserStruct implements
Structure.ByValue
{ }
}
public void sayUser(UserStruct.ByReference struct);

Structure 类有两个内部接口Structure.ByReference 和Structure.ByValue。这两个接口仅仅是标记,如果一个类实现Structure.ByReference 接口,就表示这个类代表结构体指针。

如果一个类实现Structure.ByValue 接口,就表示这个类代表结构体本身。

JNA模拟复杂结构体

c:

struct CompanyStruct{
long id;
wchar_t* name;
UserStruct users[100];
int count;
};

jna:

public static class CompanyStruct extends Structure{
public NativeLong id;
public WString name;
public UserStruct.ByValue[] users=new UserStruct.ByValue[100];
public int count;
}

测试代码:

CompanyStruct2.ByReference companyStruct2=new CompanyStruct2.ByReference();
companyStruct2.id=new NativeLong(2);
companyStruct2.name=new WString("Yahoo");
companyStruct2.count=10;
UserStruct.ByReference pUserStruct=new
UserStruct.ByReference();
pUserStruct.id=new NativeLong(90);
pUserStruct.age=99;
pUserStruct.name=new WString("杨致远");
// pUserStruct.write();
for(int i=0;i<companyStruct2.count;i++){
companyStruct2.users[i]=pUserStruct;
}
 
TestDll1.INSTANCE.sayCompany2(companyStruct2);

考察JNI 技术,我们发现Java 调用原生函数时,会把传递给原生函数的Java 数据固定在内存中,这样原生函数才可以访问这些Java 数据。对于没有固定住的Java 对象,GC 可以删除它,也可以移动它在内存中的位置,以使堆上的内存连续。如果原生函数访问没有被固定住的Java 对象,就会导致调用失败。

固定住哪些java 对象,是JVM 根据原生函数调用自动判断的。而上面的CompanyStruct2结构体中的一个字段是UserStruct 对象指针的数组,因此,JVM 在执行时只是固定住了CompanyStruct2 对象的内存,而没有固定住users 字段引用的UserStruct 数组。因此,造成了错误。

我们需要把users 字段引用的UserStruct 数组的所有成员也全部固定住,禁止GC 移动或者删除。

如果我们执行了pUserStruct.write();这段代码,那么就可以成功执行上述代码。

Structure 类的write()方法会把结构体的所有字段固定住,使原生函数可以访问。

总结

到此这篇关于java调用动态库dll的文章就介绍到这了,更多相关java调用动态库dll内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文