[Java – C] Gọi hàm C trong Java – Call C function in Java

Có khi nào bạn muốn viết một chương trình Java nhưng lại không có thứ viện giống như một số ngôn ngữ (chẳng hạn là C) để thực hiện? Đó là một lý do để bạn đọc bài viết này, hoặc một lý do khác làm bạn muốn sử dụng các hàm từ C để chương trình Java của bạn có thể chạy nhanh hơn!

Để làm được việc này, bạn cần sử dụng thư viện JNI (Java Native Interface), nó có thể gọi hàm C từ Java và ngược lại. Về cơ bản, chúng ta sẽ viết các hàm trong C, dịch ra thành thư viện rồi từ Java gọi các hàm trong thư viện đó qua JNI. Chúng ta sẽ lần lượt từng bước tìm hiểu nó qua ví dụ gọi hàm tính giai thừa. Lưu ý các file mình viết dưới đây đều để cùng thư mục là Desktop.

Trong quá trình thực hiện, mình làm trên Ubuntu, trên windows hoặc các Linux distro khác có thể khác đôi chút. Bài viết này mình viết sau khi tham khảo 2 bài Gọi hàm C từ JavaCall c function from Java, tuy nhiên trong quá trình thực hiện có gặp một số lỗi và mình cũng đưa cách khắc phục luôn.

Bảng nội dung
Bước 1: Tạo và dịch file java sang file class
Bước 2: Tạo file header bằng javah
Bước 3: Dịch chương trình C ra share library
Bước 4: Chạy chương trình java
Khắc phục một số lỗi

Bước 1: Tạo và dịch file java sang file class


Ở bước này chúng ta cần load thư viện và khai báo hàm được viết từ C.

class CallCFunction {
	
	// report funcion write in C.
	private native long factorial(int n);
	public static void main(String[] args) {
		CallCFunction ccf = new CallCFunction();
		int n = 5;
		System.out.println(n + "! = " + ccf.factorial(5));
	}
	
	// load library factorial to use.
	static {
		System.loadLibrary("factorial");
	}
}

Sau khi tạo được file thì dịch nó bằng lệnh:

javac CallCFunction.java

Bước 2: Tạo file header bằng javah

Chương trình C viết hàm factorial mà trong file java đã khai báo, dó đó chúng ta cần tạo ra file header để có thể dùng.

javah -jni CallCFunction

Sau khi thực hiện lệnh trên, file CallCFunction.h sẽ được tạo ra. Các bạn chú ý đến dòng 15,16, đây chính là hàm chúng ta sẽ viết trong chương trình C.

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

#ifndef _Included_CallCFunction
#define _Included_CallCFunction
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     CallCFunction
 * Method:    factorial
 * Signature: (I)J
 */
JNIEXPORT jlong JNICALL Java_CallCFunction_factorial
  (JNIEnv *, jobject, jint);

#ifdef __cplusplus
}
#endif
#endif

Bước 3: Dịch chương trình C ra share library


Tạo file factorial.c chứa hàm tính giai thừa. Bạn copy 2 dòng 15, 16 trong file CallCFunction.h và điền thêm các biến vào ta được chương trình dưới đây.

#include <jni.h>  
#include <stdio.h>  
#include "CallCFunction.h"   
JNIEXPORT jlong JNICALL Java_CallCFunction_factorial (JNIEnv *env, jobject obj, jint n)
{
	printf("funcion 'factorial' create in C programn");
	int i;
	long result = 1;
	for (i = 2; i <= n; i++)
	{
		result *= i;
	}

	return result;
}

Trong đó, dòng thứ 4 là hàm tính giai thừa, jlong chỉ giá trị trả về là kiểu long, 2 tham số JNIEnv *envjobject obj là mặc định và bạn chưa cần quan tâm (mình cũng chưa hiểu rõ về nó), tham số thứ 3 là jint n ám chỉ biến int n tương ứng trong hàm factorial(int n) ở file CallCFunction.java.

