Java调用C++动态库DLL进行无图像无实体处理
作者:颇有几分姿色
前言
这里主要介绍如何通过 JNI(Java Native Interface)在 Java 中调用一个用 C++ 编写的分割算法库。不涉及图像和 java 实体处理。环境如下:
- 系统:Windows 10
- JDK:1.8
- IDE:Visual Studio 2022
- Java 构建工具:Maven
- 目标平台:x64
一、整体目标
Java 端通过 JNI 调用算法提供的动态库,实现图像分割与信息提取的功能。为了方便,我把所有的依赖库都放在了 jdk 的 bin 目录,这样我在调用的时候只需要导入我的自己生成的 jni 库就可以,如果需要频繁切换 jdk,那需要自己指定目录,处理好动态库的依赖关系即可。
二、准备工作
1. C++ 头文件(API 定义)
这是算法的头文件,jni 层需要根据他的方法调用。建议和 java 的方法保持一致,不保持一致也行,自己在 jni 层处理好就可以。
// passportSegC.h DLL_API const char* passport_seg_get_version(); DLL_API int passport_seg_init(const int equip_type, const int passport_type, const char* model_path, const char* config_path); DLL_API const char* passport_seg_run(const char* json_str_c, const char* save_root_c, int* code_passport_offset_det); DLL_API void passport_seg_release_per_call(const char* json_ctr); DLL_API void passport_seg_release();
2. Java 接口类定义
package com.emp.empxmrz.util; /*** * @title * @author * @date 2025/8/7 10:58 **/ public class PassportSeg { static { System.loadLibrary("passportSeg"); } // 获取版本号 public static native String passportSegGetVersion(); // 算法初始化 public static native int passportSegInit(int equipType, int passportType, String modelPath, String configPath); // 调用算法 public static native String passportSegRun(String jsonStr, String saveRoot, int[] codePassportOffsetDet); // 每次调用算法后释放内存 public static native void passportSegReleasePerCall(String jsonCtr); // 程序终止时释放AI模型内存 public static native void passportSegRelease(); }
三、生成 JNI 头文件
javah -classpath target/classes -d src/main/jni com.emp.empxmrz.util.PassportSeg
这会生成 com_emp_empxmrz_util_PassportSeg.h
,它定义了 JNI 接口供 C++ 实现。生成之后不能随便移动类的位置或修改包名、类名等,如果必须调整的话,需要重新生成。
生成的头文件大概如下:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class com_emp_empxmrz_util_PassportSeg */ #ifndef _Included_com_emp_empxmrz_util_PassportSeg #define _Included_com_emp_empxmrz_util_PassportSeg #ifdef __cplusplus extern "C" { #endif /* * Class: com_emp_empxmrz_util_PassportSeg * Method: passportSegGetVersion * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_emp_empxmrz_util_PassportSeg_passportSegGetVersion (JNIEnv *, jclass); /* * Class: com_emp_empxmrz_util_PassportSeg * Method: passportSegInit * Signature: (IILjava/lang/String;Ljava/lang/String;)I */ JNIEXPORT jint JNICALL Java_com_emp_empxmrz_util_PassportSeg_passportSegInit (JNIEnv *, jclass, jint, jint, jstring, jstring); /* * Class: com_emp_empxmrz_util_PassportSeg * Method: passportSegRun * Signature: (Ljava/lang/String;Ljava/lang/String;[I)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_com_emp_empxmrz_util_PassportSeg_passportSegRun (JNIEnv *, jclass, jstring, jstring, jintArray); /* * Class: com_emp_empxmrz_util_PassportSeg * Method: passportSegReleasePerCall * Signature: (Ljava/lang/String;)V */ JNIEXPORT void JNICALL Java_com_emp_empxmrz_util_PassportSeg_passportSegReleasePerCall (JNIEnv *, jclass, jstring); /* * Class: com_emp_empxmrz_util_PassportSeg * Method: passportSegRelease * Signature: ()V */ JNIEXPORT void JNICALL Java_com_emp_empxmrz_util_PassportSeg_passportSegRelease (JNIEnv *, jclass); #ifdef __cplusplus } #endif #endif
四、JNI 实现(C++)
这是 jni 的头文件实现,因为我是要调用多个算法,所以使用 vs 创建了一个解决方案,里面创建了多个项目,每个项目都是一种算法的 jni 层,根据自己的实际情况操作就可以,实现类中需要包含算法头文件和 jni 头文件。项目结果大致如下:
cpp 完整代码如下:
#include <jni.h> #include <iostream> #include <stdexcept> #include "com_emp_empxmrz_util_PassportSeg.h" #include "passportSegC.h" void native_log(const std::string& message) { std::cerr << "[NativeLog] " << message << std::endl; } void throwJavaException(JNIEnv* env, const char* message) { jclass exceptionCls = env->FindClass("java/lang/RuntimeException"); if (exceptionCls != nullptr) { env->ThrowNew(exceptionCls, message); } } extern "C" { JNIEXPORT jstring JNICALL Java_com_emp_empxmrz_util_PassportSeg_passportSegGetVersion (JNIEnv* env, jclass clazz) { try { std::cout << "passport seg get version........" << std::endl; const char* version = passport_seg_get_version(); return env->NewStringUTF(version); } catch (const std::exception& e) { native_log(e.what()); throwJavaException(env, e.what()); return env->NewStringUTF("error"); } catch (...) { native_log("Unknown error in passportSegGetVersion"); throwJavaException(env, "Unknown error in passportSegGetVersion"); return env->NewStringUTF("error"); } } JNIEXPORT jint JNICALL Java_com_emp_empxmrz_util_PassportSeg_passportSegInit (JNIEnv* env, jclass clazz, jint equipType, jint passportType, jstring modelPath, jstring configPath) { try { const char* model_path = env->GetStringUTFChars(modelPath, 0); const char* config_path = env->GetStringUTFChars(configPath, 0); int result = passport_seg_init(equipType, passportType, model_path, config_path); env->ReleaseStringUTFChars(modelPath, model_path); env->ReleaseStringUTFChars(configPath, config_path); return result; } catch (const std::exception& e) { native_log(e.what()); throwJavaException(env, e.what()); return -1; } catch (...) { native_log("Unknown error in passportSegInit"); throwJavaException(env, "Unknown error in passportSegInit"); return -1; } } JNIEXPORT jstring JNICALL Java_com_emp_empxmrz_util_PassportSeg_passportSegRun (JNIEnv* env, jclass clazz, jstring jsonStr, jstring saveRoot, jintArray codeArray) { try { if (!jsonStr || !saveRoot || !codeArray) { native_log("Null input detected."); throwJavaException(env, "Null input parameter."); return nullptr; } const char* json_str = env->GetStringUTFChars(jsonStr, 0); const char* save_root = env->GetStringUTFChars(saveRoot, 0); jint* codes = env->GetIntArrayElements(codeArray, NULL); if (!json_str || !save_root || !codes) { native_log("Failed to convert jstring/jintArray."); throwJavaException(env, "JNI conversion failed."); return nullptr; } native_log("Calling passport_seg_run..."); const char* result = passport_seg_run(json_str, save_root, reinterpret_cast<int*>(codes)); jstring jResult = nullptr; if (result != nullptr) { jResult = env->NewStringUTF(result); // 拷贝内容 passport_seg_release_per_call(result); // 安全释放 } else { jResult = env->NewStringUTF(""); } env->ReleaseStringUTFChars(jsonStr, json_str); env->ReleaseStringUTFChars(saveRoot, save_root); env->ReleaseIntArrayElements(codeArray, codes, 0); return jResult; } catch (const std::exception& e) { native_log(std::string("[C++ Exception] ") + e.what()); throwJavaException(env, e.what()); return nullptr; } catch (...) { native_log("Unknown error in passportSegRun"); throwJavaException(env, "Unknown error in passportSegRun"); return nullptr; } } JNIEXPORT void JNICALL Java_com_emp_empxmrz_util_PassportSeg_passportSegReleasePerCall (JNIEnv* env, jclass clazz, jstring jsonCtr) { try { const char* json_ctr = env->GetStringUTFChars(jsonCtr, 0); passport_seg_release_per_call(json_ctr); env->ReleaseStringUTFChars(jsonCtr, json_ctr); } catch (const std::exception& e) { native_log(e.what()); throwJavaException(env, e.what()); } catch (...) { native_log("Unknown error in passportSegReleasePerCall"); throwJavaException(env, "Unknown error in passportSegReleasePerCall"); } } JNIEXPORT void JNICALL Java_com_emp_empxmrz_util_PassportSeg_passportSegRelease (JNIEnv* env, jclass clazz) { try { passport_seg_release(); } catch (const std::exception& e) { native_log(e.what()); throwJavaException(env, e.what()); } catch (...) { native_log("Unknown error in passportSegRelease"); throwJavaException(env, "Unknown error in passportSegRelease"); } } }
建议添加异常处理,避免JVM 崩溃。
五、Visual Studio 配置
1. 添加包含目录
打开【项目属性】 > C/C++
> 常规
> 附加包含目录
:
防止编译阶段报错。
2. 添加库目录
打开【链接器】 > 常规
> 附加库目录
:
3. 添加依赖库
打开【链接器】 > 输入
> 附加依赖项
:
告诉编译器如何调用 .dll
中的函数;
4.生成 DLL
编译后会生成 passportSeg.dll
和 passportSeg.lib
,将他们复制到 jdk 的 bin 目录或者你自己定义的目录下, java 就可以直接调用 passportSeg
了。
六、Java 调用测试
java 测试结果是否正确,也可以自己封装成接口。
public static void main(String[] args) { System.out.println("版本号>>>>" + PassportSeg.passportSegGetVersion()); int init = PassportSeg.passportSegInit(1, 1, MODEL_PATH, CONFIG_PATH); if (init != 0) { System.out.println("Init failed: " + init); return; } int[] code = new int[1]; String resultJson = PassportSeg.passportSegRun(IMG_PATH, SAVE_PATH, code); System.out.println("Result: " + resultJson); System.out.println("Error Code: " + code[0]); // JNI 层已自动调用,这里不需要重复调用 // PassportSeg.passportSegReleasePerCall(resultJson); PassportSeg.passportSegRelease(); }
调试建议
如果报错找不到 passportSeg.dll
,请将该 DLL 放入:
- 项目运行目录;
- 或者
jdk/bin
目录; - 或者设置
-Djava.library.path
。
如果 JNI 函数名对应不上,请确保:
- 包名、类名、方法名匹配;
- DLL 导出的函数使用
extern "C"
。 - JNI 方法未正确导出,可通过
.def
文件显式指定导出符号。
七、总结
- 编写 Java native 接口;
- 使用
javah
生成 JNI 头文件; - C++ 实现 JNI 方法;
- 配置 Visual Studio 编译动态库;
- Java 调用测试。
如果你不想污染自己的JDK bin,可以将 .dll
和 .lib
放到一个统一的目录下,这样的话切换 jdk 比较方便,因为我只使用 jdk8,所以贪图方便放在了 bin 目录,但这是不规范滴 。
到此这篇关于Java调用C++动态库DLL进行无图像无实体处理的文章就介绍到这了,更多相关Java调用C++动态库DLL内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!