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ừ Java và Call 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 *env và jobject 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 |
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 |
1. Can’t compile java by command |
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 |
2. Could not find jni.h library |
export LD_LIBRARY_PATH=/usr/lib/jvm/jdk1.8.0_05/include/ |
|
3. Lỗi khi tạo file header |
3. Error create header file |
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 |
4. Could not find jni_md.h library |