JAVA调用DLL动态链接库
java通过JNA调用动态库

java通过JNA调⽤动态库前⾔⽼规矩,先说下为什么会有这篇⽂章。
近期对接了⼀个项⽬,应接⼝提供⽅要求,必须通过动态库调⽤,⼀个是为了安全可控,调⽤⽅不⽤知道内部实现,加密、解密、具体的逻辑不需要考虑,只需要调⽤即可;另⼀个是封装了统⼀的GUI界⾯。
总之就是⾮⽤动态库不可,然后我查了很多资料,请教了⼏个⼤佬,最后在运⽓的加持下,终于调通了,但整个过程特别坎坷,所以我觉有必要记录下。
需要说明的是我们这⾥采⽤的是JNA的⽅式什么是动态库说实话,⼀般我们不会有调⽤动态库的需求,因为这不是web开发的范畴,出发你涉及到嵌⼊式的开发,或者客户端开发。
动态库也叫动态链接库,英⽂简写DLL,简单来讲,就是Windows下开发的程序模块,类似于java下的jar(不知道可不可以这样理解)。
它是实现Windows应⽤程序共享资源、节省内存空间、提⾼使⽤效率的⼀个重要技术⼿段。
windows下它是以dll结尾的⽂件,⽐如:msctf.dll百度百科给的解释是这样的:动态链接库英⽂为DLL*,是Dynamic Link Library*的缩写。
DLL是⼀个包含可由多个程序,同时使⽤的代码和数据的库。
在中,这种⽂件被称为应⽤程序拓展。
例如,在操作系统中,执⾏与对话框有关的常见函数。
因此,每个程序都可以使⽤该 DLL 中包含的功能来实现“打开”对话框。
这有助于避免代码重⽤和促进内存的有效使⽤。
通过使⽤ DLL,程序可以实现模块化,由相对独⽴的组件组成。
例如,⼀个计账程序可以按模块来销售。
可以在运⾏时将各个模块加载到主程序中(如果安装了相应模块)。
因为模块是彼此独⽴的,所以程序的加载速度更快,⽽且模块只在相应的功能被请求时才加载。
咱也不是特别知道,咱也不敢问,你现在只有保证知道动态库这样的东西就⾏了。
开整Talk is cheap. Show me the code。
先上代码,然后再解释public class DllTest {static {String filePath = "D:\\dll\\"; // 这⾥是你的动态库所在⽂件夹的绝对路径// 这⾥引⽤动态库和他的依赖System.load(filePath + "mfc100.dll");System.load(filePath + "mydll.dll");}public static void main(String[] args) {String strUrl = "http://127.0.0.1/test";String InData = "{\"data\":{\"operatorId\":\"test001\",\"operatorName\":\"超级管理员\",\"orgId\":\"123\"},\"orgId\":\"1232\"}";byte[] OutData = new byte[1024];String msg = CLibrary.INSTANCE.test(strUrl.getBytes(), InData.getBytes(), OutData);System.out.println(msg);try {System.out.println(new String(OutData, "GBK"));} catch (UnsupportedEncodingException e) {e.printStackTrace();}}// 这⾥是最关键的地⽅public interface CLibrary extends Library {// FS_CheckCode是动态库名称,前⾯的d://test//是路径CLibrary INSTANCE = (CLibrary) Native.loadLibrary("mydll", CLibrary.class);// 我们要调⽤的动态库⾥⾯的⽅法。
java利用jni调用dll方法

java利⽤jni调⽤dll⽅法准备⼯作:(1) jni4net 是⼀个开源的项⽬(2) jni4net-0.8.8.0.zip 中是⼯具的所有⽂件,包含⽰例下载后解压是这样的打开lib下边有你需要⽤到的jar包和dll这是关键。
创建C#程序:打开vs创建⼀个类库com.demo.lib项⽬,写上测试程序。
HelloWorld.csresult.csUser.cs编译成功之后,会在bin下的Debug下⽣成相关的dll。
使⽤proxygen⼯具来⽣成proxy类.jni4net-0.8.8.0-bin\bin下就有proxygen⼯具,可以直接在该⽬录下打开cmd,执⾏命令:.\proxygen.exe "DLL所在路径" -wd "java项⽬根路径"此处我是直接在bin下建了⼀个⽂件夹,⽤于存放⽣成的⽂件。
⽣成之后,需要将所有⽂件拷到java项⽬根路径下。
执⾏后会出现以下⽂件然后将jni4net的相关⽂件都拷到java项⽬的根路径下,执⾏build.cmd⾥⾯的脚本需要修改成你对应的项⽬路径。
错误提⽰:执⾏过程中如果提⽰你某些程序包不存在,说明你没有放到项⽬路径下,或者说,你没有项⽬中没有相关的jar包。
如果提⽰你csc.exe是不可执⾏的命令,说明你没有配置环境变量,将PATH中加上路径:C:\WINDOWS\\Framework\v4.0.30319(环境变量之间⽤英⽂分号;隔开),其他⽅法也可以实现。
执⾏成功之后会⽣成⼀个dll⼀个jar。
这两个是后边需要⽤到的。
java部分我采⽤的开发⼯具是IDEA,我在项⽬根⽬录下,建了⼀个⽂件夹叫,lib,然后将两个jar包拷到lib下⾯。
记住此处要将jar包添加到项⽬中去。
dll的话,我是采⽤的第三⽅路径,别的⽅法也可以,我简单阐述⼀下我的⽅法。
我将剩余的dll拷到e盘下dll⽂件夹中,然后在配置⽂件中,配置上相关的路径。
java中通过JNA调用dll

java中通过JNA调⽤dll---恢复内容开始---1. JNA简单介绍先说JNI(Java Native Interface)吧,有过不同语⾔间通信经历的⼀般都知道,它允许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 Native Access)是⼀个开源的Java框架,是Sun公司推出的⼀种调⽤本地⽅法的技术,是建⽴在经典的JNI基础之上的⼀个框架。
之所以说它是JNI的替代者,是因为JNA⼤⼤简化了调⽤本地⽅法的过程,使⽤很⽅便,基本上不需要脱离Java环境就可以完成。
如果要和上图做个⽐较,那么JNA调⽤C/C++的过程⼤致如下:可以看到步骤减少了很多,最重要的是我们不需要重写我们的动态链接库⽂件,⽽是有直接调⽤的API,⼤⼤简化了我们的⼯作量。
JNA只需要我们写Java代码⽽不⽤写JNI或本地代码。
功能相对于Windows的Platform/Invoke和Python的ctypes。
2. JNA技术原理JNA使⽤⼀个⼩型的JNI库插桩程序来动态调⽤本地代码。
开发者使⽤Java接⼝描述⽬标本地库的功能和结构,这使得它很容易利⽤本机平台的功能,⽽不会产⽣多平台配置和⽣成JNI代码的⾼开销。
java使用JNA(JavaNativeAccess)调用dll的方法

java使⽤JNA(JavaNativeAccess)调⽤dll的⽅法JNA(Java Native Access):建⽴在JNI之上的Java开源框架,SUN主导开发,⽤来调⽤C、C++代码,尤其是底层库⽂件(windows中叫dll⽂件,linux下是so【shared object】⽂件)。
JNI是Java调⽤原⽣函数的唯⼀机制,JNA就是建⽴在JNI之上,JNA简化了Java调⽤原⽣函数的过程。
JNA提供了⼀个动态的C语⾔编写的转发器(实际上也是⼀个动态链接库,在Linux-i386中⽂件名是:libjnidispatch.so)可以⾃动实现Java与C之间的数据类型映射。
从性能上会⽐JNI技术调⽤动态链接库要低。
复制代码代码如下:main.h⽂件#ifndef __MAIN_H__#define __MAIN_H__#include <windows.h>/* To use this exported function of dll, include this header* in your project.*/#ifdef BUILD_DLL#define DLL_EXPORT __declspec(dllexport) __stdcall#else#define DLL_EXPORT __declspec(dllimport) __stdcall#endif#ifdef __cplusplusextern "C"{#endifint DLL_EXPORT add(int a,int b);#ifdef __cplusplus}#endif#endif // __MAIN_H__main.cpp#include "main.h"// a sample exported functionint DLL_EXPORT add(int a ,int b){return a+b;}extern "C" DLL_EXPORT BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {switch (fdwReason){case DLL_PROCESS_ATTACH:// attach to process// return FALSE to fail DLL loadbreak;case DLL_PROCESS_DETACH:// detach from processbreak;case DLL_THREAD_ATTACH:// attach to threadbreak;case DLL_THREAD_DETACH:// detach from threadbreak;}return TRUE; // succesful}复制代码代码如下://import com.sun.jna.Library; cdecl call调⽤约定import com.sun.jna.Native;import com.sun.jna.Platform;import com.sun.jna.win32.StdCallLibrary;public class main {public interface CLibrary extends StdCallLibrary { //cdecl call调⽤约定时为Library CLibrary INSTANCE = (CLibrary)Native.loadLibrary("forjava",CLibrary.class); public int add(int a,int b);}public static void main(String[] args) {System.out.print(CLibrary.INSTANCE.add(2,3));}}。
java 接收 dll 多个返回值的方法

java 接收 dll 多个返回值的方法在Java中接收DLL返回多个值的方法有很多种,其中比较常用的方法是使用JNA(Java Native Access)技术。
JNA是一个开源的Java库,它允许Java应用程序直接访问本地的动态链接库(DLL)和共享库(SO)。
使用JNA,我们可以定义一个Java接口,然后将其映射到DLL 中的函数上。
DLL函数可以返回一个或多个值,我们可以使用Java 数据类型来接收这些值。
例如,如果DLL函数返回一个整数和一个字符串,我们可以定义一个Java接口如下:```import com.sun.jna.Library;import com.sun.jna.Native;import com.sun.jna.Structure;public interface MyDll extends Library {MyDll INSTANCE = (MyDll) Native.loadLibrary('MyDll', MyDll.class);public static class MyStruct extends Structure {public int intValue;public byte[] stringValue = new byte[256];@Overrideprotected List<String> getFieldOrder() {return Arrays.asList(new String[] { 'intValue','stringValue' });}}public void myFunction(MyStruct struct);}```在这个例子中,我们定义了一个MyDll接口,其中包含一个myFunction方法,该方法将一个MyStruct结构体作为参数传入。
MyStruct结构体包含一个整数和一个字符串,我们使用Structure 类来定义它。
Java动态加载DLL方法

Java动态加载DLL⽅法⼀、JAVA中所需要做的⼯作 在JAVA程序中,⾸先需要在类中声明所调⽤的库名称,如下:static {System.loadLibrary(“goodluck”);} 在这⾥,库的扩展名字可以不⽤写出来,究竟是DLL还是SO,由系统⾃⼰判定。
还需对将要调⽤的⽅法做本地声明,要害字为native。
且只需要声明,⽽不需要具体实现。
如下:public native static void set(int i);public native static int get(); 然后编译该JAVA程序⽂件,⽣成CLASS,再⽤JAVAH命令,JNI就会⽣成C/C 的头⽂件。
例如程序testdll.java,内容为:public class testdll{static{System.loadLibrary("goodluck");}public native static int get();public native static void set(int i);public static void main(String[] args){testdll test = new testdll();test.set(10);System.out.println(test.get());}} ⽤javac testdll.java编译它,会⽣成testdll.class。
再⽤javah testdll,则会在当前⽬录下⽣成testdll.h⽂件,这个⽂件需要被C/C 程序调⽤来⽣成所需的库⽂件。
⼆、C/C 中所需要做的⼯作 对于已⽣成的.h头⽂件,C/C 所需要做的,就是把它的各个⽅法具体的实现。
然后编译连接成库⽂件即可。
再把库⽂件拷贝到JAVA程序的路径下⾯,就可以⽤JAVA调⽤C/C 所实现的功能了。
接上例⼦。
我们先看⼀下testdll.h⽂件的内容:/* DO NOT EDIT THIS FILE - it is machine generated */#include/* Header for class testdll */#ifndef _Included_testdll#define _Included_testdll#ifdef __cplusplusextern "C" {#endif/** Class: testdll* Method: get* Signature: ()I*/JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass);/** Class: testdll* Method: set* Signature: (I)V*/JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint);#ifdef __cplusplus}#endif#endif 在具体实现的时候,我们只关⼼两个函数原型 JNIEXPORT jint JNICALL Java_testdll_get (JNIEnv *, jclass); 和 JNIEXPORT void JNICALL Java_testdll_set (JNIEnv *, jclass, jint); 这⾥JNIEXPORT和JNICALL都是JNI的要害字,表⽰此函数是要被JNI调⽤的。
Java调用dll文件的实现解析

Java调⽤dll⽂件的实现解析⽬录Java调⽤dll⽂件环境接下来进⼊正⽂Java调⽤dll⽂件⼏种常见⽅式Java调⽤动态库需要关注的问题⼀.数据类型对应关系⼆.Jnative调⽤dll三.JNA调⽤dllJava调⽤dll⽂件近期根据C++做了⼀个图⽚质量检测的项⽬,⽬前需要在在java中进⾏调⽤,所以先在C++上⽣成dll⽂件,然后基于java调⽤dll⽂件实现功能。
环境C++:VS2017(之前配置opencv真是要了⽼命)java:idea2020+jdk1.8。
注意:jdk安装的时候⼩⼼点,path路径容易点编辑,千万别新建,会覆盖的。
接下来进⼊正⽂1. 创建Java项⽬,假设定义HelloWorld函数,其中“winproject1”是等会要调⽤的dll⽂件,现在进⼊cmd并cd到当前⽬录下⾯,然后javah -jni HelloWorld.HelloWorld,这样会在⽬录下⾯⽣成⼀个HelloWorld_HelloWorld.h。
等会需要将这个头⽂件移到之前安装jdk⽬录⾥的include下⾯,如:D:%你的路径%\Java\include。
2. 打开vs2017,创建⼀下新的控制台项⽬,然后需要配置项⽬包含⽬录的路径,将下⾯两个路径加进去,保险点还可以在附加⽬录⾥⾯加上这些路径。
vs2017中编写上⾯头⽂件中的代码:JNIEXPORT void JNICALL Java_HelloWorld_HelloWorld_sayHello (JNIEnv *, jobject, jstring, jstring, jstring, jstring) { cout<<"hello world!"<<endl; }**注:**其中#include "single_check.h"就是我定义检测函数的头⽂件,在下⾯的函数中可以调⽤⾃⼰定义的函数,从⽽让java 执⾏⾥⾯的内容,可以调⽤函数。
Java调用C#的DLL实现方案

Java调用C#的DLL的最佳实践2021.4.1需求背景,甲方以前有个项目用C#开发,现在我们用Java开发的项目业务需要调用C#的加密解码工具。
1.开发工具Visual Studio 2019提前安装好开发工具和组件库我在管网下载了社区版,免费。
安装好安装组件库包括.NET桌面开发、使用C++的桌面开发、通用Windows平台开发。
2.以管理员方式启动vs(项目涉及到注册com组件,必须以管理员启动才能完成),新建c#项目这里一定要注意,选择语言C#、项目类型选择库、这时有三个类库,如上的2个,都不能选。
这是坑。
选下面这个,类库(.NET Framework)输入项目名称:Invoke解决方案名称:DLL_of_CSharp_calling_by_Java注意选择框架:必须选择4,这个都是坑,后面进行不下去的。
3.设置c#项目首先,右键刚刚新建的Invoke项目,点击属性修改程序集,是COM可见。
我的如下继续设置项目属性。
点击生成我选择64位,勾选COM互操作注册。
千万记得保存。
4.新建需要被调用的CSharp类代码项目默认帮我们创建了一个Class1.cs文件。
我们给它重命名为MClass.cs。
并给class MClass添加两个方法。
加密、解密。
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Security.Cryptography;namespace Invoke{public class MClass{public string Encrypt(string pToEncrypt, string sKey){using (DESCryptoServiceProvider des = new DESCryptoServiceProvider()){byte[] inputByteArray = Encoding.UTF8.GetBytes(pToEncrypt);des.Key = ASCIIEncoding.ASCII.GetBytes(sKey);des.IV = ASCIIEncoding.ASCII.GetBytes(sKey);System.IO.MemoryStream ms = new System.IO.MemoryStream();using (CryptoStream cs = new CryptoStream(ms, des.CreateEncryptor(), CryptoStreamMode.Write)){cs.Write(inputByteArray, 0, inputByteArray.Length);cs.FlushFinalBlock();cs.Close();}string str = Convert.ToBase64String(ms.ToArray());ms.Close();return str;}}public string Decrypt(string pToDecrypt, string sKey){byte[] inputByteArray = Convert.FromBase64String(pToDecrypt);using (DESCryptoServiceProvider des = new DESCryptoServiceProvider()){des.Key = ASCIIEncoding.ASCII.GetBytes(sKey);des.IV = ASCIIEncoding.ASCII.GetBytes(sKey);System.IO.MemoryStream ms = new System.IO.MemoryStream();using (CryptoStream cs = new CryptoStream(ms, des.CreateDecryptor(), CryptoStreamMode.Write)){cs.Write(inputByteArray, 0, inputByteArray.Length);cs.FlushFinalBlock();cs.Close();}string str = Encoding.UTF8.GetString(ms.ToArray());ms.Close();return str;}}}}然后右键项目,点击生成生成成功,在下面看到关键文件Invoke.dll。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
JAVA调用SDK动态链接库
一、案例简述
JA V A作为跨平台语言,开发中难免遇到跨平台调用接口方法,近期在项目定制开发过程中,现场需要JA V A平台通过DLL动态链接库设置DVR设备功能,如设置获取DVR设备白名单功能、开启关闭白名单功能、获取设置DVR设备码流是否加密、设置获取DVR设备的AES码流加密信息(包括加密密码、加密方式、加密瞬间等信息)等功能。
二、案例分析和解决过程
初始化DLL动态库
需要通过接口实体初始化DLL动态库,详细定义格式如下:
public interface HCNetSDK extends StdCallLibrary {
public static final String path =
"C:\\Tools\\HCNetSDK_v4.1.5.61_20151015_WIN64\\lib\\HCNetSDK";
HCNetSDK INSTANCE = (HCNetSDK)Native.loadLibrary(path, HCNetSDK.class);
}
其中:path为DLL动态库的路径,定义DLL动态库方法中的对象INSTANCE为DLL 动态库初始化的实例
动态库方法的对象定义需要实现Structure,并重写Structure中的getFieldOrder方法,详细声明如下:
public static class LPNET_DVR_DEVICEINFO_V40 extends Structure {
public NET_DVR_DEVICEINFO_V30 struDeviceV30;
public byte bySupportLock;
public byte byRetryLoginTime;
public byte byPasswordLevel;
public byte byRes1;
public int dwSurplusLockTime;
public byte[] byRes2 = new byte[256];
@Override
protected List getFieldOrder() {
return Arrays.asList(new String[] {"struDeviceV30", "bySupportLock",
"byRetryLoginTime", "byPasswordLevel",
"byRes1", "dwSurplusLockTime", "byRes2"});
}
}
其中:变量属性中byte[]对字节长度有严格要求,必须长度一致,否则调用时会出现JDK源代码错误。
声明DLL动态库中方法
DLL动态库方法中有变量和指针的区别,JA V A中没有指针变量,在定义DLL方法的时候指针的定义需要特别注意:
NativeLong NET_DVR_Login_V30(String sDVRIP, short wDVRPort, String sUserName, String sPassword, LPNET_DVR_DEVICEINFO_V30 lpDeviceInfo);
NativeLong NET_DVR_Login_V40(LPNET_DVR_USER_LOGIN_INFO lpLoginInfo, LPNET_DVR_DEVICEINFO_V40 lpDeviceInfo);
boolean NET_DVR_GetDVRConfig(NativeLong lUserID, int dwCommand, NativeLong lChannel, Pointer lpOutBuffer, int dwOutBufferSize, IntByReference lpBytesReturned);
其中:Pointer lpOutBuffer变量代表指针参数的传入,在传指针的时候要区别变量和指针的区别。
执行方法调用
实例化DLL动态库对象后,直接调用DLL初始化接口中的方法即可:
static HCNetSDK hCNetSDK = HCNetSDK.INSTANCE;
NET_DVR_STREAM_ENC_PARA v30 = new NET_DVR_STREAM_ENC_PARA();
boolean getDVRConfigSuc = true;
v30.write();
getDVRConfigSuc = _DVR_GetStreamKey(lUserId, v30);
if (!getDVRConfigSuc) {
int ret = _DVR_GetLastError();
String errorMsg = _DVR_GetErrorMsg(null);
log.error("获取DVR设备AES码流密码调用SDK时出现错误,错误代码:{},描述:{}", ret, errorMsg);
result.setSuccess(false);
result.setErrorMsg("获取DVR设备AES码流密码调用SDK时出现错误,错误代码:"+ret);
}
v30.read();
//解析v30对象返回信息
其中:byte[]数组转换成String的时候需要多注意,如byte[]数组为ip地址信息,转换成string
时候首先要将每个byte转换成16进制数字,然后再转换成String变量,否则会出现乱码;boolean指针在传入对象时需要通过定制对象的方法传入,否则方法调用时会提示参数回调写入失败。
三、经验总结、预防措施及建议
通过本案例我们可以认识到,JA V A中初始化DLL动态库并不复杂,只需要通过Native.loadLibrary(path)方法将path指向DLL动态库路径就可以初始化,DLL动态库中定义声明的方法的调用和初始化也不复杂,只需要通过抽象类将方法初始化即可,但DLL 方法中涉及到的变量的定义要求非常严格,参数信息稍有不同就会导致方法调用失败,由于JA V A和C++开发语言的不同,对同一个变量的属性定义不同,对接开发时一定要注意,变量的属性类型定义和长度限制。
鉴于JA V A调用DLL动态库时对参数严格性的要求,在开发过程中,我们应仔细查看DLL动态库对接开发文档,注意文档中参数与指针的区别(name和*name),同时方法调用完成后,注意对资源的释放,避免造成资源没有释放,后来的线程全部挂起处于等待状态导致程序崩溃。
四、从本文可导出的检查项(checklist)*
JA V A跨平台调用DLL动态库方法调用JNative
五、参考文献、标准、案例
JA V A调用DLL动态库(JNI)
JA V A调用动态链接库DLL之JNative学习。