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

Java JNI 呼叫 C 函數完整實戰教學

     編輯:Java 程式設計入門

前言:跨語言橋樑 JNI 簡介

從 C/C++ 邁向 Java,再透過 JNI 回頭呼叫 C/C++,這就是連接兩個世界的關鍵橋樑。JNI (Java Native Interface) 讓 Java 程式能夠直接調用原生 C/C++ 函式庫,對於效能敏感型應用或 Java 無法直接處理的底層任務,這是一種極佳的解決方案。本文將以一個完整的「Java 呼叫 C 函數」範例,詳細記錄從編碼、編譯到執行的全流程。

核心流程總覽

假設 Java 類別為 J2C,C 來源檔為 J2C.c,互動方法命名為 write2proc。其聲明在 Java 端,實作在 C 端。完整步驟如下:

  1. 編寫並編譯 Java 程式 (javac J2C.java)
  2. 產生 C/C++ 標頭檔 (javah J2C → J2C.h)
  3. 編寫對應的 C 程式 (J2C.c)
  4. 編譯 C 程式產生目標檔 (gcc -I... -fPIC -c J2C.c)
  5. 產生共享函式庫 (gcc -shared ... -o libj2c.so.1.0)
  6. 重命名共享庫 (cp libj2c.so.1.0 libj2c.so)
  7. 設定動態連結庫路徑 (export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH)
  8. 執行 Java 程式驗證 (java J2C)

詳細程式碼實作

1. Java 端:J2C.java

import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;

public class J2C {
    static {
        try {
            // 載入名為 "j2c" 的共享庫,對應檔案名 libj2c.so
            System.loadLibrary("j2c");
        } catch (UnsatisfiedLinkError e) {
            System.err.println("無法載入 J2C 函式庫:\n" + e);
        }
    }

    // 宣告 native 方法
    public static native int write2proc(int pid);

    public static void main(String[] args) {
        // 取得目前 JVM 進程 ID
        final RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
        final String info = runtime.getName(); // 格式通常為 "pid@hostname"
        final int index = info.indexOf("@");

        if (index != -1) {
            final int pid = Integer.parseInt(info.substring(0, index));
            System.out.println("Process Info: " + info);
            System.out.println("PID: " + pid);

            // 呼叫 C 函數
            write2proc(pid);
        }

        try { Thread.sleep(8000); } catch (InterruptedException e) { e.printStackTrace(); }
    }
}

關鍵點: System.loadLibrary("j2c") 會自動尋找 libj2c.so (Linux) 或 j2c.dll (Windows),庫名必須與編譯產出的共享庫名稱一致。

2. 自動產生標頭檔:J2C.h

執行 javah J2C 後產生(請勿手動修改):

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class J2C */

#ifndef _Included_J2C
#define _Included_J2C
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     J2C
 * Method:    write2proc
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_J2C_write2proc
  (JNIEnv *, jclass, jint);

#ifdef __cplusplus
}
#endif
#endif

函數簽名 Java_J2C_write2proc 遵循命名規則:Java_類別名_方法名。C 端實作必須完全符合此簽名。

3. C 端實作:J2C.c

#include <stdio.h>
#include "J2C.h"

JNIEXPORT jint JNICALL Java_J2C_write2proc(JNIEnv *env, jclass cls, jint pid) {
    printf("C 語言端收到 PID: %d\n", pid);
    return 0;
}

4. 編譯與連結指令詳解 (Linux 環境)

# 1. 編譯為目標檔,需指定 JDK 的 include 路徑 (含 jni.h 與平台相關頭檔)
gcc -I/usr/lib/jvm/java-6-openjdk-amd64/include \
    -I/usr/lib/jvm/java-6-openjdk-amd64/include/linux \
    -fPIC -c J2C.c -o J2C.o

# 2. 產生共享庫
gcc -shared -Wl,-soname,libj2c.so.1 -o libj2c.so.1.0 J2C.o

# 3. 建立連結供執行期載入
cp libj2c.so.1.0 libj2c.so

# 4. 設定庫搜尋路徑 (當前目錄)
export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH

# 5. 執行
java J2C

提示: 若遇到連結錯誤,可嘗試改用 g++ 編譯 C++ 相容模式。JDK 版本路徑請依實際安裝位置調整 (如 $JAVA_HOME/include)。

常見坑點總結

  • 方法簽名不一致: Java 端 native 宣告、javah 產生的頭檔、C 端實作三者必須完全一致 (類別名、方法名、參數類型)。
  • 庫名稱不匹配: System.loadLibrary("name") 對應 libname.so (Linux) 或 name.dll (Windows),前綴 lib 由系統自動添加。
  • 路徑問題: 執行時需確保 LD_LIBRARY_PATH (Linux) 或 PATH (Windows) 包含共享庫所在目錄。

結語

JNI 雖然強大,但也引入了平台相依性與記憶體管理複雜度。實務上建議:僅在必要時使用 (如高效能運算、硬體控制、舊有 C/C++ 資產復用),並妥善封裝邊界、處理異常,避免 JVM 崩潰。

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