Daily Archives: 05/01/2017

JNI – String và char

Về cơ bản Java sử dụng bảng mã Unicode để lưu trữ chuỗi, còn các ngôn ngữ lập trình native có sử dụng bảng mã này hay không thì tùy thuộc vào hệ điều hành nữa (như C++ trên Windows thì có). Do đó khi chúng ta chuyển đổi chuỗi từ Java sang native thì chúng ta phải xem mình đang sử dụng hệ điều hành có hỗ trợ Unicode hay không, nếu không thì chúng ta không thể dùng các hàm như GetStringUTFChars() hay GetStringUTFRegion().

Tạo đối tượng jstring từ mảng char trong C++

Chúng ta sẽ gọi phương thức khởi tạo String(byte[] bytes) của Java từ C++.

Chúng ta có lớp Java như sau:

class MakeString {
    static { System.loadLibrary("MakeString"); }
 
    private native String makeAString();
 
    public static void main(String[] args) {
        String str = new MakeString().makeAString();
        System.out.println(str);
    }
}

Phương thức native là makeAString(), phương thức này sẽ tạo một chuỗi string nào đó ở phía native.

Code native như sau:

#include "MakeString.h"
#include <string.h>
#include <stdlib.h>
using namespace std;

jstring makeAString(JNIEnv *e, char *str) { 
    jbyteArray bytes = 0;
    int len = strlen(str);
    bytes = e->NewByteArray(len);
 
    jclass string_cls = e->FindClass("Ljava/lang/String;");
    jmethodID string_constructor = e->GetMethodID(string_cls, "<init>", "([B)V");
 
    if(bytes != NULL) {
        e->SetByteArrayRegion(bytes, 0, len, (jbyte *)str);
        jstring result = (jstring)e->NewObject(string_cls, string_constructor, bytes); 
        return result;
    }
    return NULL;
}

JNIEXPORT jstring JNICALL Java_MakeString_makeAString (JNIEnv *e, jobject obj) { 
    char *str = (char*)malloc(12); 
    strcpy(str, "Lorem Ipsum");
    return makeAString(e, str); 
}

Chúng ta viết một hàm là makeAString(), phương thức này nhận vào một mảng char* rồi chuyển thành một đối tượng jstring.

Đầu tiên chúng ta lấy độ dài của mảng char*, sau đó tạo một đối tượng jbyteArray từ phương thức NewByteArray(), phương thức này nhận vào số lượng phần tử và trả về một đối tượng mảng lưu trữ danh sách các phần tử byte.

Tiếp theo chúng ta lấy jclass của lớp java.lang.String, jmethodID của phương thức String(byte[] bytes). 

Nếu ở trên chúng ta tạo đối tượng jbyteArray thành công thì tiếp theo chúng ta sao chép nội dung của mảng char* vào đối tượng này bằng phương thức SetByteArrayRegion(), phương thức này nhận vào đối tượng jbyteArray được sao chép, vị trí bắt đầu của mảng char*, số lượng các phần tử sao chép, cuối cùng là con trỏ đến mảng char* đó.

Cuối cùng chúng ta tạo đối tượng jstring bằng phương thức NewObject(), phương thức này trả về đối tượng jobject nên chúng ta ép kiểu qua jstring.

Lorem Ipsum

Nếu hệ điều hành có hỗ trợ Unicode thì chúng ta chỉ cần gọi phương thức e->NewStringUTF(str) là được ngay một đối tượng jstring rồi chứ không cần làm rườm rà phức tạp như trên.

Chuyển jstring thành mảng char trong C++

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

class ConvertToChar {
    static { System.loadLibrary("ConvertToChar"); }
 
    private native void convert(String str);
 
    public static void main(String[] args) {
        new ConvertToChar().convert("Lorem ipsum dolor sit amet"); 
    }
}

Phương thức convert() nhận vào một đối tượng String và chuyển thành mảng char* bên native.

Code native:

#include "ConvertToChar.h"
#include <stdio.h>
#include <stdlib.h>
using namespace std;

JNIEXPORT void JNICALL Java_ConvertToChar_convert (JNIEnv *e, jobject obj, jstring str) { 
    char *result = 0;
 
    jclass string_class = e->GetObjectClass(str);
    jmethodID getBytesMethod = e->GetMethodID(string_class, "getBytes", "()[B"); 
 
    jbyteArray bytes = (jbyteArray)e->CallObjectMethod(str, getBytesMethod);
    if(!e->ExceptionOccurred()) {
        jint len = e->GetArrayLength(bytes);
        result = (char*)malloc(len + 1);
        e->GetByteArrayRegion(bytes, 0, len, (jbyte*)result);
        result[len] = 0;
    } 
    puts(result);
}

Trong lớp java.lang.String có sẵn phương thức getBytes() trả về một mảng byte bên Java. Chúng ta dùng phương thức này để lấy mảng đó, sau đó dùng phương thức JNI để chuyển thành một mảng char*.

Đầu tiên chúng ta lấy jclassjmethodID như bình thường, sau đó chúng ta gọi phương thức CallObjectMethod(), phương thức này sẽ gọi phương thức getBytes() của lớp java.lang.String, kiểu trả về bên Java là Byte[] và tương ứng bên native là jbyteArray do đó chúng ta ép kiểu trả về là jbyteArray.

Tiếp theo chúng ta kiểm tra xem phương thức này có báo lỗi không, nếu không thì chúng ta lấy độ dài của đối tượng jbyteArray vừa lấy được, rồi dùng hàm malloc() để cấp bộ nhớ cho mảng char*, cuối cùng gọi phương thức GetByteArrayRegion() để chép nội dung trong đối tượng jbyteArray đó vào mảng char*.

Lorem ipsum dolor sit amet