java通过JNA调用DLL文件(包含回调函数的实现)

java基础 专栏收录该内容
14 篇文章 0 订阅

JNA(Java Native Access)框架是一个开源的Java框架,是SUN公司主导开发的,建立在经典的JNI的基础之上的一个框架。非常强大、易用。其中JNA是对JNI的封装,能让java使用者更好的使用本地的动态库

一、JNA与JNI的比较

JNI:

JNI允许Java代码和其他语言(尤其C/C++)写的代码进行交互,只要遵守调用约定即可。首先看下JNI调用C/C++的过程,注意写程序时自下而上,调用时自上而下。

可见步骤非常的多,很麻烦,使用JNI调用.dll/.so共享库都能体会到这个痛苦的过程。如果已有一个编译好的.dll/.so文件,如果使用JNI技术调用,我们首先需要使用C语言另外写一个.dll/.so共享库,使用SUN规定的数据结构替代C语言的数据结构,调用已有的 dll/so中公布的函 数。然后再在Java中载入这个库dll/so,最后编写Java native函数作为链接库中函数的代理。经过这些繁琐的步骤才能在Java中调用 本地代码。因此,很少有Java程序员愿意编写调用dll/.so库中原生函数的java程序。这也使Java语言在客户端上乏善可陈,可以说JNI是 Java的一大弱点!

JNA:

JNA框架解决了既需要编写java代码,又要编写C语言的代理方法及很多数据类型的转换的问题,它提供一组Java工具类用于在运行期动态访问系统本地共享类库而不需要编写任何Native/JNI代码。开发人员只要在一个java接口中描述目标native library的函数与结构,JNA将自动实现Java接口到native function的映射,大大降低了Java调用本体共享库的开发难度。

那么JNA调用C/C++的过程大致如下:

可以看到 JNA调用时何等的轻松

二、原理

JNA使用一个小型的JNI库插桩程序来动态调用本地代码。开发者使用Java接口描述目标本地库的功能和结构,这使得它很容易利用本机平台的功能,而不会产生多平台配置和生成JNI代码的高开销。这样的性能、准确性和易用性显然受到很大的重视。

此外,JNA包括一个已与许多本地函数映射的平台库,以及一组简化本地访问的公用接口。

注意:

JNA是建立在JNI技术基础之上的一个Java类库,它使您可以方便地使用java直接访问动态链接库中的函数。

原来使用JNI,你必须手工用C写一个动态链接库,在C语言中映射Java的数据类型。

JNA中,它提供了一个动态的C语言编写的转发器,可以自动实现Java和C的数据类型映射,你不再需要编写C动态链接库。

也许这也意味着,使用JNA技术比使用JNI技术调用动态链接库会有些微的性能损失。但总体影响不大,因为JNA也避免了JNI的一些平台配置的开销。

三、回调函数

在WINDOWS中,程序员想让系统DLL调用自己编写的一个方法,于是利用DLL当中回调函数(CALLBACK)的接口来编写程序,使它调用,这个就 称为回调。在调用接口时,需要严格的按照定义的参数和方法调用,并且需要处理函数的异步,否则会导致程序的崩溃。

所谓回调,就是客户程序C调用服务程序S中的某个方法a,然后S又在某个时候反过来调用C中的某个方法b,对于C来说,这个b便叫做回调函数。

一般说来,C不会自己调用b,C提供b的目的就是让S来调用它,而且是C不得不提供。由于S并不知道C提供的b叫甚名谁,所以S会约定b的接口规范(函数原型),然后由C提前通过S的一个函数r告诉S自己将要使用b函数,这个过程称为回调函数的注册,r称为注册函数。

下面举个通俗的例子:

某天,我打电话向你请教问题,当然是个难题,:),你一时想不出解决方法,我又不能拿着电话在那里傻等,于是我们约定:等你想出办法后打手机通知我,这样,我就挂掉电话办其它事情去了。过了XX分钟,我的手机响了,你兴高采烈的说问题已经搞定,应该如此这般处理。故事到此结束。

这个例子说明了“异步+回调”的编程模式。其中,你后来打手机告诉我结果便是一个“回调”过程;我的手机号码必须在以前告诉你,这便是注册回调函数;我的手机号码应该有效并且手机能够接收到你的呼叫,这是回调函数必须符合接口规范

package S;

public class C {
	//回调函数
	void add(int i) {
		System.out.println(i);
	}
	public static void main(String[] args) throws InterruptedException {
		S s = new S();
		s.start();
		Thread.sleep(10);
		//注册回调函数
		s.set(new C());
	}

}
class S extends Thread{

	private C c;
	//注册回调函数
	void set(C c) { 
		this.c =c;
	}

