JNA 加载动态链接库工具
获取系统默认动态库加载路径:System.out.println(System.getProperty("java.library.path"));
(实际上就是PATH环境变量的目录列表),既只需要将动态库放在列表中的任意一个目录下即可使用System.loadLibrary(libraryName);
加载到jvm中,但一般的做法是放在当前工程的资源路径下当程序执行后将jar包中的动态库文件提取到用户家目录下进行加载
几种加载方式
- 加载默认路径下的动态库:System.loadLibrary()
public class LibraryLoader {
public static void loadLibrary(String libraryName) {
try {
System.loadLibrary(libraryName);
} catch (UnsatisfiedLinkError e) {
System.err.println("Native code library failed to load.\n" + e);
}
}
}
- 加载自定义路径下的动态库:System.load()
public class LibraryLoader {
public static void loadLibrary(String libraryName, String libraryPath) {
try {
System.load(libraryPath + System.mapLibraryName(libraryName));
} catch (UnsatisfiedLinkError e) {
System.err.println("Native code library failed to load.\n" + e);
}
}
}
兼容linux、mac平台的动态链接库加载(主要是添加了对动态链接库的名称进行了处理)
- 加载默认路径下的动态库
public class LibraryLoader {
public static void loadLibrary(String libraryName) {
try {
System.loadLibrary(System.mapLibraryName(libraryName));
} catch (UnsatisfiedLinkError e) {
System.err.println("Native code library failed to load.\n" + e);
}
}
}
- 加载自定义路径下的动态库
public class LibraryLoader {
public static void loadLibrary(String libraryName, String libraryPath) {
try {
System.load(libraryPath + System.mapLibraryName(libraryName));
} catch (UnsatisfiedLinkError e) {
System.err.println("Native code library failed to load.\n" + e);
}
}
}
用户家目录:public static final String USER_HOME = System.getProperty("user.home") + File.separator;
系统临时文件目录:public static final String SYSTEM_TMP_DIR = System.getProperty("java.io.tmpdir");
海康sdk的动态库加载原理:简而言之就是把jar包中的动态库解压写到系统临时文件目录中去,然后在动态库的目录结构的要求进行加载,如下是具体实现
// 如果动态库有目录结构方面的要求,就提前判断目录是否存在
public static void main(String[] args) {
String HCNetSDKCom = System.getProperty("java.io.tmpdir") + "HCNetSDKCom";
System.out.println(HCNetSDKCom);
File file = new File(HCNetSDKCom);
if (!file.exists()) {
file.mkdir();
}
}
【DynamicParseUtil】
package com.jt.hk.utils;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
/**
* 动态解析库解析工具
*/
public class DynamicParseUtil {
private DynamicLibParseHandler handler;
private SAXParserFactory saxParserFactory;
private SAXParser saxParser;
private DynamicParseUtil() throws ParserConfigurationException {
// 获取SAX分析器的工厂实例,专门负责创建SAXParser分析器
saxParserFactory = SAXParserFactory.newInstance();
// 获取SAXParser分析器的实例
try {
saxParser = saxParserFactory.newSAXParser();
handler = new DynamicLibParseHandler();
} catch (ParserConfigurationException | SAXException e) {
throw new ParserConfigurationException();
}
}
public DynamicParseUtil(InputStream inputSteam)
throws ParserConfigurationException, IOException, SAXException {
this();
saxParser.parse(inputSteam, handler);
}
/**
* 适配各系统动态库名称大小写不同,以及lib前缀造成的找不到库的问题
*
* @param currentSystem 当前系统:win64,win32,linux64,linux32,mac64
* @param libName 动态库名称
* @return
*/
public String compareLibName(String currentSystem, String libName) {
List<String> libs = handler.getLibsBySystem(currentSystem);
if (currentSystem.equalsIgnoreCase("win64")) {
return findLibs(libs, libName);
}
String dynamicLibName = libName;
if (libName.startsWith("lib")) {
dynamicLibName = libName.substring(3);
}
return findLibs(libs, dynamicLibName);
}
private String findLibs(List<String> libs, String libName) {
for (String lib : libs) {
if (libName.equalsIgnoreCase(lib)) {
return lib;
}
}
return "";
}
public List<String> getLibsSystem(String system) {
return handler.getLibsBySystem(system);
}
/**
* xml解析handler器
*/
private static class DynamicLibParseHandler extends DefaultHandler {
private final HashMap<String, List<String>> dynamics = new HashMap<>();
private final List<String> systems = Arrays.asList("win64", "win32", "linux64", "linux32", "mac64", "linuxARM");
private String currentDynamicSystem;
private List<String> libs;
public List<String> getLibsBySystem(String system) {
return dynamics.get(system);
}
@Override
public void startDocument() throws SAXException {
super.startDocument();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
super.startElement(uri, localName, qName, attributes);
if (systems.contains(qName)) {
currentDynamicSystem = qName;
if (libs == null) {
libs = new ArrayList<>();
}
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
super.endElement(uri, localName, qName);
if (systems.contains(qName)) {
// 保存到hashmap中
dynamics.put(currentDynamicSystem, libs);
// 清除libs
libs = null;
}
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
super.characters(ch, start, length);
String lib = new String(ch, start, length);
if (!lib.trim().isEmpty()) {
libs.add(lib);
}
}
}
}
【LibraryLoad】
package com.jt.hk.utils;
import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.*;
/**
* 动态库加载工具
*/
public class LibraryLoad {
private static final String ARCH_WINDOWS = "win";
private static final String ARCH_LINUX = "linux";
private static final String ARCH_MAC = "mac";
private static final int PREFIX_64 = 64;
private static final int PREFIX_32 = 32;
private static final String PREFIX_ARM = "ARM";
private static final String EXTERNAL_WIN = ".dll";
private static final String EXTERNAL_LINUX = ".so";
private static final String EXTERNAL_MAC = ".dylib";
private static DynamicParseUtil dynamicParseUtil;
/**
* 类路径下动态库的存放文件夹名称
*/
private static String currentFold;
/**
* 动态库需要写入的临时目录
*/
private static String EXTRACT_PATH = System.getProperty("java.io.tmpdir");
/**
* 控制只加载一次动态库的标志位
*/
private static boolean written = false;
/**
* 设置动态库写入的路径,适用于需要自定义加载路径的用户
*
* @param path 动态库写入的文件夹,从该文件夹下加载sdk的动态库
*/
public static void setExtractPath(String path) {
EXTRACT_PATH = path;
}
/**
* 动态库路径
*/
private static String INNER_PATH;
// private static final String EXTERNAL_MAC = ".so";
private static String extractNetSDKLib(String libName) {
return extractLibrary(libName);
}
public static String getLoadLibrary(String libraryName) {
// 在临时目录中创建 HCNetSDKCom 文件夹
String HCNetSDKCom = EXTRACT_PATH + "HCNetSDKCom";
System.out.println(HCNetSDKCom);
File file = new File(HCNetSDKCom);
if (!file.exists()) {
file.mkdir();
System.out.println("创建目录:" + HCNetSDKCom);
}
// 获取操作系统类型及架构(例如 win64)
currentFold = getLibraryFold();
// 避免重复加载动态库
if (dynamicParseUtil == null) {
try {
// 解析指定的动态库 xml 描述文件 dynamic-lib-load.xml
dynamicParseUtil = new DynamicParseUtil(LibraryLoad.class.getClassLoader().getResourceAsStream("dynamic-lib-load.xml"));
// 标志位,用于控制只从 jar 包中提取一次
if (!written) {
// 获取所有的动态库文件名称
for (String libName : dynamicParseUtil.getLibsSystem(currentFold)) {
// 从当前的 jar 包中提取动态链接库文件
extractLibrary(libName);
}
written = true;
}
} catch (ParserConfigurationException | IOException | SAXException e) {
e.printStackTrace();
}
}
// 动态库名称补全 例如(将 xxx 补全为 xxx.dll)
String fullName = getLibraryName(libraryName);
// 当前操作系统临时文件目录
String path = EXTRACT_PATH;
if (!(EXTRACT_PATH.endsWith("/") || EXTRACT_PATH.endsWith("\\"))) {
path = EXTRACT_PATH + "/";
}
System.out.println("加载函数库 >>>> " + path + fullName);
return path + fullName;
}
/**
* 将jar包里的动态库写入到系统缓存目录,使用绝对路径加载动态库
*/
private static String extractLibrary(String libName) {
return extractLibrary("", libName);
}
/**
* 真正提取jar包内文件到操作系统临时文件目录中的方法
*
* @param relativePath 相对路径
* @param libName 动态库路径
* @return
*/
private static String extractLibrary(String relativePath, String libName) {
if (libName.trim().equals("")) {
return "";
}
// 补全动态库文件后缀
String libFullName = getLibraryName(libName);
String dir = getLibraryFold();
if (!(relativePath.endsWith("/") || relativePath.endsWith("\\"))) {
relativePath = relativePath + "/";
}
// jar包中类路径下的 文件名称,以 "/" 开头
String fileName = relativePath + dir + "/" + libFullName;
InputStream in = LibraryLoad.class.getResourceAsStream(fileName);
BufferedInputStream reader;
FileOutputStream writer;
File extractedLibFile = null;
try {
if (in == null) {
in = new FileInputStream(fileName);
if (in == null) {
return "";
}
}
String nativeTempDir = EXTRACT_PATH;
if (!(nativeTempDir.endsWith("/") || nativeTempDir.endsWith("\\"))) {
nativeTempDir = nativeTempDir + "/";
}
extractedLibFile = new File(nativeTempDir + libFullName);
reader = new BufferedInputStream(in);
writer = new FileOutputStream(extractedLibFile);
byte[] buffer = new byte[1024];
while (true) {
int len = reader.read(buffer);
if (len == 0 || len == -1) {
break;
}
writer.write(buffer, 0, len);
}
reader.close();
writer.close();
in.close();
} catch (Exception e) {
System.err.println("######################### \t[ 动态库加载异常 ]\t #########################");
}
String res = extractedLibFile != null ? extractedLibFile.getAbsolutePath() : "";
System.out.println("加载的目标动态库的绝对路径为: >>>>>>> " + res);
return res;
}
/**
* 处理动态库的文件名称及扩展名
*
* @param libName
* @return
*/
private static String getLibraryName(String libName) {
String dir = currentFold;
String libPrefix = "";
String libExtension = EXTERNAL_WIN;
// 如果 dynamic-lib-load.xml 文件中写了扩展名,那么就不对扩展名做额外处理
if (libName.contains(".")) {
libExtension = "";
}
// 对 linux 与 mac 做兼容处理
if (!dir.contains("win")) {
libPrefix = "lib";
if (dir.contains("linux")) {
libExtension = EXTERNAL_LINUX;
} else {
// libExtension=".dylib";
libExtension = EXTERNAL_MAC;
}
}
libName = dynamicParseUtil.compareLibName(currentFold, libName);
// 动态库以lib开头,则不添加lib前缀
// 以lib开头的库则不添加lib前缀
libName = (libName.startsWith("lib") ? "" : libPrefix) + libName + libExtension;
System.out.println("获取 dynamic-lib-load.xml 中的 动态库名称为: >>>>>>> " + libName);
return libName;
}
/**
* 获取当前操作系统类型及架构
*/
private static String getLibraryFold() {
// 从 jvm虚拟机 的属性获取操作系统类型
String osType;
String osName = System.getProperty("os.name");
if (osName.toLowerCase().startsWith("linux")) {
osType = ARCH_LINUX;
} else if (osName.toLowerCase().startsWith("mac") || osName.toLowerCase().startsWith("darwin")) {
osType = ARCH_MAC;
} else if (osName.toLowerCase().startsWith("windows")) {
osType = ARCH_WINDOWS;
} else {
osType = "";
}
// 从 jvm虚拟机 的属性获取操作系统架构
String arch = System.getProperty("os.arch");
arch = arch.toLowerCase().trim();
if ("i386".equals(arch) || "i686".equals(arch) || "x86".equals(arch)) {
arch = PREFIX_32 + "";
} else if ("x86_64".equals(arch) || "amd64".equals(arch)) {
arch = PREFIX_64 + "";
} else if (arch.startsWith("arm")) {
arch = PREFIX_ARM + "";
}
System.out.println("当前操作系统类型及架构为: >>>>>>> " + osType + arch);
return osType + arch;
}
}
【dynamic-lib-load.xml】
<?xml version="1.0" encoding="UTF-8" ?>
<dynamic-lib>
<win64> <!-- 这个是平台类型与架构,需要与resources目录保持一致才能正常匹配 -->
<lib>AudioRender</lib>
<lib>HCCore</lib>
<lib>HCNetSDK</lib>
<lib>hlog</lib>
<lib>hpr</lib>
<lib>libeay32</lib>
<lib>PlayCtrl</lib>
<lib>ssleay32</lib>
<lib>SuperRender</lib>
<lib>zlib1</lib>
<!-- 带文件夹的写法 -->
<lib>HCNetSDKCom/AnalyzeData.dll</lib>
<lib>HCNetSDKCom/AudioIntercom.dll</lib>
<lib>HCNetSDKCom/AudioRender.dll</lib>
<lib>HCNetSDKCom/HCAlarm.dll</lib>
<lib>HCNetSDKCom/HCAlarm.lib</lib>
<lib>HCNetSDKCom/HCCoreDevCfg.dll</lib>
<lib>HCNetSDKCom/HCDisplay.dll</lib>
<lib>HCNetSDKCom/HCGeneralCfgMgr.dll</lib>
<lib>HCNetSDKCom/HCGeneralCfgMgr.lib</lib>
<lib>HCNetSDKCom/HCIndustry.dll</lib>
<lib>HCNetSDKCom/HCPlayBack.dll</lib>
<lib>HCNetSDKCom/HCPreview.dll</lib>
<lib>HCNetSDKCom/HCPreview.lib</lib>
<lib>HCNetSDKCom/HCVoiceTalk.dll</lib>
<lib>HCNetSDKCom/libiconv2.dll</lib>
<lib>HCNetSDKCom/OpenAL32.dll</lib>
<lib>HCNetSDKCom/StreamTransClient.dll</lib>
<lib>HCNetSDKCom/SystemTransform.dll</lib>
</win64>
<win32>
<lib>avnetsdk</lib>
<lib>dhconfigsdk</lib>
<lib>dhnetsdk</lib>
<lib>dhplay</lib>
<lib>Infra</lib>
<lib>ImageAlg</lib>
<lib>StreamConvertor</lib>
<lib>jninetsdk</lib>
</win32>
<linux64>
<lib>avnetsdk</lib>
<lib>dhnetsdk</lib>
<lib>dhconfigsdk</lib>
<lib>StreamConvertor</lib>
<lib>jninetsdk</lib>
</linux64>
<linux32>
<lib>avnetsdk</lib>
<lib>dhconfigsdk</lib>
<lib>dhnetsdk</lib>
<lib>StreamConvertor</lib>
<lib>jninetsdk</lib>
</linux32>
<mac64>
<lib>avnetsdk</lib>
<lib>dhnetsdk</lib>
<lib>dhconfigsdk</lib>
<lib>StreamConvertor</lib>
</mac64>
</dynamic-lib>
使用
public interface NetSDKLib extends Library {
NetSDKLib NETSDK_INSTANCE = Native.load(LibraryLoad.getLoadLibrary("dhnetsdk"), NetSDKLib.class);
// 动态库的 jna 代码
// ...
// ...
}