JNI – Mảng

5/5 - (53 votes)

Trong phần này chúng ta sẽ tìm hiểu cách thao tác với mảng.

Mảng dữ liệu cơ bản

Chúng ta sẽ viết hàm tính tổng các phần tử số nguyên trong một mảng int.

Đầu tiên chúng ta viết đoạn code main trong Java như sau:

class IntArray {
    static {
        System.loadLibrary("SumIntArr");
    }
    private native int sum(int[] arr);
    public static void main(String[] args) {
        int[] arr = new int[] {18, 4, 5, 7, 1, 2, 9, 3, 7, 9};
        int s = new IntArray().sum(arr);
        System.out.println("Sum of array 'arr' is " + s);
    } 
}

Ở đây chúng ta tạo một mảng int có 10 phần tử, phương thức native là sum() sẽ tính tổng mảng này. Tên thư viện native chúng ta đặt là SumIntArr.

Sau đó chúng ta dịch và tạo file header C++:

javac IntArray.java
...
javah -classpath . -jni IntArray
...

File IntArray.h được tạo ra với phương thức native như sau:

...
JNIEXPORT jint JNICALL Java_IntArray_sum
 (JNIEnv *, jobject, jintArray);
...

Theo như bạn thấy thì kiểu int[] trong Java sẽ được chuyển tương ứng thành jintArray trong JNI.

Bây giờ chúng ta tạo file C++ tên SumIntArray.cpp để code phương thức trên như sau:

#include "IntArray.h"

JNIEXPORT jint JNICALL Java_IntArray_sum (JNIEnv *e, jobject obj, jintArray arr) { 
    jint length = e->GetArrayLength(arr);
   
    jint convertedArr[length]; 
    e->GetIntArrayRegion(arr, 0, length, convertedArr); 
 
    jint result = 0;
    for(jint i = 0 ; i < length ; i++) 
        result += convertedArr[i]; 
    return result;
}

Kiểu jintArray không phải là một mảng, mảng int trong JNI có cú pháp là jint a[...]. Do đó chúng ta phải chuyển đổi kiểu jintArray này.

Để chuyển thì chúng ta gọi phương thức GetIntArrayRegion(), phương thức này nhận vào 4 tham số là đối tượng jintArray, vị trí bắt đầu mảng, vị trí kết thúc mảng, và mảng jint [] đích sẽ được chuyển đổi. Vị trí bắt đầu là 0, vị trí kết thúc là độ dài của mảng, độ dài này chúng ta lấy bằng phương thức GetArrayLength(). Mảng jint chúng ta tạo ra trước có tên là convertedArr.

Sau khi đã chuyển đổi xong thì chúng ta có thể dùng vòng lặp for, while để duyệt như mảng bình thường.

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 SumIntArr.o -c SumIntArr.cpp
...
g++ -Wl,--add-stdcall-alias -shared -o SumIntArr.dll SumIntArr.o
...
java -classpath . -Djava.library.path=. IntArray
...
Sum of array 'arr' is 65

Ngoài phương thức GetIntArrayRegion() trên thì chúng ta có thể dùng phương thức GetIntArrayElements() như sau:

#include "IntArray.h"

JNIEXPORT jint JNICALL Java_IntArray_sum (JNIEnv *e, jobject obj, jintArray arr) {  
    jint *convertedArr;
    convertedArr = e->GetIntArrayElements(arr, NULL);

    jint length = e->GetArrayLength(arr);

    jint result = 0;
    for(jint i = 0 ; i < length ; i++)
        result += convertedArr[i];

    e->ReleaseIntArrayElements(arr, convertedArr, 0);
    return result;
}

Điểm khác biệt giữa 2 phương thức là phương thức GetIntArrayRegion() lấy một khoảng cố định trong mảng trong khi GetIntArrayElements() sẽ lấy tất cả các phần tử trong mảng. Sau khi dùng phương thức GetIntArrayElements() thì chúng ta gọi phương thức ReleaseIntArrayElements() để giải phóng mảng đó ra khỏi bộ nhớ.

Đối với các kiểu dữ liệu khác thì chúng ta cũng làm tương tự, bảng dưới đây là tên các phương thức ứng với từng kiểu dữ liệu:

PHƯƠNG THỨC Array Type Native Type
GetBooleanArrayElements() jbooleanArray jboolean
GetByteArrayElements() jbyteArray jbyte
GetCharArrayElements() jcharArray jchar
GetShortArrayElements() jshortArray jshort
GetIntArrayElements() jintArray jint
GetLongArrayElements() jlongArray jlong
GetFloatArrayElements() jfloatArray jfloat
GetDoubleArrayElements() jdoubleArray jdouble

Mảng hai chiều

Mảng 2 chiều là mảng chứa các phàn tử là mảng khác, thao tác với mảng 2 chiều cũng tương tự như với mảng một chiều.

Ví dụ chúng ta có code main Java như sau:

class TwoDArray {
    static {
        System.loadLibrary("Display2DArray");
    }
 
    private static native void display(int arr[][]);
    public static void main(String[] args) {
        int arr[][] = new int[][] {
            {9, 3, 5},
            {1, 2, 4},
            {4, 5, 7}
        };
        new TwoDArray().display(arr);
    }
}

Ở đây chúng ta có phương thức native là display(), phương thức này nhận vào một mảng 2 chiều.

Ở phía C++ chúng ta sẽ code phương thức này làm việc hiển thị các phần tử trong mảng như sau:

#include <iostream>
#include "TwoDArray.h"
using namespace std;

JNIEXPORT void JNICALL Java_TwoDArray_display
    (JNIEnv *e, jclass cls, jobjectArray arr) {
 
    jint n = e->GetArrayLength(arr);
 
    for(jint i = 0 ; i < n ; i++) {
 
        jintArray innerArrObj = (jintArray)e->GetObjectArrayElement(arr, i);
        jint m = e->GetArrayLength(innerArrObj);
        jint *innerArr = e->GetIntArrayElements(innerArrObj, NULL);
 
        for(jint j = 0 ; j < m ; j++) {
           cout << innerArr[j] << " ";
        }
        cout << endl;
    } 
}

Đầu tiên chúng ta lấy độ dài mảng chính, đặt tên là n.

Sau đó chúng ta lặp n lần, mỗi lần lặp chúng ta lấy từng mảng con ra, và vì mảng 2 chiều là mảng chứa các con trỏ, không phải các kiểu dữ liệu cơ bản nên chúng ta dùng phương thức GetObjectArrayElement(), tham số đầu tiên là biến mảng chính, tham số thứ 2 là vị trí phần tử. Ở đây khi lấy về chúng ta ép kiểu sang jintArray, tùy loại dữ liệu mà bạn sẽ ép sang từng kiểu khác nhau.

Sau đó chúng ta lại lặp qua mảng một chiều vừa lấy được như trong ví dụ đầu tiên.

9 3 5
1 2 4
4 5 7
0 0 votes
Article Rating
Subscribe
Thông báo cho tôi qua email khi
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments