首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 調用 C++ DLL 完整實戰指南 (JNI)

     編輯:Java 相關知識

項目背景

Web 端人臉識別項目:核心算法用 C++ 實現,Java 後台需通過 JNI (Java Native Interface) 調用 DLL 庫實現高性能計算。

完整開發流程

第一階段:Java 端定義 Native 方法

package com.test.jni;

public class Java2cpp {
    // 載入 DLL (文件名不含擴展名)
    static { System.loadLibrary("JavaCallcpp"); }
    
    // 聲明 native 方法
    public native int DLL_ADD(int a, int b);
    public native int DLL_SUB(int a, int b);
    public native int DLL_MUL(int a, int b);
    public native int DLL_DIV(int a, int b);
    
    public static void main(String[] args) {
        Java2cpp jni = new Java2cpp();
        System.out.println("加法: " + jni.DLL_ADD(10, 5));
        System.out.println("減法: " + jni.DLL_SUB(10, 5));
        System.out.println("乘法: " + jni.DLL_MUL(10, 5));
        System.out.println("除法: " + jni.DLL_DIV(10, 5));
    }
}

第二階段:生成 C/C++ 頭文件

# 1. 編譯 Java 文件
javac com/test/jni/Java2cpp.java

# 2. 生成頭文件 (在 bin 目錄下執行)
javah -classpath . -jni com.test.jni.Java2cpp
# 生成: com_test_jni_Java2cpp.h

生成的頭文件關鍵內容:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
#ifndef _Included_com_test_jni_Java2cpp
#define _Included_com_test_jni_Java2cpp
#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT jint JNICALL Java_com_test_jni_Java2cpp_DLL_1ADD
  (JNIEnv *, jobject, jint, jint);

JNIEXPORT jint JNICALL Java_com_test_jni_Java2cpp_DLL_1SUB
  (JNIEnv *, jobject, jint, jint);

JNIEXPORT jint JNICALL Java_com_test_jni_Java2cpp_DLL_1MUL
  (JNIEnv *, jobject, jint, jint);

