Java調(diào)用C++動(dòng)態(tài)庫(kù)(DLL)的完整實(shí)踐指南
Java 通過(guò) JNI 調(diào)用 C++ 動(dòng)態(tài)庫(kù)的完整流程
這里主要介紹如何通過(guò) JNI(Java Native Interface)在 Java 中調(diào)用一個(gè)用 C++ 編寫(xiě)的分割算法庫(kù)。不涉及圖像和 java 實(shí)體處理。環(huán)境如下:
- 系統(tǒng):Windows 10
- JDK:1.8
- IDE:Visual Studio 2022
- Java 構(gòu)建工具:Maven
- 目標(biāo)平臺(tái):x64
一、整體目標(biāo)
Java 端通過(guò) JNI 調(diào)用算法提供的動(dòng)態(tài)庫(kù),實(shí)現(xiàn)圖像分割與信息提取的功能。為了方便,我把所有的依賴(lài)庫(kù)都放在了 jdk 的 bin 目錄,這樣我在調(diào)用的時(shí)候只需要導(dǎo)入我的自己生成的 jni 庫(kù)就可以,如果需要頻繁切換 jdk,那需要自己指定目錄,處理好動(dòng)態(tài)庫(kù)的依賴(lài)關(guān)系即可。
二、準(zhǔn)備工作
1. C++ 頭文件(API 定義)
這是算法的頭文件,jni 層需要根據(jù)他的方法調(diào)用。建議和 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 接口類(lèi)定義
package com.emp.empxmrz.util;
/***
* @title
* @author
* @date 2025/8/7 10:58
**/
public class PassportSeg {
static {
System.loadLibrary("passportSeg");
}
// 獲取版本號(hào)
public static native String passportSegGetVersion();
// 算法初始化
public static native int passportSegInit(int equipType, int passportType, String modelPath, String configPath);
// 調(diào)用算法
public static native String passportSegRun(String jsonStr, String saveRoot, int[] codePassportOffsetDet);
// 每次調(diào)用算法后釋放內(nèi)存
public static native void passportSegReleasePerCall(String jsonCtr);
// 程序終止時(shí)釋放AI模型內(nèi)存
public static native void passportSegRelease();
}
三、生成 JNI 頭文件
javah -classpath target/classes -d src/main/jni com.emp.empxmrz.util.PassportSeg
這會(huì)生成 com_emp_empxmrz_util_PassportSeg.h,它定義了 JNI 接口供 C++ 實(shí)現(xiàn)。生成之后不能隨便移動(dòng)類(lèi)的位置或修改包名、類(lèi)名等,如果必須調(diào)整的話,需要重新生成。
生成的頭文件大概如下:
/* 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 實(shí)現(xiàn)(C++)
這是 jni 的頭文件實(shí)現(xiàn),因?yàn)槲沂且{(diào)用多個(gè)算法,所以使用 vs 創(chuàng)建了一個(gè)解決方案,里面創(chuàng)建了多個(gè)項(xiàng)目,每個(gè)項(xiàng)目都是一種算法的 jni 層,根據(jù)自己的實(shí)際情況操作就可以,實(shí)現(xiàn)類(lèi)中需要包含算法頭文件和 jni 頭文件。項(xiàng)目結(jié)果大致如下:

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); // 拷貝內(nèi)容
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. 添加包含目錄
打開(kāi)【項(xiàng)目屬性】 > C/C++ > 常規(guī) > 附加包含目錄:

防止編譯階段報(bào)錯(cuò)。
2. 添加庫(kù)目錄
打開(kāi)【鏈接器】 > 常規(guī) > 附加庫(kù)目錄:

3. 添加依賴(lài)庫(kù)
打開(kāi)【鏈接器】 > 輸入 > 附加依賴(lài)項(xiàng):

告訴編譯器如何調(diào)用 .dll 中的函數(shù);
4.生成 DLL
編譯后會(huì)生成 passportSeg.dll 和 passportSeg.lib,將他們復(fù)制到 jdk 的 bin 目錄或者你自己定義的目錄下, java 就可以直接調(diào)用 passportSeg了。
六、Java 調(diào)用測(cè)試
java 測(cè)試結(jié)果是否正確,也可以自己封裝成接口。
public static void main(String[] args) {
System.out.println("版本號(hào)>>>>" + 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 層已自動(dòng)調(diào)用,這里不需要重復(fù)調(diào)用
// PassportSeg.passportSegReleasePerCall(resultJson);
PassportSeg.passportSegRelease();
}
調(diào)試建議
如果報(bào)錯(cuò)找不到 passportSeg.dll,請(qǐng)將該 DLL 放入:
- 項(xiàng)目運(yùn)行目錄;
- 或者
jdk/bin目錄; - 或者設(shè)置
-Djava.library.path。
如果 JNI 函數(shù)名對(duì)應(yīng)不上,請(qǐng)確保:
- 包名、類(lèi)名、方法名匹配;
- DLL 導(dǎo)出的函數(shù)使用
extern "C"。 - JNI 方法未正確導(dǎo)出,可通過(guò)
.def文件顯式指定導(dǎo)出符號(hào)。
七、總結(jié)
- 編寫(xiě) Java native 接口;
- 使用
javah生成 JNI 頭文件; - C++ 實(shí)現(xiàn) JNI 方法;
- 配置 Visual Studio 編譯動(dòng)態(tài)庫(kù);
- Java 調(diào)用測(cè)試。
如果你不想污染自己的JDK bin,可以將 .dll 和 .lib 放到一個(gè)統(tǒng)一的目錄下,這樣的話切換 jdk 比較方便,因?yàn)槲抑皇褂?jdk8,所以貪圖方便放在了 bin 目錄,但這是不規(guī)范滴 。
到此這篇關(guān)于Java調(diào)用C++動(dòng)態(tài)庫(kù)(DLL)的完整實(shí)踐指南的文章就介紹到這了,更多相關(guān)Java調(diào)用C++動(dòng)態(tài)庫(kù)DLL內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
實(shí)例解析Java關(guān)于static的作用
只要是有學(xué)過(guò)Java的都一定知道static,也一定能多多少少說(shuō)出一些作用和注意事項(xiàng)。文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
logback ThresholdFilter臨界值日志過(guò)濾器源碼解讀
這篇文章主要為大家介紹了logback ThresholdFilter臨界值日志過(guò)濾器源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11
圖解Java?ReentrantLock公平鎖和非公平鎖的實(shí)現(xiàn)
ReentrantLock是Java并發(fā)中十分常用的一個(gè)類(lèi),具備類(lèi)似synchronized鎖的作用。但是相比synchronized,?它具備更強(qiáng)的能力,同時(shí)支持公平鎖和非公平鎖。本文就來(lái)聊聊ReentrantLock公平鎖和非公平鎖的實(shí)現(xiàn),需要的可以參考一下2022-10-10
feign開(kāi)啟日志Logger.Level?feignLoggerLevel()中Level爆紅的解決
這篇文章主要介紹了feign開(kāi)啟日志Logger.Level?feignLoggerLevel()中Level爆紅的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06
SpringBoot同時(shí)集成Mybatis和Mybatis-plus框架
在實(shí)際開(kāi)發(fā)中,項(xiàng)目里面一般都是Mybatis和Mybatis-Plus公用,但是公用有版本不兼容的問(wèn)題,本文主要介紹了Spring Boot項(xiàng)目中同時(shí)集成Mybatis和Mybatis-plus,具有一檔的參考價(jià)值,感興趣的可以了解一下2024-12-12
CompletableFuture并行處理List分批數(shù)據(jù)demo
這篇文章主要介紹了CompletableFuture并行處理List分批數(shù)據(jù)實(shí)現(xiàn)實(shí)例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11