	public void run() {

		for(int i =0;i<5;i++) {

			if(c != null) {
				//回调函数
				c.add(i);
			}
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

四、JNA示例

java通过c的的方法提供一个mp4文件名称,c通过回调函数把音、视频流给我的示例

定义接口加载dll文件及声明链接库中对应的方法

package com.jna.dll;


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

/**
 * @author Administrator
 * 定义加载dll库文件接口,继承自Library 或StdCallLibrary,默认的是继承Library,如果动态链接库里的函数是以stdcall方式输出的,
 * 那么就继承StdCallLibrary,比如众所周知的kernel32库。
 */
public interface Clibrary extends Library{
	/**
	 * 动态链接库编译时选择的平台。如果通过x86平台编译,那么只能使用32位jdk环境加载,如果要使用64位jdk,必须使用x64平台编译。
	 * 
	 * 接口内部需要一个公共静态常量:INSTANCE,通过这个常量,就可以获得这个接口的实例,从而使用接口的方法,也就是调用外部dll/so的函数。
	 *	该常量通过Native.loadLibrary()这个API函数获得,该函数有2个参数:
	 *	
	 *	第一个参数是动态链接库dll/so的名称,但不带.dll或.so这样的后缀,这符合JNI的规范,因为带了后缀名就不可以跨操作系统平台了。
	 *	搜索动态链接库路径的顺序是:先从当前类的当前文件夹找,如果没有找到,再在工程当前文件夹下面找win32/win64文件夹,
	 *	找到后搜索对应的dll文件,如果 找不到再到WINDOWS下面去搜索,再找不到就会抛异常了。
	 *	第二个参数是本接口的Class类型。JNA通过这个Class类型,根据指定的.dll/.so文件,动态创建接口的实例。
	 *	该实例由JNA通过反射自动生成。
	 *	
	 *	接口中只需要定义你要用到的函数或者公共变量,不需要的可以不定义,注意参数和返回值的类型,应该和链接库中的函数类型保持一致。
	 *	定义好接口后,就可以使用接口中的函数即相应dll/so中的函数了
	 */
	//加载链接库
	//如果本地类库不是线程安全的,可用Clibrary INSTANCE2 =  (Clibrary)Native.synchronizedLibrary("VIDEOMP4LIB",Clibrary.class);
	Clibrary INSTANCE = (Clibrary) Native.loadLibrary("VIDEOMP4LIB",Clibrary.class);
	//链接库中对应的方法
	int  JMp4ServerPlay(int UserID, String strFilePath);//开始播发
	
	int  JMp4ServerStop(int UserID);//停止播发
	
	void JMp4Server_RegCallBack(CallBack callBack);//注册回调函数
}

定义回调函数接口

package com.jna.dll;

import com.sun.jna.Pointer;
import com.sun.jna.win32.StdCallLibrary.StdCallCallback;

/**
 * @author Administrator
 *  定义回调函数接口并继承StdCallCallback
 *	必须继承自com.sun.jna.Callback接口 (如果回调函数是以stdcall输出,有时候可能引起jvm崩溃,
 *	可以改成继承StdCallCallback接口试试,)
	子接口必须定义单个公有方法或一个名为callback的公有方法。必须持有到回调对象的一个存活引用。一个回调应该不抛出异常。
 */
public interface CallBack extends StdCallCallback {
	void  JMp4Server_RegCallBack(int UserID, int MP4BackCode,Pointer pData, int dwDataSize);
}

定义回调接口的实现类

package com.jna.dll;

import com.sun.jna.Pointer;

/**
 * @author Administrator
 *	定义回调接口的实现类 
 */
public class CallBackImpl implements CallBack {
	public static final int ER_MP4_NORMAL=0; //MP4正常播放
	public static final int ER_MP4_VEDIO_IN=1; //MP4文件的分包视频流
	public static final int ER_MP4_VEDIO=2; //MP4文件的视频流
	public static final int ER_MP4_AUDIO=3; //MP4文件的音频流
	public static final int ER_MP4_OVER=4; //MP4文件播放结束 
	public static final int ER_MP4_STOP=5; //停止MP4播放(手动)
	public static final int ER_MP4_NEXT=6; //MP4播放下一首(手动)
	public static final int ER_MP4_NO_EXIST=-1; //MP4文件不存在
	public static final int ER_MP4_FORMAT=-2; //MP4文件格式错误
	@Override
	public void JMp4Server_RegCallBack(int UserID, int MP4BackCode, Pointer pData, int dwDataSize) {
		// TODO Auto-generated method stub
		System.out.println("UserID:"+UserID);
		byte[] byteArray = pData.getByteArray(0, dwDataSize);
		System.out.println(byteArray.length+"==="+dwDataSize);
		switch(MP4BackCode) {
			case ER_MP4_NORMAL:System.out.println("MP4正常播放");break;
			case ER_MP4_VEDIO_IN:System.out.println("MP4文件的分包视频流");break;
			case ER_MP4_VEDIO:System.out.println("MP4文件的视频流");break;
			case ER_MP4_AUDIO:System.out.println("MP4文件的音频流");break;
			case ER_MP4_OVER:System.out.println("MP4文件播放结束 ");break;
			case ER_MP4_STOP:System.out.println("停止MP4播放(手动)");break;
			case ER_MP4_NEXT:System.out.println("MP4播放下一首(手动)");break;
			case ER_MP4_NO_EXIST:System.out.println("MP4文件不存在");break;
			case ER_MP4_FORMAT:System.out.println("MP4文件格式错误");break;
			default:System.out.println("未知类型");break;
		}
	}
}

定义main函数程序开始

package com.jna.dll;

public class JnaToDLL {

	public static void main(String[] args) {

		CallBack callBack = new CallBackImpl();//回调函数实例
		Clibrary c = Clibrary.INSTANCE;//Dll实例
		c.JMp4Server_RegCallBack(callBack);//注册回调函数
		c.JMp4ServerPlay(1,"D:\\KuGou\\audio\\76990144\\1.MP4");
		
		try {
			Thread.sleep(100000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}

结果

 

JNI调用dll文件

参考链接:

https://www.jianshu.com/p/164443701574

http://lvqionghua.blog.163.com/blog/static/1852774201141113519185/

https://www.jb51.net/article/126546.htm

 

  • 2
    点赞
  • 2
    评论
  • 21
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 书香水墨 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值