Bây giờ chúng ta sẽ tạo thư viện và chia sẻ nó đến thư mục chứa java (thư mục chứa java của bạn có thể khác nhé). Ở file CallCFunction.java chúng ta load thư viện factorial bằng lệnh System.loadLibrary(“factorial”);, do đó thư viện của chúng ta phải có thêm chuỗi lib đằng trước tức là libfactorial.so

gcc -shared -I/usr/lib/jvm/jdk1.8.0_05/include/ factorial.c -o libfactorial.so

Bước 4: Chạy chương trình java


Bây giờ chúng ta hưởng thụ thành quả bằng việc chạy chương trình thôi.

java -Djava.library.path=. CallCFunction

Cái -Djava.library.path=. là báo cho chương trình có thể tìm thấy thư viện vửa tạo ra ở thư mục hiện tại (thư mục chứa file libfactorial.so). Ngoài ra bạn có thể đặt biến môi trường cho thư mục đó bằng lệnh export LD_LIBRARY_PATH=. (có dấu chấm ở cuối), khi đó bạn có thể chạy ngay bằng lệnh java CallCFunction

call c in java

Khắc phục một số lỗi


Trong quá trình thực hiện, mình có gặp một số lỗi, nếu các bạn cũng bị giống mình hãy thử làm theo cách khắc phục dưới đây, nếu gặp lỗi nào đó khác các bạn có thể chia sẽ lên đây cùng trao đổi.

1. Không thể dịch java bằnd lệnh
Nếu bạn không thể dịch java bằng lệnh javac mặc dù đã cài đặt jdk thì có lẽ bạn chưa đặt biến môi trường cho nó. Bạn tìm xem các file java, javac, javah nằm ở thư mục nào và tiến hành đặt biến môi trường cho thư mục đó. Giả sử JDK nằm trong “/usr/lib/jvm/jdk1.8.0_05/”

export JAVA_HOME=/usr/lib/jvm/jdk1.8.0_05/
export PATH=$JAVA_HOME/bin:$PATH

2. Không tìm thấy thư viện jni.h
Nếu khi dịch mà báo lỗi không thấy thư viện này thì có lẽ nó chưa được đặt biến môi trường để dùng. và bạn cần làm điều này. Ví dụ của mình là:

export LD_LIBRARY_PATH=/usr/lib/jvm/jdk1.8.0_05/include/

3. Lỗi khi tạo file header
Trong quá trình tạo file header (CallCFunction.h), bạn có thể gặp lỗi như sau:

Exception in thread "main" java.lang.IllegalArgumentException: Not a valid class name: HelloWorld
	at com.sun.tools.javac.api.JavacTool.getTask(JavacTool.java:129)
	at com.sun.tools.javac.api.JavacTool.getTask(JavacTool.java:107)
	at com.sun.tools.javac.api.JavacTool.getTask(JavacTool.java:64)
	at com.sun.tools.javah.JavahTask.run(JavahTask.java:503)
	at com.sun.tools.javah.JavahTask.run(JavahTask.java:329)
	at com.sun.tools.javah.Main.main(Main.java:46)

Đó là khi bạn thực thi lệnh tại một thư mục khác không chứa file *.class tạo ra khi dịch file *.java. Nói cách khác là không thể tìm thấy file *.class. Bạn cần đặt classpath đến thư mục chứa file class đó. Giả sử nó nằm ở Desktop.

javah -jni -classpath /home/nguyenvanquan7826/Desktop/ HelloWorld

4. Không tìm thấy thư viện jni_md.h
Bạn tìm xem thư viện này ở đâu trong máy tính. Của mình, nó nẳm ở /usr/lib/jvm/jdk1.8.0_05/include/linux, bây giờ chỉ cần copy nó sang /usr/lib/jvm/jdk1.8.0_05/include/ (forder chứa file jni.h).