Chúng ta sẽ tìm hiểu về các tham số trong các phương thức native.
Để làm việc này thì chúng ta sẽ viết hàm native trong Java, hàm này sẽ nhận vào một chuỗi kí tự, bên C++ sẽ nhận chuỗi kí tự này và in ra màn hình.
Đầu tiên chúng ta tạo file .java
như sau:
public class Arg { static { System.loadLibrary("Arg"); } private native void sendName(String name); public static void main(String args[]){ String name = "Pho Code"; Arg arg = new Arg(); arg.sendName(name); } }
Phương thức native
là sendName(),
chúng ta truyền vào với chuỗi là "Pho Code"
.
Sau đó dịch và tạo file header C++:
javac Arg.java ... javah -classpath . -jni Arg ...
File Arg.h
với phương thức native được chuyển sang C++ sẽ được sinh ra như thế này:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class Arg */ #ifndef _Included_Arg #define _Included_Arg #ifdef __cplusplus extern "C" { #endif /* * Class: Arg * Method: sendName * Signature: (Ljava/lang/String;)V */ JNIEXPORT void JNICALL Java_Arg_sendName (JNIEnv *, jobject, jstring); #ifdef __cplusplus } #endif #endif
Trong đó JNIEXPORT
và JNICALL
là các macro được định nghĩa trong file jni.h,
các macro này cho biết là hàm đó có thể được gọi từ nơi khác (giống như từ khóa extern
trong C). Bạn cũng có thể để ý tên phương thức được tạo nên từ Java_<tên_lớp>_<tên_phương_thức>.
Và trong phương thức native này bao giờ cũng sẽ có ít nhất là 2 tham số JNIEnv*
và jobject
. Về cơ bản thì đối tượng JNIEnv*
là một con trỏ trỏ tới một con trỏ, con trỏ này trỏ tới một mảng, mảng này lại chứa các con trỏ trỏ tới các phương thức của JNI để chúng ta có thể sử dụng.
Tham số jobject
sẽ khác nhau tùy thuộc vào phương thức native ở bên phía Java như thế nào. Nếu phương thức native là phương thức static thì jobject
sẽ đại diện cho lớp chứa phương thức đó, còn nếu không thì jobject
sẽ là đối tượng được tạo ra từ lớp đó.
Ngoài 2 tham số bắt buộc phải có kia thì chúng ta có thể truyền thêm các tham số khác như bình thường.
Bây giờ chúng ta viết code cho phương thức native C++ như sau:
#include <iostream> #include "Arg.h" JNIEXPORT void JNICALL Java_Arg_sendName (JNIEnv *e, jobject obj, jstring name){ const char *str; str = e->GetStringUTFChars(name, JNI_FALSE); if(str == NULL) return; std::cout << "Hello " << str; e->ReleaseStringUTFChars(name, str); }
Tham số jstring
ở bên Java là biến là một đối tượng java.lang.String
, nhưng C++ thì lại không có kiểu dữ liệu nào như thế này. Do đó chúng ta phải chuyển kiểu dữ liệu, may mắn là trong số các phương thức JNI có phương thức để chuyển, ở đây chúng ta dùng phương thức GetStringUTFChars()
, phương thức này sẽ chuyển jstring
sang một mảng char
, tham số đầu tiên là biến jstring,
tham số thứ 2 là một giá trị bool,
chúng ta có thể dùng JNI_FALSE
hoặc JNI_TRUE,
ý nghĩa của tham số thứ 2 là có muốn thực hiện copy hay không.
Sau khi chuyển xong chúng ta có thể in ra như thường, ngoài ra chúng ta cũng có thể kiểm tra xem việc chuyển đổi có thành công hay không bằng cách kiểm tra xem str
có NULL
hay không.
Cuối cùng chúng ta gọi phương hức ReleaseStringUTFChars()
để xóa biến này khỏi bộ nhớ.
Bây giờ chúng ta có thể dịch và build file thư viện và chạy:
g++ -I"C:\Program Files\Java\jdk1.8.0_65\include" -I"C:\Program Files\Java\jdk1.8.0_65\include\win32 -o Arg.o -c Arg.cpp ... g++ -Wl,--add-stdcall-alias -shared -o Arg.dll Arg.o ... java -classpath . -Djava.library.path=. Arg Hello Pho Code
Ngoài 2 phương thức GetStringUTFChars()
và ReleaseStringUTFChars()
ở trên thì còn rất nhiều phương thức khác như:
jstring NewString(JNIEnv *, const jchar *, jsize
)
: tạo chuỗijava.lang.String
jsize GetStringLength(JNIEnv *, jstring):
lấy độ dài của chuỗijstring
const jchar * GetStringChars(JNIEnv *, jstring,
jboolean *)
:tương tự
GetStringUTFChars()
void ReleaseStringChars(JNIEnv
*,
jstring, const jchar
*)
: tương tựReleaseStringUTFChars()
- …
Bạn có thể xem danh sách tại đây.