JNIEXPORT jint JNICALL Java_com_test_jni_Java2cpp_DLL_1DIV
  (JNIEnv *, jobject, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

第三階段:Visual Studio 創建 DLL 項目

  1. 新建 Win32 Project → DLL 類型 → 名稱 TestJNI
  2. 複製生成的 com_test_jni_Java2cpp.h 到項目目錄
  3. 配置包含目錄:
    JDK安裝路徑/include
    JDK安裝路徑/include/win32

第四階段:實現 Native 邏輯

1. 定義純 C++ 接口 (dllApi.h)

#include "com_test_jni_Java2cpp.h"

int DLL_API_ADD(int a, int b);
int DLL_API_SUB(int a, int b);
int DLL_API_MUL(int a, int b);
int DLL_API_DIV(int a, int b);

2. 實現核心算法 (dllApi.cpp)

#include "stdafx.h"
#include "dllApi.h"

int DLL_API_ADD(int a, int b) { return a + b; }
int DLL_API_SUB(int a, int b) { return a - b; }
int DLL_API_MUL(int a, int b) { return a * b; }
int DLL_API_DIV(int a, int b) { return a / b; }

3. JNI 橋接實現 (TestJNI.cpp)

#include "stdafx.h"
#include "TestJNI.h"
#include "com_test_jni_Java2cpp.h"
#include "dllApi.h"

JNIEXPORT jint JNICALL Java_com_test_jni_Java2cpp_DLL_1ADD
  (JNIEnv *env, jobject obj, jint a, jint b) {
    return DLL_API_ADD(a, b);
}

JNIEXPORT jint JNICALL Java_com_test_jni_Java2cpp_DLL_1SUB
  (JNIEnv *env, jobject obj, jint a, jint b) {
    return DLL_API_SUB(a, b);
}

JNIEXPORT jint JNICALL Java_com_test_jni_Java2cpp_DLL_1MUL
  (JNIEnv *env, jobject obj, jint a, jint b) {
    return DLL_API_MUL(a, b);
}

JNIEXPORT jint JNICALL Java_com_test_jni_Java2cpp_DLL_1DIV
  (JNIEnv *env, jobject obj, jint a, jint b) {
    return DLL_API_DIV(a, b);
}

命名規則:Java_包名_類名_方法名,方法名中 _1 代表下劃線轉義。

第五階段:編譯生成 DLL 並部署

  1. VS 中 Build → Build Solution,生成 TestJNI.dll (位於 Debug/ 或 Release/)
  2. 重命名:TestJNI.dll → JavaCallcpp.dll (與 System.loadLibrary 參數一致)
  3. 部署:複製 JavaCallcpp.dll 到 JRE/bin/ 或項目啟動目錄,或配置 java.library.path

第六階段:運行驗證

java com.test.jni.Java2cpp
# 輸出:
# 加法: 15
# 減法: 5
# 乘法: 50
# 除法: 2

進階主題與最佳實踐

數據類型映射表

Java 類型JNI 類型C/C++ 類型
booleanjbooleanunsigned char (8位)
bytejbytesigned char (8位)
charjcharunsigned short (16位)
shortjshortshort (16位)
intjintint (32位)
longjlonglong long (64位)
floatjfloatfloat (32位)
doublejdoubledouble (64位)
Stringjstring需用 GetStringUTFChars 轉換
int[]jintArray需用 GetIntArrayElements 訪問
Objectjobject通用對象引用

字符串處理示例

// Java
public native String processString(String input);

// C++
JNIEXPORT jstring JNICALL Java_com_test_jni_Java2cpp_processString
  (JNIEnv *env, jobject obj, jstring input) {
    const char *str = env->GetStringUTFChars(input, NULL);
    // 處理邏輯...
    char result[256];
    sprintf(result, "Processed: %s", str);
    env->ReleaseStringUTFChars(input, str);
    return env->NewStringUTF(result);
}

數組處理示例

// Java
public native int sumArray(int[] arr);

// C++
JNIEXPORT jint JNICALL Java_com_test_jni_Java2cpp_sumArray
  (JNIEnv *env, jobject obj, jintArray arr) {
    jint *elements = env->GetIntArrayElements(arr, NULL);
    jsize len = env->GetArrayLength(arr);
    int sum = 0;
    for (int i = 0; i < len; i++) sum += elements[i];
    env->ReleaseIntArrayElements(arr, elements, 0); // 0: 複製回並釋放
    return sum;
}

異常處理與資源管理

  • 檢查異常:JNI 調用後檢查 env->ExceptionCheck()
  • 釋放資源:GetXXX 必須配對 ReleaseXXX,防止內存洩漏
  • 全局引用:長期持有 Java 對象用 NewGlobalRef,用畢 DeleteGlobalRef
  • 線程安全:JNIEnv 僅在當前線程有效,跨線程需 AttachCurrentThread

替代方案對比

方案優點缺點適用場景
JNI性能最高、直接調用、無序列化開銷開發複雜、平台相關、崩潰風險高、調試困難高性能計算、硬件交互、legacy C++庫復用
JNA (Java Native Access)純 Java、無需編譯 C++、跨平台、開發簡單性能較 JNI 低 10-20%、複雜結構體映射麻煩快速集成、簡單接口、跨平台需求
進程間通信 (Socket/gRPC)解耦合、語言無關、獨立部署、故障隔離網絡延遲、序列化開銷、運維複雜度高微服務架構、重型計算服務、多語言團隊

總結

JNI 是 Java 與 C/C++ 互操作的橋樑,核心流程:Java 聲明 native → javah 生成頭文件 → C++ 實現 JNIEXPORT → 編譯 DLL → System.loadLibrary 載入。關鍵點在於命名規範匹配、數據類型正確映射、內存管理嚴謹。生產環境建議評估 JNA 或微服務化替代方案,僅在極致性能場景堅持使用 JNI。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved