Update 14/07/2014: Sửa lỗi tiếng Anh.

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.

Have you ever wanted to write a java program, but it doesn’t have library like some other languages (example C language) to do it? It is the reason why you read this post, or another reason making you want to use functions in C to make your Java program can faster.

To do it, you need use JNI library (Java Native Interface), it can call C functions from Java and vice versa. Basically, we will write functions in C, compile, build library and call them via JNI. We will learn via example call factorial function step by step. Attention please, all these file I wrote here are in the same forder is Desktop.

During the implementation process, I work in Ubuntu, with Windows or other Linux Distro can be differ a little. This post, I wrote after consulting from Gọi hàm C từ Java and Call c function from Java, however I had met some error while processing and now I will show them for you, too.

Bảng nội dung – Table of content
Bước 1: Tạo và dịch file java sang file class – Step 1: Create and compile java file to class file
Bước 2: Tạo file header bằng javah – Step 2: Create header file by javah
Bước 3: Dịch chương trình C ra share library – Step 3: Compile C program to share library
Bước 4: Chạy chương trình java – Step 4: Run java program
Khắc phục một số lỗi – Fix some errors

Bước 1: Tạo và dịch file java sang file class – Step 1: Create and compile java file to class file

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

In step, we need to load library and report function coded by 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:

After create file, we compile by command:

javac CallCFunction.java

Bước 2: Tạo file header bằng javah – Step 2: Create header file by 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.

C program write factorial function, which is reported in java file, so we need to create header file to use.

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.

After run command, CallCFunction.h is created. You pay attention to line 15.16, this is function we will write in C program.

/* 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 – Step 3: Compile C program to 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.

Create factorial.c file contain factorial function. You copy two line 15 and 16 in CallCFunction.h file and fill variables and we have program.

#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 program\n");
	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

In that, 4th line is the function that calculates the factorial, jlong is the return value as type long, two parameter default JNIEnv *env and jobject obj you don’t need care (and I don’t understand them), 3rd parameter is jint n, it is variable int n corresponding in function factorial(int n) in file CallCFunction.java.

Now, we will create library and share it into forder contain java (your forder can differ). In the file CallCFunction.java we load factorial library by command System.loadLibrary(“factorial”);, so us library must have string lib before, ie the 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 – Step 4: Run java program

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

Now we are enjoying achievements by run program.

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

The -Djava.library.path=. report for program can find library created in current forde (forder conatain libfactorial.so). Addition, you can set environment variable for forder by command export LD_LIBRARY_PATH=. (have dot on last), then you can run by command java CallCFunction

call c in java

Khắc phục một số lỗi – Fix some errors

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.

During the implementation process, I have encounter some errors, if you are like me, try to follow the following remedies. If there are any ohther errors, you can share on this to exchange.

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/”

1. Can’t compile java by command
If you can’t compile by javac command although jdk is installed, maybe you don’t set the environment variable for it. You have to find java, javac, javah located in any folder and proceed to set the environment for that folder. Assuming you do have your JDK in “/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à:

2. Could not find jni.h library
If during compile, it notice error file not found, maybe you don’t set the environment variable for it, so you have to do it. For example its:

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:

3. Error create header file
During create header file (CallCFunction.h), you can have a error:

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.

That’s when you execute the command in a different directory don’t contains *.class files, which is created when compile *.java file. In other words, can not find *.class file. You need to set classpath to forder contain *.class file. Assuming it is in 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).

4. Could not find jni_md.h library
You need find where is it. with me, it is located in /usr/lib/jvm/jdk1.8.0_05/include/linux, now you just copy it into /usr/lib/jvm/jdk1.8.0_05/include/ (forder contain jni.h file).